Slide 1

Slide 1 text

@antonarhipov Idiomatic Kotlin 2nd edition

Slide 2

Slide 2 text

Anton Arhipov @antonarhipov Developer Advocate @ JetBrains

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Idiomatic - using, containing, or denoting expressions that are natural to a native speaker

Slide 6

Slide 6 text

Idiomatic - using, containing, or denoting expressions that are natural to a native speaker Commonly accepted style

Slide 7

Slide 7 text

Idiomatic - using, containing, or denoting expressions that are natural to a native speaker Commonly accepted style Effective use of features

Slide 8

Slide 8 text

Functions

Slide 9

Slide 9 text

fun main() { doSomething() } fun doSomething() { doMoreStuff( : : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") }

Slide 10

Slide 10 text

fun main() { doSomething() } fun doSomething() { doMoreStuff( : : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") }

Slide 11

Slide 11 text

fun main() { doSomething() } fun doSomething() { doMoreStuff( : : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") }

Slide 12

Slide 12 text

fun main() { doSomething() } fun doSomething() { doMoreStuff( : : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") } Just functions, no classes!

Slide 13

Slide 13 text

fun main() { doSomething() } fun doSomething() { doMoreStuff( : : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") } Grouping by fi les …

Slide 14

Slide 14 text

fun main() { doSomething() } fun doSomething() { doMoreStuff( : : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") } Grouping by fi les …

Slide 15

Slide 15 text

Classical project structure

Slide 16

Slide 16 text

The project structure simpli fi es

Slide 17

Slide 17 text

We can simplify it even more!

Slide 18

Slide 18 text

Extensions

Slide 19

Slide 19 text

Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } }

Slide 20

Slide 20 text

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() } }

Slide 21

Slide 21 text

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() }

Slide 22

Slide 22 text

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() }

Slide 23

Slide 23 text

Extension or a member? https://kotlinlang.org/docs/coding-conventions.html#extension-functions Use extension functions liberally. Minimize API pollution, restrict the visibility. As necessary, use local extension functions, member extension functions, or top-level extension functions with private visibility.

Slide 24

Slide 24 text

+

Slide 25

Slide 25 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id )

Slide 26

Slide 26 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id ) @Override public List query(String sql, RowMapper rowMapper, @Nullable Object . .. args) throws DataAccessException { return result(query(sql, args, new RowMapperResultSetExtractor < > (rowMapper))); }

Slide 27

Slide 27 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id )

Slide 28

Slide 28 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", id, RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) } )

Slide 29

Slide 29 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) } )

Slide 30

Slide 30 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", id) { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }

Slide 31

Slide 31 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", id) { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }

Slide 32

Slide 32 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", id) { rs, _ -> Message(rs.getString("id"), rs.getString("text")) } fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List = query(sql, RowMapper { rs, i -> function(rs, i) }, *args)

Slide 33

Slide 33 text

Scope functions apply, let, run, also, with

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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 }

Slide 37

Slide 37 text

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 } public inline fun T.apply(block: T.() -> Unit): T { block() return this }

Slide 38

Slide 38 text

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 } public inline fun T.apply(block: T.() -> Unit): T { block() return this } Lambda with receiver

Slide 39

Slide 39 text

?.let val order = retrieveOrder() if (order != null){ processCustomer(order.customer) }

Slide 40

Slide 40 text

val order = retrieveOrder() if (order != null){ processCustomer(order.customer) } retrieveOrder() ?. let { processCustomer(it.customer) } retrieveOrder() ?. customer ?. let { :: processCustomer } or ?.let

Slide 41

Slide 41 text

val order = retrieveOrder() if (order != null){ processCustomer(order.customer) } retrieveOrder() ?. let { processCustomer(it.customer) } No extra variable retrieveOrder() ?. let { processCustomer(it.customer) } retrieveOrder() ?. customer ?. let { :: processCustomer } or ?.let

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Default argument values and named parameters

Slide 45

Slide 45 text

fun find(name: String){ find(name, true) } fun find(name: String, recursive: Boolean){ } Function overloading

Slide 46

Slide 46 text

fun find(name: String){ find(name, true) } fun find(name: String, recursive: Boolean){ } fun find(name: String, recursive: Boolean = true){ } Default argument value Function overloading

Slide 47

Slide 47 text

