SlideShare a Scribd company logo
@antonarhipov
Idiomatic Kotlin
from formatting to DSLs
Agenda
• Expressions

• Examples from standard library

• DSL
Anton Arhipov


@antonarhipov
Developer Advocate @ JetBrains
Idiomatic kotlin
Idiomatic kotlin
Idiomatic kotlin
Idiomatic - using, containing, or denoting expressions
that are natural to a native speaker
Idiomatic - using, containing, or denoting expressions
that are natural to a native speaker
In case of a programming language:

•Conforms to a commonly accepted style

•E
ff
ectively uses features of the programming language
Idiomatic kotlin
Expressions
try, if, when
fun adjustSpeed(weather: Weather): Drive {


var result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


var result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


result = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


result = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {




val result: Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {




val result: Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {




return if (weather is Rainy) {


Safe()


} else {


Calm()


}


}
fun adjustSpeed(weather: Weather): Drive {




return if (weather is Rainy) {


Safe()


} else {


Calm()


}


}
fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()


fun adjustSpeed(weather: Weather): Drive {


var result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
abstract class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


else
-
>
Calm()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


/
/
else
-
>
Calm()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


/
/
else
-
>
Calm()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


is Sunny
-
>
TODO()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


is Sunny
-
>
TODO()


}


Use expressions!


Use when as expression body


Use sealed classes with when
Use try as expression body
fun tryParse(number: String) : Int? {


try {


return Integer.parseInt(number)


} catch (e: NumberFormatException) {


return null


}


}
Use try as expression body
fun tryParse(number: String) = try {


Integer.parseInt(number)


} catch (e: NumberFormatException) {


null


}
Use try as expression
fun tryParse(number: String) : Int? {


val n = try {


Integer.parseInt(number)


} catch (e: NumberFormatException) {


null


}


println(n)


return n


}
Use elvis operator
class Person(val name: String?, val age: Int?)


val p = retrievePerson()
?
:
Person()
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name


if (name
=
=
null)


throw IllegalArgumentException("Named required")


val age = person.age


if (age
=
=
null) return


println("$name: $age")


}
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name


if (name
=
=
null)


throw IllegalArgumentException("Named required")


val age = person.age


if (age
=
=
null) return


println("$name: $age")


}
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name


if (name
=
=
null)


throw IllegalArgumentException("Named required")


val age = person.age


if (age
=
=
null) return


println("$name: $age")


}
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name
?
:


throw IllegalArgumentException("Named required")


val age = person.age
?
:
return


println("$name: $age")


}
Nullability
Consider using null-safe call
val order = retrieveOrder()


if (order
=
=
null
|
|
order.customer
=
=
null
|
|
order.customer.address
=
=
null){


throw IllegalArgumentException("Invalid Order")


}


val city = order.customer.address.city
Consider using null-safe call
val order = retrieveOrder()


val city = order
?
.
customer
?
.
address
?
.
city
Consider using null-safe call
val order = retrieveOrder()


val city = order
?
.
customer
?
.
address
?
.
city


?
:
throw IllegalArgumentException("Invalid Order")
Avoid not-null assertions !!
val order = retrieveOrder()


val city = order
!
!
.customer
!
!
.address
!
!
.city


“You may notice that the double exclamation mark looks a bit rude:
 

it’s almost like you’re yelling at the compiler. This is intentional.” - Kotlin in Action
Avoid not-null assertions !!
class MyTest {


class State(val data: String)


private var state: State? = null


@BeforeEach


fun setup() {


state = State("abc")


}


@Test


fun foo() {


assertEquals("abc", state
!
!
.data)


}


}
Avoid not-null assertions !!
class MyTest {


class State(val data: String)


private var state: State? = null


@BeforeEach


fun setup() {


state = State("abc")


}


@Test


fun foo() {


assertEquals("abc", state
!
!
.data)


}


}
class MyTest {


class State(val data: String)


private lateinit var state: State


@BeforeEach


fun setup() {


state = State("abc")


}


@Test


fun foo() {


assertEquals("abc", state.data)


}


}
- use lateinit
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
No need for an extra variable
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
Consider using safe cast for type checking
override fun equals(other: Any?) : Boolean {


val command = other as Command


return command.id
=
=
id


}
Consider using safe cast for type checking
override fun equals(other: Any?) : Boolean {


val command = other as Command


return command.id
=
=
id


}
override fun equals(other: Any?) : Boolean {


return (other as? Command)
?
.
id
=
=
id


}
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c
>
=
'A'
&
&
c
<
=
'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c
>
=
'A'
&
&
c
<
=
'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c in 'A'
.
.
'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c in 'A'
.
.
'Z'
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}


for (i in 0 until args.size) {


println("$i: ${args[i]}")


}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}


