Slide 1

Slide 1 text

Kotlin Standard Library Gems @antonarhipov

Slide 2

Slide 2 text

Concise & modern Coroutines Multiplatform Nice stdlib Null-safety

Slide 3

Slide 3 text

Collections Scope functions Builders eXtras

Slide 4

Slide 4 text

Collections

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

listOf("#1", "#2", "#3") [#1, #2, #3]

Slide 7

Slide 7 text

listOf("#1", "#2", "#3") List(3) { "#${it + 1}" } [#1, #2, #3] [#1, #2, #3]

Slide 8

Slide 8 text

listOf("#1", "#2", "#3") List(3) { "#${it + 1}" } val ml = MutableList(5) { "?" } ml[0] = "a" ml[2] = "c" [#1, #2, #3] [#1, #2, #3] [a, ?, c, ?, ?]

Slide 9

Slide 9 text

listOf("#1", "#2", "#3") List(3) { "#${it + 1}" } val ml = MutableList(5) { "?" } ml[0] = "a" ml[2] = "c" [#1, #2, #3] [#1, #2, #3] [a, ?, c, ?, ?] ml.joinToString("") a?c ? ?

Slide 10

Slide 10 text

listOf("#1", "#2", "#3") List(3) { "#${it + 1}" } val ml = MutableList(5) { "?" } ml[0] = "a" ml[2] = "c" ml.joinToString( separator = "-", prefix = " << ", postfix = " >> ", limit = 3, truncated = "etc." ){ "#$it" } [#1, #2, #3] [#1, #2, #3] [a, ?, c, ?, ?] < < #a- #? -#c-etc. >> ml.joinToString("") a?c ? ?

Slide 11

Slide 11 text

val students = listOf("Ann", "Joe", "Max", "Anton", "John", "Bo", "Ken").shuffled() println(students.chunked(2)) println(students.windowed(3))

Slide 12

Slide 12 text

val students = listOf("Ann", "Joe", "Max", "Anton", "John", "Bo", "Ken").shuffled() println(students.chunked(2)) println(students.windowed(3)) [[John, Ann], [Joe, Bo], [Max, Anton], [Ken]] [[John, Ann, Joe], [Ann, Joe, Bo], [Joe, Bo, Max], [Bo, Max, Anton], [Max, Anton, Ken]]

Slide 13

Slide 13 text

val students = listOf("Ann", "Joe", "Max", "Anton", "John", "Bo", "Ken").shuffled() println(students.chunked(2)) println(students.windowed(3)) [[John, Ann], [Joe, Bo], [Max, Anton], [Ken]] [[John, Ann, Joe], [Ann, Joe, Bo], [Joe, Bo, Max], [Bo, Max, Anton], [Max, Anton, Ken]] listOf(11, 10, 12, 9, 10, 15).windowed(3) { it.average() } [11.0, 12.0, 12.0, 13.0]

Slide 14

Slide 14 text

val cities = listOf("Amsterdam", "Paris", "New York") val countries = listOf("NL", "FR", "US") val citiesToCountries = cities zip countries [(Amsterdam, NL), (Paris, FR), (New York, US)]

Slide 15

Slide 15 text

val cities = listOf("Amsterdam", "Paris", "New York") val countries = listOf("NL", "FR", "US") val citiesToCountries = cities zip countries val (first, second) = citiesToCountries.unzip() [(Amsterdam, NL), (Paris, FR), (New York, US)]

Slide 16

Slide 16 text

val cities = listOf("Amsterdam", "Paris", "New York") val countries = listOf("NL", "FR", "US") val citiesToCountries = cities zip countries val (first, second) = citiesToCountries.unzip() [(Amsterdam, NL), (Paris, FR), (New York, US)] listOf(1, 2, 3, 4, 5).zipWithNext { a, b -> a + b } [3, 5, 7, 9] listOf(1, 2, 3, 4, 5).zipWithNext() [(1, 2), (2, 3), (3, 4), (4, 4)]

Slide 17

Slide 17 text

youtube.com/kotlin

Slide 18

Slide 18 text

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

Slide 19

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

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

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 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 28

Slide 28 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 29

Slide 29 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 30

Slide 30 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 31

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

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

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

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

Slide 40 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 41

Slide 41 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 42

Slide 42 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 43

Slide 43 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 44

Slide 44 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 45

Slide 45 text

fold & reduce

Slide 46

Slide 46 text

listOf(1, 2, 3, 4, 5).fold(10) { a, b - > a + b } listOf(1, 2, 3, 4, 5).reduce { a, b - > a + b } listOf("#1", "#2", "#3").fold("#0") { a, b - > a + b } listOf("#1", "#2", "#3").reduce { a, b -> a + b } / / 25 / / 15 / / #0#1#2#3 / / #1#2#3

Slide 47

Slide 47 text

listOf(1, 2, 3, 4, 5).fold(10) { a, b - > a + b } / / 25 How does this work? How do I trace the logic of this operation? What are the results intermediate steps?

Slide 48

Slide 48 text

listOf(1, 2, 3, 4, 5).fold(10) { a, b - > a + b } / / 25 runningFold runningReduce

Slide 49

Slide 49 text

listOf(1, 2, 3, 4, 5).runningFold(10) { a, b -> a + b } / / [10, 11, 13, 16, 20, 25] listOf(1, 2, 3, 4, 5).runningReduce { a, b - > a + b } / / [1, 3, 6, 10, 15] listOf("#1", "#2", "#3").runningReduce { a, b -> a + b } / / [#1, #1#2, #1#2#3]

Slide 50

Slide 50 text

Scope functions

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

val result: = x.foo { } Return type Original object (x) Result of lambda Access to X it this

Slide 53

Slide 53 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 54

Slide 54 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 55

Slide 55 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 56

Slide 56 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 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

let() as a helper for a complex condition if (some.complex.expression.let { it is Type && it.has.some.property }) { . .. }

Slide 60

Slide 60 text

let() as a helper for a complex condition if (some.complex.expression.let { it is Type && it.has.some.property }) { . .. }

Slide 61

Slide 61 text

let() as a helper for a complex condition if (some.complex.expression.let { it is Type && it.has.some.property }) { . .. }

Slide 62

Slide 62 text

if (retrieveOrder().let { it is Subscription && it.customer.name == "Anton"}) { . .. } let() as a helper for a complex condition if (some.complex.expression.let { it is Type && it.has.some.property }) { . .. }

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 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 } This is simpler! Don’t overuse the scope functions!

Slide 66

Slide 66 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 } fun makeDir(path: String) = File(path).also { it.mkdirs() } OK, this one is actually fi ne :) This is simpler! Don’t overuse the scope functions!

Slide 67

Slide 67 text

Builders

Slide 68

Slide 68 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 69

Slide 69 text

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

Slide 70

Slide 70 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 71

Slide 71 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 72

Slide 72 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 73

Slide 73 text

Lambda with receiver T.() -> Unit The essential language feature for the type-safe builders, a.k.a. DSL

Slide 74

Slide 74 text

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 75

Slide 75 text

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 76

Slide 76 text

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 77

Slide 77 text

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 78

Slide 78 text

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 79

Slide 79 text

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

Slide 80

Slide 80 text

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 person = person { firstName = "Anton" lastName = "Arhipov" twitter { handle = "@antonarhipov" } company { name = "JetBrains" city = "Tallinn" } } println("Hello, $person!")

Slide 81

Slide 81 text

eXtras

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 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 85

Slide 85 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 86

Slide 86 text

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)) compareBy compares by multiple keys

Slide 87

Slide 87 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 88

Slide 88 text

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) } groupBy to group elements

Slide 89

Slide 89 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 90

Slide 90 text

https://speakerdeck.com/antonarhipov @antonarhipov https://github.com/antonarhipov