fun find(name: String){ find(name, true) } fun find(name: String, recursive: Boolean){ } fun find(name: String, recursive: Boolean = true){ } fun main() { find("myfile.txt") } Default argument value Function overloading

Slide 48

Slide 48 text

class Figure( val width: Int = 1, val height: Int = 1, val depth: Int = 1, color: Color = Color.BLACK, description: String = "This is a 3d figure", ) Figure(Color.RED, "Red figure")

Slide 49

Slide 49 text

class Figure( val width: Int = 1, val height: Int = 1, val depth: Int = 1, color: Color = Color.BLACK, description: String = "This is a 3d figure", ) Figure(Color.RED, "Red figure") Compilation error

Slide 50

Slide 50 text

class Figure( val width: Int = 1, val height: Int = 1, val depth: Int = 1, color: Color = Color.BLACK, description: String = "This is a 3d figure", ) Figure(color = Color.RED, description = "Red figure")

Slide 51

Slide 51 text

Default argument values diminish the need for overloading in most cases.

Slide 52

Slide 52 text

Default argument values diminish the need for overloading in most cases. Named parameters is a necessary tool for working with default argument values

Slide 53

Slide 53 text

Expressions try, if, when

Slide 54

Slide 54 text

fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }

Slide 55

Slide 55 text

fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }

Slide 56

Slide 56 text

fun adjustSpeed(weather: Weather): Drive { val result: Drive = if (weather is Rainy) { Safe() } else { Calm() } return result }

Slide 57

Slide 57 text

fun adjustSpeed(weather: Weather): Drive { val result: Drive = if (weather is Rainy) { Safe() } else { Calm() } return result }

Slide 58

Slide 58 text

fun adjustSpeed(weather: Weather): Drive { return if (weather is Rainy) { Safe() } else { Calm() } }

Slide 59

Slide 59 text

fun adjustSpeed(weather: Weather): Drive { return if (weather is Rainy) { Safe() } else { Calm() } }

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm() Is it concise? Sure!

Slide 65

Slide 65 text

fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm() Is it readable? It depends!

Slide 66

Slide 66 text

fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm() What does the function return?

Slide 67

Slide 67 text

fun adjustSpeed(weather: Weather): Drive = ... fun adjustSpeed(weather: Weather) = ... For public API, keep the return type in the signature For private API it is generally OK to use type inference

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

abstract class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() else -> Calm() }

Slide 70

Slide 70 text

sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() / / else -> Calm() }

Slide 71

Slide 71 text

sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() / / else -> Calm() }

Slide 72

Slide 72 text

sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() is Sunny -> TODO() }

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

Use try as expression body fun tryParse(number: String) : Int? { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } }

Slide 76

Slide 76 text

Use try as expression body fun tryParse(number: String) = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null }

Slide 77

Slide 77 text

Use try as expression fun tryParse(number: String) : Int? { val n = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null } println(n) return n }

Slide 78

Slide 78 text

Null-safety

Slide 79

Slide 79 text

class Nullable { fun someFunction(){} } fun createNullable(): Nullable? = null fun main() { val n: Nullable? = createNullable() n.someFunction() }

Slide 80

Slide 80 text

class Nullable { fun someFunction(){} } fun createNullable(): Nullable? = null fun main() { val n: Nullable? = createNullable() n.someFunction() }

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Consider using null-safe call val order = retrieveOrder() val city = order ?. customer ? . address ?. city ?: throw IllegalArgumentException("Invalid Order")

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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) } }

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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") }

Slide 92

Slide 92 text

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") }

Slide 93

Slide 93 text

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") }

Slide 94

Slide 94 text

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") }

Slide 95

Slide 95 text

Consider using safe cast for type checking override fun equals(other: Any?) : Boolean { val command = other as Command return command.id == id }

Slide 96

Slide 96 text

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 }

Slide 97

Slide 97 text

Functional types

Slide 98

Slide 98 text

fun someFunction(function: () -> T): T { … }

Slide 99

Slide 99 text

fun someFunction(function: () -> T): T { … } Functional type

Slide 100

Slide 100 text

fun someFunction(function: () -> T): T { … } fun someOtherFunction(){ val s: String = someFunction { "Hello" } }

Slide 101

Slide 101 text

fun someFunction(function: Action): T { … } fun someOtherFunction(){ val s: String = someFunction { "Hello" } } typealias Action = () -> T