for (i in 0 until args.size) {


println("$i: ${args[i]}")


}
for (i in args.indices) {


println("$i: ${args[i]}")


}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}


for (i in 0 until args.size) {


println("$i: ${args[i]}")


}
for (i in args.indices) {


println("$i: ${args[i]}")


}
for ((i, arg) in args.withIndex()) {


println("$i: $arg")


}
Classes and Functions
Don’t create classes just to hold functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}
Don’t create classes just to hold functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}


object StringUtils {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}
Don’t create classes just to hold functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}


object StringUtils {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }
Use extension functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}


object StringUtils {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


fun String.isPhoneNumber() =


length
=
=
7
&
&
all { it.isDigit() }
Extension or a member?
https://kotlinlang.org/docs/coding-conventions.html#extension-functions
•Use extension functions liberally.
 

•If a function works primarily on an object, consider making it an
extension with that object as a receiver.
 

•Minimize API pollution, restrict the visibility.
 

•As necessary, use local extension functions, member extension
functions, or top-level extension functions with private visibility.
Use default values instead of overloading
class Phonebook {


fun print() {


print(",")


}


fun print(columnSeparator: String) {}


}


fun main(args: Array<String>) {


Phonebook().print("|")


}
Use default values instead of overloading
class Phonebook {


fun print() {


print(",")


}


fun print(columnSeparator: String) {}


}


fun main(args: Array<String>) {


Phonebook().print("|")


}


class Phonebook {


fun print(separator: String = ",") {}


fun someFun(x: Int) {}


}


fun main(args: Array<String>) {


Phonebook().print(separator = "|")


}
Return multiple values using data classes
fun namedNum(): Pair<Int, String> =


1 to "one"


/
/
same but shorter


fun namedNum2() = 1 to "one"


fun main(args: Array<String>) {


val pair = namedNum()


val number = pair.first


val name = pair.second


}
Return multiple values using data classes
fun namedNum(): Pair<Int, String> =


1 to "one"


/
/
same but shorter


fun namedNum2() = 1 to "one"


fun main(args: Array<String>) {


val pair = namedNum()


val number = pair.first


val name = pair.second


}
data class GameResult(


val rank: Int,


val name: String


)


fun namedNum() =


GameResult(1, "Player 1")


fun main(args: Array<String>) {


val (rank, name) = namedNum()


println("$name, rank $rank")


}
Return multiple values using data classes
data class GameResult(


val rank: Int,


val name: String


)


fun namedNum() =


GameResult(1, "Player 1")


fun main(args: Array<String>) {


val (rank, name) = namedNum()


println("$name, rank $rank")


}
GameResult var1 = namedNum();


int var2 = var1.component1();


String var3 = var1.component2();
Destructuring in loops
fun printMap(map: Map<String, String>) {


for (item in map.entries) {


println("${item.key}
-
>
${item.value}")


}


}
Destructuring in loops
fun printMap(map: Map<String, String>) {


for (item in map.entries) {


println("${item.key}
-
>
${item.value}")


}


}
fun printMap(map: Map<String, String>) {


for ((key, value) in map) {


println("$key
-
>
$value")


}


}
Destructuring in lists
data class NameExt(


val name: String,


val ext: String?


)


fun splitNameExt(filename: String): NameExt {


if ('.' in filename) {


val parts = filename.split('.', limit = 2)


return NameExt(parts[0], parts[1])


}


return NameExt(filename, null)


}


fun splitNameAndExtension(filename: String): NameExt {


if ('.' in filename) {


val (name, ext) = filename.split('.', limit = 2)


return NameExt(name, ext)


}


return NameExt(filename, null)


}
Idiomatic kotlin
Use type aliases for functional types
class Event


class EventDispatcher {


fun addClickHandler(handler: (Event)
-
>
Unit) {}


fun removeClickHandler(handler: (Event)
-
>
Unit) {}


}
Use type aliases for functional types
class Event


class EventDispatcher {


fun addClickHandler(handler: (Event)
-
>
Unit) {}


fun removeClickHandler(handler: (Event)
-
>
Unit) {}


}
typealias ClickHandler = (Event)
-
>
Unit


class EventDispatcher {


fun addClickHandler(handler: ClickHandler) {


}


fun removeClickHandler(handler: ClickHandler) {


}


}
Standard Library
Verify parameters using require()
class Person(


val name: String?,


val age: Int


)


fun processPerson(person: Person) {


if (person.age < 18) {


throw IllegalArgumentException("Adult required")


}


}
Verify parameters using require()
class Person(


val name: String?,


val age: Int


)


fun processPerson(person: Person) {


if (person.age < 18) {


throw IllegalArgumentException("Adult required")


}


}


fun processPerson(person: Person) {


require(person.age
>
=
18) { "Adult required" }


}
Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) =


objects.filter { it is String }
Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) =


objects.filter { it is String }


fun findAllStrings(objects: List<Any>) =


objects.filterIsInstance<String>()
Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) : List<Any> =


objects.filter { it is String }


fun findAllStrings(objects: List<Any>) : List<String> =


objects.filterIsInstance<String>()
Apply operation to non-null elements mapNotNull
data class Result(


val data: Any?,


val error: String?


)


fun listErrors(results: List<Result>): List<String> =


results.map { it.error }.filterNotNull()


fun listErrors(results: List<Result>): List<String> =


results.mapNotNull { it.errorMessage }
compareBy compares by multiple keys
class Person(


val name: String,


val age: Int


)


fun sortPersons(persons: List<Person>) =


persons.sortedWith(Comparator<Person> { person1, person2
-
>


val rc = person1.name.compareTo(person2.name)


if (rc
!
=
0)


rc


else


person1.age - person2.age


})
compareBy compares by multiple keys
class Person(


val name: String,


val age: Int


)


fun sortPersons(persons: List<Person>) =


persons.sortedWith(Comparator<Person> { person1, person2
-
>


val rc = person1.name.compareTo(person2.name)


if (rc
!
=
0)


rc


else


person1.age - person2.age


})


fun sortPersons(persons: List<Person>) =


persons.sortedWith(compareBy(Person
:
:
name, Person
:
:
age))
groupBy to group elements
class Request(


val url: String,


val remoteIP: String,


val timestamp: Long


)


fun analyzeLog(log: List<Request>) {


val map = mutableMapOf<String, MutableList<Request
>
>
()


for (request in log) {


map.getOrPut(request.url) { mutableListOf() }


.add(request)


}


}
groupBy to group elements
class Request(


val url: String,


val remoteIP: String,


val timestamp: Long


)


fun analyzeLog(log: List<Request>) {


val map = mutableMapOf<String, MutableList<Request
>
>
()


for (request in log) {


map.getOrPut(request.url) { mutableListOf() }


.add(request)


}


}


fun analyzeLog(log: List<Request>) {


val map = log.groupBy(Request
:
:
url)


}
Use coerceIn to ensure numbers in range
fun updateProgress(value: Int) {


val actualValue = when {


value < 0
-
>
0


value > 100
-
>
100


else
-
>
value


}


}
fun updateProgress(value: Int) {


val actualValue = value.coerceIn(0, 100)


}
Initializing objects with apply
val dataSource = BasicDataSource(
)

dataSource.driverClassName = "com.mysql.jdbc.Driver"
dataSource.url = "jdbc:mysql://domain:3309/db"
dataSource.username = "username"
dataSource.password = "password"
dataSource.maxTotal = 40
dataSource.maxIdle = 40
dataSource.minIdle = 4
val dataSource = BasicDataSource().apply
{

driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://domain:3309/db"
username = "username"
password = "password"
maxTotal = 40
maxIdle = 40
minIdle = 4
}
Initializing objects with apply
final ClientBuilder builder = new ClientBuilder();


builder.setFirstName("Anton");


builder.setLastName("Arhipov");


final TwitterBuilder twitterBuilder = new TwitterBuilder();


twitterBuilder.setHandle("@antonarhipov");


builder.setTwitter(twitterBuilder.build());


final CompanyBuilder companyBuilder = new CompanyBuilder();


companyBuilder.setName("JetBrains");


companyBuilder.setCity("Tallinn");


builder.setCompany(companyBuilder.build());


final Client client = builder.build();


System.out.println("Created client is: " + client);
Initializing objects with apply
val builder = ClientBuilder()


builder.firstName = "Anton"


builder.lastName = "Arhipov"


val twitterBuilder = TwitterBuilder()


twitterBuilder.handle = "@antonarhipov"


builder.twitter = twitterBuilder.build()


val companyBuilder = CompanyBuilder()


companyBuilder.name = "JetBrains"


companyBuilder.city = "Tallinn"


builder.company = companyBuilder.build()


val client = builder.build()


println("Created client is: $client")
Initializing objects with apply
val builder = ClientBuilder()


builder.firstName = "Anton"


builder.lastName = "Arhipov"


val twitterBuilder = TwitterBuilder()


twitterBuilder.handle = "@antonarhipov"


builder.twitter = twitterBuilder.build()


val companyBuilder = CompanyBuilder()


companyBuilder.name = "JetBrains"


companyBuilder.city = "Tallinn"


builder.company = companyBuilder.build()


val client = builder.build()


println("Created client is: $client")


val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
Domain Specific Languages
“Domain Speci
fi
c”,
i.e. tailored for a speci
fi
c task
“Domain Speci
fi
c”,
i.e. tailored for a speci
fi
c task
Examples:

•Compose strings - stringBuilder

•Create HTML documents - kotlinx.html

•Con
fi
gure routing logic for a web app - ktor

•Generally, build any object graphs. See “type-safe builders”
buildString
//Java
String name = "Joe";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append("Hello, ");
sb.append(name);
sb.append("!n");
}
System.out.println(sb);
//Kotlin
val name = "Joe"
val s = buildString {
repeat(5) {
append("Hello, ")
append(name)
appendLine("!")
}
}
println(s)
kotlinx.html
System.out.appendHTML().html {


body {


div {


a("http:
/
/
kotlinlang.org") {


target = ATarget.blank


+"Main site"


}


}


}


}
Ktor
fun main() {


embeddedServer(Netty, port = 8080, host = "0.0.0.0") {


routing {


get("/html-dsl") {


call.respondHtml {


body {


h1 { +"HTML" }


ul {


for (n in 1
.
.
10) {


li { +"$n" }


}


}


}


}


}


}


}.start(wait = true)


}
Ktor
fun main() {


embeddedServer(Netty, port = 8080, host = "0.0.0.0") {


routing {


get("/html-dsl") {


call.respondHtml {


body {


h1 { +"HTML" }


ul {


for (n in 1
.
.
10) {


li { +"$n" }


}


}


}


}


}


}


}.start(wait = true)


}


