Slide 1

Slide 1 text

@antonarhipov Idiomatic Kotlin DevFest Vladivostok 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

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Functions

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Extensions

Slide 16

Slide 16 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 17

Slide 17 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 18

Slide 18 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 19

Slide 19 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 20

Slide 20 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 21

Slide 21 text

+

Slide 22

Slide 22 text

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

Slide 23

Slide 23 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 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

fun findMessageById(id: String) = db.query( "select * from messages where id = ?", id) { 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")) } fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List = query(sql, RowMapper { rs, i -> function(rs, i) }, *args)

Slide 30

Slide 30 text

Scope functions apply, let, run, also, with

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 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 33

Slide 33 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 34

Slide 34 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 35

Slide 35 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 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 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 39

Slide 39 text

No content

Slide 40

Slide 40 text

fun makeDir(path: String) = path.let { File(it) }.also { it.mkdirs() }

Slide 41

Slide 41 text

fun makeDir(path: String) = path.let { File(it) }.also { it.mkdirs() } Don’t overuse the scope functions!

Slide 42

Slide 42 text

fun makeDir(path: String) = path.let { File(it) }.also { it.mkdirs() } fun makeDir(path: String) : File { val file = File(path) file.mkdirs() return file } Don’t overuse the scope functions! This is simpler!

Slide 43

Slide 43 text

fun makeDir(path: String) = path.let { File(it) }.also { it.mkdirs() } fun makeDir(path: String) : File { val file = File(path) file.mkdirs() return file } Don’t overuse the scope functions! This is simpler! fun makeDir(path: String) = File(path).also { it.mkdirs() } OK, this one is actually fi ne :)

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

Ranges

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 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 103

Slide 103 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 104

Slide 104 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 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 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 108

Slide 108 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 109

Slide 109 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 110

Slide 110 text

No content

Slide 111

Slide 111 text

Destructuring

Slide 112

Slide 112 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 113

Slide 113 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 114

Slide 114 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 115

Slide 115 text

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

Slide 116

Slide 116 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 117

Slide 117 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 118

Slide 118 text

No content

Slide 119

Slide 119 text

Standard library

Slide 120

Slide 120 text

No content

Slide 121

Slide 121 text

No content

Slide 122

Slide 122 text

Idiomatic Kotlin

Slide 123

Slide 123 text

Standard library

Slide 124

Slide 124 text

Kotlin for backend development

Slide 125

Slide 125 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 126

Slide 126 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 127

Slide 127 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 128

Slide 128 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 129

Slide 129 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 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 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 134

Slide 134 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 135

Slide 135 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 136

Slide 136 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 137

Slide 137 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 138

Slide 138 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 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 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 146

Slide 146 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 147

Slide 147 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 148

Slide 148 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 149

Slide 149 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 150

Slide 150 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 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 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 154

Slide 154 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 155

Slide 155 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 156

Slide 156 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 157

Slide 157 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 158

Slide 158 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 159

Slide 159 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 160

Slide 160 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 161

Slide 161 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 162

Slide 162 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 163

Slide 163 text

Domain Specific Languages

Slide 164

Slide 164 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 165

Slide 165 text

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

Slide 166

Slide 166 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 167

Slide 167 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 168

Slide 168 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 169

Slide 169 text

Lambda with receiver T.() -> Unit

Slide 170

Slide 170 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 171

Slide 171 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 172

Slide 172 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 173

Slide 173 text

https://speakerdeck.com/antonarhipov @antonarhipov