Slide 102

Slide 102 text

typealias Action = () -> T

Slide 103

Slide 103 text

typealias Action = () -> T class MyAction : Action { override fun invoke(): T { TODO("Not yet implemented") } }

Slide 104

Slide 104 text

typealias Action = () -> T class MyAction : Action { override fun invoke(): T { TODO("Not yet implemented") } } fun someFunction(function: Action): T { … }

Slide 105

Slide 105 text

typealias Action = () -> T class MyAction(val param: String) : Action { override fun invoke(): T { TODO("Not yet implemented") } } fun someFunction(function: Action): T { … } fun someOtherFunction(){ val s: String = someFunction(MyAction("Greetings")) }

Slide 106

Slide 106 text

It’s not enough for the programming language to provide higher-order functions. We need other tools for working with these! Observation:

Slide 107

Slide 107 text

It’s not enough for the programming language to provide FEATURE. We need other tools for working with FEATURE! General observation:

Slide 108

Slide 108 text

Ranges

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

class Version(val major: Int, val minor: Int): Comparable { override fun compareTo(other: Version): Int { if (this.major != other.major) { return this.major - other.major } return this.minor - other.minor } } fun main() { val versionRange = Version(1, 11) . . Version(1, 30) println(Version(0, 9) in versionRange) println(Version(1, 20) in versionRange) } Comparable range

Slide 114

Slide 114 text

class Version(val major: Int, val minor: Int): Comparable { override fun compareTo(other: Version): Int { if (this.major != other.major) { return this.major - other.major } return this.minor - other.minor } } fun main() { val versionRange = Version(1, 11) . . Version(1, 30) println(Version(0, 9) in versionRange) println(Version(1, 20) in versionRange) } Comparable range public operator fun > T.rangeTo(that: T): ClosedRange = ComparableRange(this, that)

Slide 115

Slide 115 text

class Version(val major: Int, val minor: Int): Comparable { override fun compareTo(other: Version): Int { if (this.major != other.major) { return this.major - other.major } return this.minor - other.minor } } fun main() { val versionRange = Version(1, 11) . . Version(1, 30) println(Version(0, 9) in versionRange) println(Version(1, 20) in versionRange) } Comparable range public operator fun > T.rangeTo(that: T): ClosedRange = ComparableRange(this, that)

Slide 116

Slide 116 text

Ranges in loops fun main(args: Array) { for (i in 0 .. args.size - 1) { println("$i: ${args[i]}") } }

Slide 117

Slide 117 text

Ranges in loops fun main(args: Array) { for (i in 0 .. args.size - 1) { println("$i: ${args[i]}") } }

Slide 118

Slide 118 text

Ranges in loops fun main(args: Array) { for (i in 0 .. args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") }

Slide 119

Slide 119 text

Ranges in loops fun main(args: Array) { 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]}") }

Slide 120

Slide 120 text

Ranges in loops fun main(args: Array) { 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") }

Slide 121

Slide 121 text

No content

Slide 122

Slide 122 text

Destructuring

Slide 123

Slide 123 text

Return multiple values using data classes fun namedNum(): Pair = 1 to "one" // same but shorter fun namedNum2() = 1 to "one" fun main(args: Array) { val pair = namedNum() val number = pair.first val name = pair.second }

Slide 124

Slide 124 text

Return multiple values using data classes fun namedNum(): Pair = 1 to "one" // same but shorter fun namedNum2() = 1 to "one" fun main(args: Array) { 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) { val (rank, name) = namedNum() println("$name, rank $rank") }

Slide 125

Slide 125 text

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) { val (rank, name) = namedNum() println("$name, rank $rank") } GameResult var1 = namedNum(); int var2 = var1.component1(); String var3 = var1.component2();

Slide 126

Slide 126 text

Destructuring in loops fun printMap(map: Map) { for (item in map.entries) { println("${item.key} -> ${item.value}") } }

Slide 127

Slide 127 text

Destructuring in loops fun printMap(map: Map) { for (item in map.entries) { println("${item.key} -> ${item.value}") } } fun printMap(map: Map) { for ((key, value) in map) { println("$key -> $value") } }

Slide 128

Slide 128 text

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) }

Slide 129

Slide 129 text

No content

Slide 130

Slide 130 text

Standard library

Slide 131