Ktor’s routing
Ktor
fun main() {


embeddedServer(Netty, port = 8080, host = "0.0.0.0") {


routing {


get("/html-dsl") {


call.respondHtml {


body {


h1 { +"HTML" }


ul {


for (n in 1
.
.
10) {


li { +"$n" }


}


}


}


}


}


}


}.start(wait = true)


}


kotlinx.html
Ktor’s routing
Lambda with receiver
T.() -> Unit
Build your vocabulary to abstract from scope functions
val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
Build your vocabulary to abstract from scope functions
fun client(c: ClientBuilder.()
-
>
Unit): Client {


val builder = ClientBuilder()


c(builder)


return builder.build()


}


fun ClientBuilder.company(block: CompanyBuilder.()
-
>
Unit) {


company = CompanyBuilder().apply(block).build()


}


fun ClientBuilder.twitter(block: TwitterBuilder.()
-
>
Unit) {


twitter = TwitterBuilder().apply(block).build()


}
val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
val client = client {


firstName = "Anton"


lastName = "Arhipov"


twitter {


handle = "@antonarhipov"


}


company {


name = "JetBrains"


city = "Tallinn"


}


}


println("Created client is: $client")


Build your vocabulary to abstract from scope functions
val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
https://speakerdeck.com/antonarhipov
https://github.com/antonarhipov/idiomatic-kotlin
@antonarhipov

