$30 off During Our Annual Pro Sale. View Details »

Idiomatic Kotlin, 2nd edition

Idiomatic Kotlin, 2nd edition

Anton Arhipov

October 06, 2021
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. @antonarhipov
    Idiomatic Kotlin
    2nd edition

    View Slide

  2. Anton Arhipov


    @antonarhipov
    Developer Advocate @ JetBrains

    View Slide

  3. View Slide

  4. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. Functions

    View Slide

  9. fun main() {


    doSomething()


    }


    fun doSomething() {


    doMoreStuff(
    :
    :
    finishWork)


    }


    fun doMoreStuff(callback: ()
    ->
    Unit) {


    callback()


    }


    fun finishWork() {


    TODO("Not implemented yet")


    }

    View Slide

  10. fun main() {


    doSomething()


    }


    fun doSomething() {


    doMoreStuff(
    :
    :
    finishWork)


    }


    fun doMoreStuff(callback: ()
    ->
    Unit) {


    callback()


    }


    fun finishWork() {


    TODO("Not implemented yet")


    }

    View Slide

  11. fun main() {


    doSomething()


    }


    fun doSomething() {


    doMoreStuff(
    :
    :
    finishWork)


    }


    fun doMoreStuff(callback: ()
    ->
    Unit) {


    callback()


    }


    fun finishWork() {


    TODO("Not implemented yet")


    }

    View Slide

  12. fun main() {


    doSomething()


    }


    fun doSomething() {


    doMoreStuff(
    :
    :
    finishWork)


    }


    fun doMoreStuff(callback: ()
    ->
    Unit) {


    callback()


    }


    fun finishWork() {


    TODO("Not implemented yet")


    }
    Just functions,


    no classes!

    View Slide

  13. fun main() {


    doSomething()


    }


    fun doSomething() {


    doMoreStuff(
    :
    :
    finishWork)


    }


    fun doMoreStuff(callback: ()
    ->
    Unit) {


    callback()


    }


    fun finishWork() {


    TODO("Not implemented yet")


    }
    Grouping by
    fi
    les …

    View Slide

  14. fun main() {


    doSomething()


    }


    fun doSomething() {


    doMoreStuff(
    :
    :
    finishWork)


    }


    fun doMoreStuff(callback: ()
    ->
    Unit) {


    callback()


    }


    fun finishWork() {


    TODO("Not implemented yet")


    }
    Grouping by
    fi
    les …

    View Slide

  15. Classical project
    structure

    View Slide

  16. The project structure
    simpli
    fi
    es

    View Slide

  17. We can simplify it even
    more!

    View Slide

  18. Extensions

    View Slide

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


    companion object {


    fun isPhoneNumber(s: String) =


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


    }


    }


    View Slide

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


    }


    View Slide

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


    View Slide

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

    View Slide

  23. 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.

    View Slide

  24. +

    View Slide

  25. fun findMessageById(id: String) = db.query(


    "select * from messages where id = ?",


    RowMapper { rs, _
    ->

    Message(rs.getString("id"), rs.getString("text"))


    },


    id


    )

    View Slide

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


    }


    View Slide

  27. fun findMessageById(id: String) = db.query(


    "select * from messages where id = ?",


    RowMapper { rs, _
    ->

    Message(rs.getString("id"), rs.getString("text"))


    },


    id


    )

    View Slide

  28. fun findMessageById(id: String) = db.query(


    "select * from messages where id = ?",


    id,


    RowMapper { rs, _
    ->

    Message(rs.getString("id"), rs.getString("text"))


    }


    )

    View Slide

  29. fun findMessageById(id: String) = db.query(


    "select * from messages where id = ?",


    id,


    { rs, _
    ->

    Message(rs.getString("id"), rs.getString("text"))


    }


    )

    View Slide

  30. fun findMessageById(id: String) = db.query(


    "select * from messages where id = ?",


    id)


    { rs, _
    ->

    Message(rs.getString("id"), rs.getString("text"))


    }


    View Slide

  31. fun findMessageById(id: String) = db.query(


    "select * from messages where id = ?", id) { rs, _
    ->

    Message(rs.getString("id"), rs.getString("text"))


    }


    View Slide

  32. 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)

    View Slide

  33. Scope functions
    apply, let, run, also, with

    View Slide

  34. View Slide

  35. 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

    View Slide

  36. 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
    }

    View Slide

  37. 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


    }


    View Slide

  38. 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

    View Slide

  39. ?.let
    val order = retrieveOrder()


    if (order
    !=
    null){


    processCustomer(order.customer)


    }

    View Slide

  40. val order = retrieveOrder()


    if (order
    !=
    null){


    processCustomer(order.customer)


    }
    retrieveOrder()
    ?.
    let {


    processCustomer(it.customer)


    }
    retrieveOrder()
    ?.
    customer
    ?.
    let {
    ::
    processCustomer }
    or
    ?.let

    View Slide

  41. 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

    View Slide

  42. View Slide

  43. View Slide

  44. Default argument values and
    named parameters

    View Slide

  45. fun find(name: String){


    find(name, true)


    }


    fun find(name: String, recursive: Boolean){


    }
    Function


    overloading

    View Slide

  46. 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

    View Slide

  47. 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

    View Slide

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

    View Slide

  49. 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

    View Slide

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

    View Slide

  51. Default argument values diminish the need


    for overloading in most cases.

    View Slide

  52. Default argument values diminish the need


    for overloading in most cases.
    Named parameters is a necessary tool for


    working with default argument values

    View Slide

  53. Expressions
    try, if, when

    View Slide

  54. fun adjustSpeed(weather: Weather): Drive {


    val result: Drive


    if (weather is Rainy) {


    result = Safe()


    } else {


    result = Calm()


    }




    return result


    }


    View Slide

  55. fun adjustSpeed(weather: Weather): Drive {


    val result: Drive


    if (weather is Rainy) {


    result = Safe()


    } else {


    result = Calm()


    }




    return result


    }


    View Slide

  56. fun adjustSpeed(weather: Weather): Drive {




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


    Safe()


    } else {


    Calm()


    }


    return result


    }


    View Slide

  57. fun adjustSpeed(weather: Weather): Drive {




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


    Safe()


    } else {


    Calm()


    }


    return result


    }


    View Slide

  58. fun adjustSpeed(weather: Weather): Drive {




    return if (weather is Rainy) {


    Safe()


    } else {


    Calm()


    }


    }


    View Slide

  59. fun adjustSpeed(weather: Weather): Drive {




    return if (weather is Rainy) {


    Safe()


    } else {


    Calm()


    }


    }


    View Slide

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


    Safe()


    } else {


    Calm()


    }


    View Slide

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


    Safe()


    } else {


    Calm()


    }


    View Slide

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


    Safe()


    } else {


    Calm()


    }


    View Slide

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


    Safe()


    } else {


    Calm()


    }


    View Slide

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


    Is it concise?
    Sure!

    View Slide

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


    Is it readable?
    It depends!

    View Slide

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


    What does the function
    return?

    View Slide

  67. 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

    View Slide

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


    View Slide

  69. abstract class Weather


    class Sunny : Weather()


    class Rainy : Weather()


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


    is Rainy
    ->
    Safe()


    else
    ->
    Calm()


    }


    View Slide

  70. sealed class Weather


    class Sunny : Weather()


    class Rainy : Weather()


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


    is Rainy
    ->
    Safe()


    / /
    else
    ->
    Calm()


    }


    View Slide

  71. sealed class Weather


    class Sunny : Weather()


    class Rainy : Weather()


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


    is Rainy
    ->
    Safe()


    / /
    else
    ->
    Calm()


    }


    View Slide

  72. sealed class Weather


    class Sunny : Weather()


    class Rainy : Weather()


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


    is Rainy
    ->
    Safe()


    is Sunny
    ->
    TODO()


    }


    View Slide

  73. 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

    View Slide

  74. View Slide

  75. Use try as expression body
    fun tryParse(number: String) : Int? {


    try {


    return Integer.parseInt(number)


    } catch (e: NumberFormatException) {


    return null


    }


    }


    View Slide

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


    Integer.parseInt(number)


    } catch (e: NumberFormatException) {


    null


    }


    View Slide

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


    val n = try {


    Integer.parseInt(number)


    } catch (e: NumberFormatException) {


    null


    }


    println(n)


    return n


    }


    View Slide

  78. Null-safety

    View Slide

  79. class Nullable {


    fun someFunction(){}


    }


    fun createNullable(): Nullable? = null


    fun main() {


    val n: Nullable? = createNullable()




    n.someFunction()


    }


    View Slide

  80. class Nullable {


    fun someFunction(){}


    }


    fun createNullable(): Nullable? = null


    fun main() {


    val n: Nullable? = createNullable()




    n.someFunction()


    }


    View Slide

  81. View Slide

  82. View Slide

  83. 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


    View Slide

  84. Consider using null-safe call
    val order = retrieveOrder()


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


    View Slide

  85. Consider using null-safe call
    val order = retrieveOrder()


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


    ?:
    throw IllegalArgumentException("Invalid Order")


    View Slide

  86. 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

    View Slide

  87. 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)


    }


    }

    View Slide

  88. 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

    View Slide

  89. View Slide

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


    val p = retrievePerson()
    ?:
    Person()


    View Slide

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


    }


    View Slide

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


    }


    View Slide

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


    }


    View Slide

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


    }


    View Slide

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


    val command = other as Command


    return command.id
    ==
    id


    }

    View Slide

  96. 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


    }

    View Slide

  97. Functional types

    View Slide

  98. fun someFunction(function: ()
    ->
    T): T {





    }


    View Slide

  99. fun someFunction(function: ()
    ->
    T): T {





    }


    Functional type

    View Slide

  100. fun someFunction(function: ()
    ->
    T): T {





    }


    fun someOtherFunction(){


    val s: String = someFunction { "Hello" }


    }


    View Slide

  101. fun someFunction(function: Action): T {





    }


    fun someOtherFunction(){


    val s: String = someFunction { "Hello" }


    }


    typealias Action = ()
    ->
    T

    View Slide

  102. typealias Action = ()
    ->
    T

    View Slide

  103. typealias Action = ()
    ->
    T
    class MyAction : Action {


    override fun invoke(): T {


    TODO("Not yet implemented")


    }


    }


    View Slide

  104. typealias Action = ()
    ->
    T
    class MyAction : Action {


    override fun invoke(): T {


    TODO("Not yet implemented")


    }


    }


    fun someFunction(function: Action): T {





    }


    View Slide

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


    }


    View Slide

  106. It’s not enough for the programming language to provide
    higher-order functions.


    We need other tools for working with these!
    Observation:

    View Slide

  107. It’s not enough for the programming language to provide
    FEATURE.


    We need other tools for working with FEATURE!
    General observation:

    View Slide

  108. Ranges

    View Slide

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


    c
    >
    =
    'A'
    &&
    c
    < =
    'Z'


    View Slide

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


    c
    >
    =
    'A'
    &&
    c
    < =
    'Z'


    View Slide

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


    c in 'A'
    .
    .
    'Z'


    View Slide

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


    c in 'A'
    .
    .
    'Z'


    View Slide

  113. 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

    View Slide

  114. 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)

    View Slide

  115. 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)

    View Slide

  116. Ranges in loops
    fun main(args: Array) {




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


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


    }




    }


    View Slide

  117. Ranges in loops
    fun main(args: Array) {




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


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


    }




    }


    View Slide

  118. 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]}")


    }

    View Slide

  119. 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]}")


    }

    View Slide

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


    }

    View Slide

  121. View Slide

  122. Destructuring

    View Slide

  123. 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


    }

    View Slide

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


    }

    View Slide

  125. 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();

    View Slide

  126. Destructuring in loops
    fun printMap(map: Map) {


    for (item in map.entries) {


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


    }


    }

    View Slide

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


    }


    }

    View Slide

  128. 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)


    }

    View Slide

  129. View Slide

  130. Standard library

    View Slide

  131. View Slide

  132. View Slide

  133. Idiomatic Kotlin

    View Slide

  134. Standard library

    View Slide

  135. Kotlin for backend
    development

    View Slide

  136. 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

    View Slide

  137. 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

    View Slide

  138. 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}


    View Slide

  139. 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


    View Slide

  140. 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

    View Slide

  141. val input = """


    abcd


    bcde


    cdef


    defg


    xya


    xyb


    xyc


    """.trimIndent()

    View Slide

  142. val input = """


    abcd


    bcde


    cdef


    defg


    xya


    xyb


    xyc


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

    View Slide

  143. val input = """


    abcd


    bcde


    cdef


    defg


    xya


    xyb


    xyc


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

    View Slide

  144. 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()


    }


    View Slide

  145. 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()


    }


    View Slide

  146. 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()


    }


    View Slide

  147. 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()


    }


    View Slide

  148. 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

    View Slide

  149. 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

    View Slide

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

    View Slide

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

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

    View Slide

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

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

    View Slide

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

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

    View Slide

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

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

    View Slide

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

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

    View Slide

  156. 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

    View Slide

  157. 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

    View Slide

  158. 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

    View Slide

  159. 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

    View Slide

  160. 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

    View Slide

  161. 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()


    }


    View Slide

  162. Select objects by type with filterIsInstance
    fun findAllStrings(objects: List) =


    objects.filter { it is String }


    View Slide

  163. Select objects by type with filterIsInstance
    fun findAllStrings(objects: List) =


    objects.filter { it is String }


    fun findAllStrings(objects: List) =


    objects.filterIsInstance()


    View Slide

  164. Select objects by type with filterIsInstance
    fun findAllStrings(objects: List) : List =


    objects.filter { it is String }


    fun findAllStrings(objects: List) : List =


    objects.filterIsInstance()


    View Slide

  165. 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


    })


    View Slide

  166. 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))

    View Slide

  167. 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)


    }


    }


    View Slide

  168. 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)


    }

    View Slide

  169. 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)


    }

    View Slide

  170. 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
    }

    View Slide

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

    View Slide

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


    View Slide

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


    View Slide

  174. Domain Specific Languages

    View Slide

  175. 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)

    View Slide

  176. kotlinx.html
    System.out.appendHTML().html {


    body {


    div {


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


    target = ATarget.blank


    +"Main site"


    }


    }


    }


    }


    View Slide

  177. 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)


    }


    View Slide

  178. 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

    View Slide

  179. 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

    View Slide

  180. Lambda with receiver
    T.() -> Unit

    View Slide

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


    View Slide

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


    View Slide

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


    View Slide

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

    View Slide