Slide 131 text

No content

Slide 132

Slide 132 text

No content

Slide 133

Slide 133 text

Idiomatic Kotlin

Slide 134

Slide 134 text

Standard library

Slide 135

Slide 135 text

Kotlin for backend development

Slide 136

Slide 136 text

abcd bcde cdef defg xya xyb xyc Group 1: Group 2: Count the total of characters that are present on each line in every group of strings

Slide 137

Slide 137 text

abcd bcde cdef defg xya xyb xyc Group 1: Group 2: Count the total of characters that are present on each line in every group of strings

Slide 138

Slide 138 text

abcd bcde cdef defg xya xyb xyc Group 1: Group 2: Count the total of characters that are present on each line in every group of strings {d} {xy}

Slide 139

Slide 139 text

abcd bcde cdef defg xya xyb xyc Group 1: Group 2: Count the total of characters that are present on each line in every group of strings {d} {xy} count = 1 count = 2

Slide 140

Slide 140 text

abcd bcde cdef defg xya xyb xyc Group 1: Group 2: Count the total of characters that are present on each line in every group of strings {d} {xy} count = 1 count = 2 Total = 3

Slide 141

Slide 141 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent()

Slide 142

Slide 142 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n")

Slide 143

Slide 143 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n")

Slide 144

Slide 144 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }

Slide 145

Slide 145 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }

Slide 146

Slide 146 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }

Slide 147

Slide 147 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }

Slide 148

Slide 148 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() } Transforming data

Slide 149

Slide 149 text

val input = """ abcd bcde cdef defg xya xyb xyc """.trimIndent() val groups: List = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() } Transforming data Calculating the result

Slide 150

Slide 150 text

val groups: List = input.split("\n\n")

Slide 151

Slide 151 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } Transforming data

Slide 152

Slide 152 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } Transforming data

Slide 153

Slide 153 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } Transforming data

Slide 154

Slide 154 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } Transforming data

Slide 155

Slide 155 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } Transforming data

Slide 156

Slide 156 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result

Slide 157

Slide 157 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result

Slide 158

Slide 158 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result

Slide 159

Slide 159 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result

Slide 160

Slide 160 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result

Slide 161

Slide 161 text

val groups: List = input.split("\n\n") // List>> val step1 = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result groups.map { group -> group.split(nl).map(String :: toSet) }.sumOf { answerSets -> answerSets.reduce { a, b -> a intersect b }.count() }

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

compareBy compares by multiple keys class Person( val name: String, val age: Int ) fun sortPersons(persons: List) = persons.sortedWith(Comparator { person1, person2 -> val rc = person1.name.compareTo(person2.name) if (rc != 0) rc else person1.age - person2.age })

Slide 166

Slide 166 text

compareBy compares by multiple keys class Person( val name: String, val age: Int ) fun sortPersons(persons: List) = persons.sortedWith(Comparator { person1, person2 -> val rc = person1.name.compareTo(person2.name) if (rc != 0) rc else person1.age - person2.age }) fun sortPersons(persons: List) = persons.sortedWith(compareBy(Person :: name, Person : : age))

Slide 167

Slide 167 text

groupBy to group elements class Request( val url: String, val remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List) { val map = mutableMapOf> () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } }

Slide 168

Slide 168 text

groupBy to group elements class Request( val url: String, val remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List) { val map = mutableMapOf> () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } } fun analyzeLog(log: List) { val map = log.groupBy(Request :: url) }

Slide 169

Slide 169 text

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) }

Slide 170

Slide 170 text

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 }

Slide 171

Slide 171 text

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);

Slide 172

Slide 172 text

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")

Slide 173

Slide 173 text

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")

Slide 174

Slide 174 text

Domain Specific Languages

Slide 175

Slide 175 text

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)

Slide 176

Slide 176 text

kotlinx.html System.out.appendHTML().html { body { div { a("http: // kotlinlang.org") { target = ATarget.blank +"Main site" } } } }

Slide 177

Slide 177 text

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) }

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

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

Slide 180

Slide 180 text

Lambda with receiver T.() -> Unit

Slide 181

Slide 181 text

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")

Slide 182

Slide 182 text

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")

Slide 183

Slide 183 text

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")

Slide 184

Slide 184 text

https://speakerdeck.com/antonarhipov @antonarhipov