More Related Content

PDF
Idiomatic Kotlin
PDF
Kotlin Coroutines. Flow is coming
PPSX
Kotlin Language powerpoint show file
PDF
Introduction to kotlin
PPTX
Array within a class
PDF
Kotlin - Better Java
PDF
GraphQL IN Golang
PDF
Domain Driven Design com Python
Idiomatic Kotlin
Kotlin Coroutines. Flow is coming
Kotlin Language powerpoint show file
Introduction to kotlin
Array within a class
Kotlin - Better Java
GraphQL IN Golang
Domain Driven Design com Python

What's hot (19)

PPT
The Kotlin Programming Language
PDF
Java modules
PPTX
LISP:Control Structures In Lisp
PPTX
Presentation on Android application life cycle and saved instancestate
PPTX
Hydra: A Vocabulary for Hypermedia-Driven Web APIs
PPT
sets and maps
PPTX
Mastering the Sling Rewriter
PDF
Kotlin @ Coupang Backend 2017
PDF
[Golang] 以 Mobile App 工程師視角,帶你進入 Golang 的世界 (Introduction of GoLang)
PDF
Introduction of python
PDF
Kotlin scope functions
ODP
Php variables (english)
PPTX
Python built in functions
PPTX
Coroutines in Kotlin
PDF
Java 8 Default Methods
PPTX
Kotlin presentation
DOCX
Exceptions handling notes in JAVA
PPTX
For Loops and Nesting in Python
The Kotlin Programming Language
Java modules
LISP:Control Structures In Lisp
Presentation on Android application life cycle and saved instancestate
Hydra: A Vocabulary for Hypermedia-Driven Web APIs
sets and maps
Mastering the Sling Rewriter
Kotlin @ Coupang Backend 2017
[Golang] 以 Mobile App 工程師視角,帶你進入 Golang 的世界 (Introduction of GoLang)
Introduction of python
Kotlin scope functions
Php variables (english)
Python built in functions
Coroutines in Kotlin
Java 8 Default Methods
Kotlin presentation
Exceptions handling notes in JAVA
For Loops and Nesting in Python
Ad

Similar to Idiomatic kotlin (20)

PDF
Scala vs Java 8 in a Java 8 World
PDF
Exploring Koltin on Android
PDF
Kotlin Basics - Apalon Kotlin Sprint Part 2
PDF
Swift - 혼자 공부하면 분명히 안할테니까 같이 공부하기
PDF
Kotlin Advanced - Apalon Kotlin Sprint Part 3
PPTX
Concurrent Application Development using Scala
PDF
Derping With Kotlin
PDF
Kotlinify Your Project!
PPT
SDC - Einführung in Scala
PDF
Introduction to Swift programming language.
PDF
Introduction to Scala
PDF
Parametricity - #cljsyd - May, 2015
PDF
A swift introduction to Swift
PDF
Pooya Khaloo Presentation on IWMC 2015
ODP
Ast transformations
PDF
Dip into Coroutines - KTUG Munich 202303
PPT
Swift-Programming Part 1
PPTX
Why Scala is the better Java
PDF
Functional Programming with Groovy
Scala vs Java 8 in a Java 8 World
Exploring Koltin on Android
Kotlin Basics - Apalon Kotlin Sprint Part 2
Swift - 혼자 공부하면 분명히 안할테니까 같이 공부하기
Kotlin Advanced - Apalon Kotlin Sprint Part 3
Concurrent Application Development using Scala
Derping With Kotlin
Kotlinify Your Project!
SDC - Einführung in Scala
Introduction to Swift programming language.
Introduction to Scala
Parametricity - #cljsyd - May, 2015
A swift introduction to Swift
Pooya Khaloo Presentation on IWMC 2015
Ast transformations
Dip into Coroutines - KTUG Munich 202303
Swift-Programming Part 1
Why Scala is the better Java
Functional Programming with Groovy
Ad

More from Anton Arhipov (20)

PDF
JavaZone 2022 - Building Kotlin DSL.pdf
PDF
TechTrain 2019 - (Не)адекватное техническое интервью
PDF
Build pipelines with TeamCity
PDF
Build pipelines with TeamCity
PDF
Devoxx Ukraine 2018 - Kotlin DSL in under an hour
PDF
GeeCON Prague 2018 - Kotlin DSL in under an hour
PDF
Build pipelines with TeamCity and Kotlin DSL
PDF
Build pipelines with TeamCity
PDF
JavaDay Kiev 2017 - Integration testing with TestContainers
PDF
GeeCON Prague 2017 - TestContainers
PDF
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
PDF
JavaOne 2017 - TestContainers: integration testing without the hassle
PDF
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
PDF
JavaZone 2017 - The Hitchhiker’s guide to Java class reloading
PDF
JUG.ua 20170225 - Java bytecode instrumentation
PDF
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
PDF
GeeCON 2017 - TestContainers. Integration testing without the hassle
PDF
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
PDF
JEEConf 2017 - Having fun with Javassist
PDF
Devclub 01/2017 - (Не)адекватное Java-интервью
JavaZone 2022 - Building Kotlin DSL.pdf
TechTrain 2019 - (Не)адекватное техническое интервью
Build pipelines with TeamCity
Build pipelines with TeamCity
Devoxx Ukraine 2018 - Kotlin DSL in under an hour
GeeCON Prague 2018 - Kotlin DSL in under an hour
Build pipelines with TeamCity and Kotlin DSL
Build pipelines with TeamCity
JavaDay Kiev 2017 - Integration testing with TestContainers
GeeCON Prague 2017 - TestContainers
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
JavaOne 2017 - TestContainers: integration testing without the hassle
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
JavaZone 2017 - The Hitchhiker’s guide to Java class reloading
JUG.ua 20170225 - Java bytecode instrumentation
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
GeeCON 2017 - TestContainers. Integration testing without the hassle
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - Having fun with Javassist
Devclub 01/2017 - (Не)адекватное Java-интервью

Recently uploaded (20)

PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
MYSQL Presentation for SQL database connectivity
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Approach and Philosophy of On baking technology
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Electronic commerce courselecture one. Pdf
PDF
Empathic Computing: Creating Shared Understanding
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
cuic standard and advanced reporting.pdf
PPTX
sap open course for s4hana steps from ECC to s4
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Network Security Unit 5.pdf for BCA BBA.
Spectral efficient network and resource selection model in 5G networks
MYSQL Presentation for SQL database connectivity
Understanding_Digital_Forensics_Presentation.pptx
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Approach and Philosophy of On baking technology
Review of recent advances in non-invasive hemoglobin estimation
Diabetes mellitus diagnosis method based random forest with bat algorithm
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
NewMind AI Weekly Chronicles - August'25 Week I
Encapsulation_ Review paper, used for researhc scholars
Electronic commerce courselecture one. Pdf
Empathic Computing: Creating Shared Understanding
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
cuic standard and advanced reporting.pdf
sap open course for s4hana steps from ECC to s4
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx

Idiomatic kotlin

  • 2. Agenda • Expressions • Examples from standard library • DSL
  • 7. Idiomatic - using, containing, or denoting expressions that are natural to a native speaker
  • 8. Idiomatic - using, containing, or denoting expressions that are natural to a native speaker In case of a programming language: •Conforms to a commonly accepted style •E ff ectively uses features of the programming language
  • 11. fun adjustSpeed(weather: Weather): Drive { var result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 12. fun adjustSpeed(weather: Weather): Drive { var result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 13. fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 14. fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 15. fun adjustSpeed(weather: Weather): Drive { val result: Drive result = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 16. fun adjustSpeed(weather: Weather): Drive { val result: Drive result = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 17. fun adjustSpeed(weather: Weather): Drive { val result: Drive = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 18. fun adjustSpeed(weather: Weather): Drive { val result: Drive = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 19. fun adjustSpeed(weather: Weather): Drive { return if (weather is Rainy) { Safe() } else { Calm() } }
  • 20. fun adjustSpeed(weather: Weather): Drive { return if (weather is Rainy) { Safe() } else { Calm() } }
  • 21. fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) { Safe() } else { Calm() }
  • 22. fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) { Safe() } else { Calm() }
  • 23. fun adjustSpeed(weather: Weather) = if (weather is Rainy) { Safe() } else { Calm() }
  • 24. fun adjustSpeed(weather: Weather) = if (weather is Rainy) { Safe() } else { Calm() }
  • 25. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
  • 26. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm() fun adjustSpeed(weather: Weather): Drive { var result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 27. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
  • 28. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
  • 29. abstract class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() else - > Calm() }
  • 30. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() / / else - > Calm() }
  • 31. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() / / else - > Calm() }
  • 32. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() is Sunny - > TODO() }
  • 33. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() is Sunny - > TODO() } Use expressions! Use when as expression body Use sealed classes with when
  • 34. Use try as expression body fun tryParse(number: String) : Int? { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } }
  • 35. Use try as expression body fun tryParse(number: String) = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null }
  • 36. Use try as expression fun tryParse(number: String) : Int? { val n = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null } println(n) return n }
  • 37. Use elvis operator class Person(val name: String?, val age: Int?) val p = retrievePerson() ? : Person()
  • 38. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age = = null) return println("$name: $age") }
  • 39. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age = = null) return println("$name: $age") }
  • 40. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age = = null) return println("$name: $age") }
  • 41. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name ? : throw IllegalArgumentException("Named required") val age = person.age ? : return println("$name: $age") }
  • 43. Consider using null-safe call val order = retrieveOrder() if (order = = null | | order.customer = = null | | order.customer.address = = null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
  • 44. Consider using null-safe call val order = retrieveOrder() val city = order ? . customer ? . address ? . city
  • 45. Consider using null-safe call val order = retrieveOrder() val city = order ? . customer ? . address ? . city ? : throw IllegalArgumentException("Invalid Order")
  • 46. Avoid not-null assertions !! val order = retrieveOrder() val city = order ! ! .customer ! ! .address ! ! .city “You may notice that the double exclamation mark looks a bit rude: it’s almost like you’re yelling at the compiler. This is intentional.” - Kotlin in Action
  • 47. Avoid not-null assertions !! class MyTest { class State(val data: String) private var state: State? = null @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state ! ! .data) } }
  • 48. Avoid not-null assertions !! class MyTest { class State(val data: String) private var state: State? = null @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state ! ! .data) } } class MyTest { class State(val data: String) private lateinit var state: State @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state.data) } } - use lateinit
  • 49. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) }
  • 50. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . customer ? . let { : : processCustomer } or
  • 51. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } No need for an extra variable retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . customer ? . let { : : processCustomer } or
  • 52. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . customer ? . let { : : processCustomer } or
  • 53. Consider using safe cast for type checking override fun equals(other: Any?) : Boolean { val command = other as Command return command.id = = id }
  • 54. Consider using safe cast for type checking override fun equals(other: Any?) : Boolean { val command = other as Command return command.id = = id } override fun equals(other: Any?) : Boolean { return (other as? Command) ? . id = = id }
  • 55. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c > = 'A' & & c < = 'Z'
  • 56. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c > = 'A' & & c < = 'Z'
  • 57. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c in 'A' . . 'Z'
  • 58. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c in 'A' . . 'Z'
  • 59. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } }
  • 60. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } }
  • 61. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") }
  • 62. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") }
  • 63. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") } for ((i, arg) in args.withIndex()) { println("$i: $arg") }
  • 65. Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } }
  • 66. Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } }
  • 67. Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() }
  • 68. Use extension functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } fun String.isPhoneNumber() = length = = 7 & & all { it.isDigit() }
  • 69. Extension or a member? https://kotlinlang.org/docs/coding-conventions.html#extension-functions •Use extension functions liberally. •If a function works primarily on an object, consider making it an extension with that object as a receiver. •Minimize API pollution, restrict the visibility. •As necessary, use local extension functions, member extension functions, or top-level extension functions with private visibility.
  • 70. Use default values instead of overloading class Phonebook { fun print() { print(",") } fun print(columnSeparator: String) {} } fun main(args: Array<String>) { Phonebook().print("|") }
  • 71. Use default values instead of overloading class Phonebook { fun print() { print(",") } fun print(columnSeparator: String) {} } fun main(args: Array<String>) { Phonebook().print("|") } class Phonebook { fun print(separator: String = ",") {} fun someFun(x: Int) {} } fun main(args: Array<String>) { Phonebook().print(separator = "|") }
  • 72. Return multiple values using data classes fun namedNum(): Pair<Int, String> = 1 to "one" / / same but shorter fun namedNum2() = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second }
  • 73. Return multiple values using data classes fun namedNum(): Pair<Int, String> = 1 to "one" / / same but shorter fun namedNum2() = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second } data class GameResult( val rank: Int, val name: String ) fun namedNum() = GameResult(1, "Player 1") fun main(args: Array<String>) { val (rank, name) = namedNum() println("$name, rank $rank") }
  • 74. Return multiple values using data classes data class GameResult( val rank: Int, val name: String ) fun namedNum() = GameResult(1, "Player 1") fun main(args: Array<String>) { val (rank, name) = namedNum() println("$name, rank $rank") } GameResult var1 = namedNum(); int var2 = var1.component1(); String var3 = var1.component2();
  • 75. Destructuring in loops fun printMap(map: Map<String, String>) { for (item in map.entries) { println("${item.key} - > ${item.value}") } }
  • 76. Destructuring in loops fun printMap(map: Map<String, String>) { for (item in map.entries) { println("${item.key} - > ${item.value}") } } fun printMap(map: Map<String, String>) { for ((key, value) in map) { println("$key - > $value") } }
  • 77. Destructuring in lists data class NameExt( val name: String, val ext: String? ) fun splitNameExt(filename: String): NameExt { if ('.' in filename) { val parts = filename.split('.', limit = 2) return NameExt(parts[0], parts[1]) } return NameExt(filename, null) } fun splitNameAndExtension(filename: String): NameExt { if ('.' in filename) { val (name, ext) = filename.split('.', limit = 2) return NameExt(name, ext) } return NameExt(filename, null) }
  • 79. Use type aliases for functional types class Event class EventDispatcher { fun addClickHandler(handler: (Event) - > Unit) {} fun removeClickHandler(handler: (Event) - > Unit) {} }
  • 80. Use type aliases for functional types class Event class EventDispatcher { fun addClickHandler(handler: (Event) - > Unit) {} fun removeClickHandler(handler: (Event) - > Unit) {} } typealias ClickHandler = (Event) - > Unit class EventDispatcher { fun addClickHandler(handler: ClickHandler) { } fun removeClickHandler(handler: ClickHandler) { } }
  • 82. Verify parameters using require() class Person( val name: String?, val age: Int ) fun processPerson(person: Person) { if (person.age < 18) { throw IllegalArgumentException("Adult required") } }
  • 83. Verify parameters using require() class Person( val name: String?, val age: Int ) fun processPerson(person: Person) { if (person.age < 18) { throw IllegalArgumentException("Adult required") } } fun processPerson(person: Person) { require(person.age > = 18) { "Adult required" } }
  • 84. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) = objects.filter { it is String }
  • 85. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) = objects.filter { it is String } fun findAllStrings(objects: List<Any>) = objects.filterIsInstance<String>()
  • 86. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) : List<Any> = objects.filter { it is String } fun findAllStrings(objects: List<Any>) : List<String> = objects.filterIsInstance<String>()
  • 87. Apply operation to non-null elements mapNotNull data class Result( val data: Any?, val error: String? ) fun listErrors(results: List<Result>): List<String> = results.map { it.error }.filterNotNull() fun listErrors(results: List<Result>): List<String> = results.mapNotNull { it.errorMessage }
  • 88. compareBy compares by multiple keys class Person( val name: String, val age: Int ) fun sortPersons(persons: List<Person>) = persons.sortedWith(Comparator<Person> { person1, person2 - > val rc = person1.name.compareTo(person2.name) if (rc ! = 0) rc else person1.age - person2.age })
  • 89. compareBy compares by multiple keys class Person( val name: String, val age: Int ) fun sortPersons(persons: List<Person>) = persons.sortedWith(Comparator<Person> { person1, person2 - > val rc = person1.name.compareTo(person2.name) if (rc ! = 0) rc else person1.age - person2.age }) fun sortPersons(persons: List<Person>) = persons.sortedWith(compareBy(Person : : name, Person : : age))
  • 90. groupBy to group elements class Request( val url: String, val remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request > > () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } }
  • 91. groupBy to group elements class Request( val url: String, val remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request > > () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } } fun analyzeLog(log: List<Request>) { val map = log.groupBy(Request : : url) }
  • 92. Use coerceIn to ensure numbers in range fun updateProgress(value: Int) { val actualValue = when { value < 0 - > 0 value > 100 - > 100 else - > value } } fun updateProgress(value: Int) { val actualValue = value.coerceIn(0, 100) }
  • 93. Initializing objects with apply val dataSource = BasicDataSource( ) dataSource.driverClassName = "com.mysql.jdbc.Driver" dataSource.url = "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4 val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 }
  • 94. Initializing objects with apply final ClientBuilder builder = new ClientBuilder(); builder.setFirstName("Anton"); builder.setLastName("Arhipov"); final TwitterBuilder twitterBuilder = new TwitterBuilder(); twitterBuilder.setHandle("@antonarhipov"); builder.setTwitter(twitterBuilder.build()); final CompanyBuilder companyBuilder = new CompanyBuilder(); companyBuilder.setName("JetBrains"); companyBuilder.setCity("Tallinn"); builder.setCompany(companyBuilder.build()); final Client client = builder.build(); System.out.println("Created client is: " + client);
  • 95. Initializing objects with apply val builder = ClientBuilder() builder.firstName = "Anton" builder.lastName = "Arhipov" val twitterBuilder = TwitterBuilder() twitterBuilder.handle = "@antonarhipov" builder.twitter = twitterBuilder.build() val companyBuilder = CompanyBuilder() companyBuilder.name = "JetBrains" companyBuilder.city = "Tallinn" builder.company = companyBuilder.build() val client = builder.build() println("Created client is: $client")
  • 96. Initializing objects with apply val builder = ClientBuilder() builder.firstName = "Anton" builder.lastName = "Arhipov" val twitterBuilder = TwitterBuilder() twitterBuilder.handle = "@antonarhipov" builder.twitter = twitterBuilder.build() val companyBuilder = CompanyBuilder() companyBuilder.name = "JetBrains" companyBuilder.city = "Tallinn" builder.company = companyBuilder.build() val client = builder.build() println("Created client is: $client") val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  • 98. “Domain Speci fi c”, i.e. tailored for a speci fi c task
  • 99. “Domain Speci fi c”, i.e. tailored for a speci fi c task Examples: •Compose strings - stringBuilder •Create HTML documents - kotlinx.html •Con fi gure routing logic for a web app - ktor •Generally, build any object graphs. See “type-safe builders”
  • 100. buildString //Java String name = "Joe"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.append("Hello, "); sb.append(name); sb.append("!n"); } System.out.println(sb); //Kotlin val name = "Joe" val s = buildString { repeat(5) { append("Hello, ") append(name) appendLine("!") } } println(s)
  • 101. kotlinx.html System.out.appendHTML().html { body { div { a("http: / / kotlinlang.org") { target = ATarget.blank +"Main site" } } } }
  • 102. Ktor fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 . . 10) { li { +"$n" } } } } } } } }.start(wait = true) }
  • 103. Ktor fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 . . 10) { li { +"$n" } } } } } } } }.start(wait = true) } Ktor’s routing
  • 104. Ktor fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 . . 10) { li { +"$n" } } } } } } } }.start(wait = true) } kotlinx.html Ktor’s routing
  • 106. Build your vocabulary to abstract from scope functions val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  • 107. Build your vocabulary to abstract from scope functions fun client(c: ClientBuilder.() - > Unit): Client { val builder = ClientBuilder() c(builder) return builder.build() } fun ClientBuilder.company(block: CompanyBuilder.() - > Unit) { company = CompanyBuilder().apply(block).build() } fun ClientBuilder.twitter(block: TwitterBuilder.() - > Unit) { twitter = TwitterBuilder().apply(block).build() } val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  • 108. val client = client { firstName = "Anton" lastName = "Arhipov" twitter { handle = "@antonarhipov" } company { name = "JetBrains" city = "Tallinn" } } println("Created client is: $client") Build your vocabulary to abstract from scope functions val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")