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

International Winter School on Software Engineering - Kotlin Basics

International Winter School on Software Engineering - Kotlin Basics

Anton Arhipov

February 15, 2022
Tweet

More Decks by Anton Arhipov

Other Decks in Technology

Transcript

  1. Kotlin Anton Arhipov @antonarhipov Developer Advocate, JetBrains Introduction to Kotlin

    International Winter School on Software Engineering @antonarhipov
  2. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language
  3. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2014 - Kotlin 1.0 2013 - Kotlin/JS
  4. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2016 - Kotlin 1.0 2017 - Kotlin/Native preview 2016 - Experimental coroutines (Kotlin 1.1) 2013 - Kotlin/JS
  5. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2016 - Kotlin 1.0 2017 - Kotlin/Native preview 2016 - Experimental coroutines (Kotlin 1.1) 2018 - Multiplatform projects - experimental (Kotlin 1.2) 2019 - Main programming language for Android 2013 - Kotlin/JS 2018 - Coroutines release (Kotlin 1.3)
  6. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2016 - Kotlin 1.0 Current version: 1.6.20 2017 - Kotlin/Native preview 2021 - started with Kotlin/WASM 2016 - Experimental coroutines (Kotlin 1.1) 2018 - Multiplatform projects - experimental (Kotlin 1.2) 2019 - Main programming language for Android 2013 - Kotlin/JS 2018 - Coroutines release (Kotlin 1.3)
  7. Kotlin’s (very simplified) description Modern Functional Multiplatform Statically-typed Asynchronous (with

    coroutines) General purpose Object-oriented
  8. None
  9. Books https://kotlinlang.org/docs/books.html

  10. None
  11. None
  12. Kotlin Basics

  13. // entry point fun main(args: Array<String>) { println("Hello World!") }

    Program entry point
  14. // entry point fun main(args: Array<String>) { println("Hello World!") }

    Program entry point // alternatively fun main() { println("Hello World!") }
  15. Variables fun someFunction() { val a: Int = 1 //

    immediate assignment val b = 2 // `Int` type is inferred val c: Int // Type required when no initializer is provided c = 3 // deferred assignment // .. . }
  16. Variables val PI = 3.14 var x = 0 fun

    incrementX() { x += 1 }
  17. Variables val PI: Double = 3.14 var x: Int =

    0 val s: String = "Hello" Double PI = 3.14; Integer x = 0; String s = "Hello"; Explicit types Explicit types
  18. Variables val PI: Double = 3.14 var x: Int =

    0 val s: String = "Hello" val PI = 3.14 var x = 0 val s = "Hello" Double PI = 3.14; Integer x = 0; String s = "Hello"; Explicit types Type inference final var PI = 3.14; var x = 0; final var s = "Hello"; Type inference (local variables) Explicit types
  19. Functions fun sum(a: Int, b: Int): Int { return a

    + b }
  20. Functions fun sum(a: Int, b: Int): Int { return a

    + b }
  21. Functions fun sum(a: Int, b: Int): Int { return a

    + b } // expression body fun sum(a: Int, b: Int): Int = a + b
  22. Functions fun sum(a: Int, b: Int): Int { return a

    + b } // expression body fun sum(a: Int, b: Int): Int = a + b // type inference fun sum(a: Int, b: Int) = a + b
  23. Functions // return no value fun printSum(a: Int, b: Int):

    Unit { println("sum of $a and $b is ${a + b}") }
  24. Functions // return no value fun printSum(a: Int, b: Int):

    Unit { println("sum of $a and $b is ${a + b}") } // Unit can be omitted fun printSum(a: Int, b: Int) { println("sum of $a and $b is ${a + b}") }
  25. fun main(args: Array<String>) { "Hello World!".say() } Extension functions

  26. fun main(args: Array<String>) { "Hello World!".say() } private fun String.say()

    { println(this) } Extension functions
  27. Functions as parameters fun main(args: Array<String>) { process { s

    -> println("Hello, $s!") } } fun process(operation: (String) -> Unit) { operation("World") }
  28. Lambda expressions val sum: (Int, Int) -> Int = {

    x: Int, y: Int - > x + y }
  29. Lambda expressions val sum: (Int, Int) -> Int = {

    x: Int, y: Int - > x + y } val sum = { x: Int, y: Int -> x + y }
  30. Lambda expressions val sum: (Int, Int) -> Int = {

    x: Int, y: Int - > x + y } val sum = { x: Int, y: Int -> x + y } typealias IntAdder = (Int, Int) -> Int val sum: IntAdder = { x: Int, y: Int -> x + y }
  31. Default values fun find(name: String){ find(name, true) } fun find(name:

    String, recursive: Boolean){ } Function overloading
  32. Default values fun find(name: String){ find(name, true) } fun find(name:

    String, recursive: Boolean = true){ } Default value
  33. Default values fun find(name: String){ find(name, true) } fun find(name:

    String, recursive: Boolean = true){ } fun main(args: Array<String>) { find("myfile.txt") }
  34. 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", ) Default values + named arguments
  35. 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", ) Default values + named arguments Figure(Color.RED, "Red figure")
  36. 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", ) Default values + named arguments Figure(color = Color.RED, description = "Red figure")
  37. Default values + named arguments Default argument values diminish the

    need for overloading in most cases. Named parameters is a necessary tool for working with default argument values
  38. Operator overloading To implement an operator, provide a member function

    or an extension function with a specific name for the corresponding type.
  39. Operator overloading To implement an operator, provide a member function

    or an extension function with a specific name for the corresponding type. data class Point(val x: Int, val y: Int) operator fun Point.unaryMinus() = Point(-x, -y) val point = Point(10, 20) fun main() { println(-point) // prints "Point(x=-10, y=-20)" }
  40. Expressions fun adjustSpeed(weather: Weather): Drive { val result: Drive if

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

    (weather is Rainy) { result = Safe } else { result = Calm } return result } fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe else Calm Note to myself: show in the IDE
  42. Expressions fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe

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

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

    else Calm What does the function return?
  45. Expressions fun adjustSpeed(weather: Weather) : Drive = ... private 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
  46. Expressions The expressions if, try, and when can return values

    as a result Note to myself: show in the IDE
  47. fun adjustSpeed(weather: Weather): Drive = when (weather) { is Rainy

    -> Safe else - > Calm } ‘when’ expression is exhaustive for sealed classes, enums and booleans
  48. fun adjustSpeed(weather: Weather): Drive = when (weather) { is Rainy

    -> Safe // else -> Calm } Compilation error: We should either specify all options or use the ‘else’ branch sealed class Weather object Rainy : Weather() object Sunny : Weather()
  49. Ranges val x = 10 val y = 9 if

    (x in 1 .. y+1) { println("fits in range") }
  50. Ranges: iteration for (x in 1 . . 5) {

    print(x) }
  51. Ranges: iteration for (x in 1 . . 5) {

    print(x) } for (x in 1 . . 10 step 2) { print(x) } for (x in 9 downTo 0 step 3) { print(x) }
  52. fun isLatinUppercase(c: Char) = c >= 'A' & & c

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

    < = 'Z' fun isLatinUppercase(c: Char) = c in 'A' .. 'Z' Use range checks instead of comparison pairs
  54. class Version(val major: Int, val minor: Int): Comparable<Version> { 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
  55. class Version(val major: Int, val minor: Int): Comparable<Version> { 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 : Comparable<T > > T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
  56. class Version(val major: Int, val minor: Int): Comparable<Version> { 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 : Comparable<T > > T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
  57. Ranges in loops fun main(args: Array<String>) { for (i in

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

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

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

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

    0 .. args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") } for ((i, value) in args.withIndex()) { println("$i: $value}") }
  62. None
  63. Destructuring declarations data class Person(val firstName: String, val lastName: String)

    val (name, age) = Person("Anton", "Arhipov")
  64. Destructuring declarations data class Person(val firstName: String, val lastName: String)

    val p = Person("Anton", "Arhipov") val name = p.component1() val age = p.component2()
  65. Destructuring: returning multiple values data class Result(val result: Int, val

    status: Status) fun function( ... ): Result { // computations return Result(result, status) } // Now, to use this function: val (result, status) = function( .. . )
  66. Destructuring: iterating over a map for ((key, value) in map)

    { // do something with the key and the value }
  67. Null-safety

  68. Null-safety class Nullable { fun someFunction(){} } fun createNullable(): Nullable?

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

    = null fun main() { val n: Nullable? = createNullable() n.someFunction() }
  70. 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
  71. Using null-safe call val order = retrieveOrder() if (order ?.

    customer ?. address == null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
  72. Using null-safe call val order = retrieveOrder() val city =

    order ?. customer ?. address ?. city ?: throw IllegalArgumentException("Invalid Order")
  73. Using null-safe call 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
  74. Use lateinit instead of !! 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) } }
  75. Use lateinit instead of !! 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) } }
  76. None
  77. class Person(val name: String?, val age: Int?) val p =

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

    retrievePerson() ?: Person() Use elvis operator
  79. 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") }
  80. 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") }
  81. 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") }
  82. 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") }
  83. 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 } ‘as’ throws a ClassCastException on cast failure ‘as?’ returns null value on cast failure
  84. Classes Properties Data classes Sealed classes Inline classes Objects Note

    to myself: show in the IDE
  85. Collections Note to myself: show in the IDE

  86. Java interoperability

  87. None
  88. Standard library

  89. Idiomatic Kotlin

  90. Kotlin for backend development

  91. Idiomatic Kotlin (writing DSL-like code)

  92. Idiomatic - using, containing, or denoting expressions that are natural

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

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

    to a native speaker Commonly accepted style Effective use of features
  95. None
  96. Domain Specific Languages DSL

  97. External VS Internal

  98. External VS Internal

  99. External VS Internal

  100. External VS Internal

  101. External VS Internal

  102. Internal

  103. val name = "Joe" val s = buildString { repeat(5)

    { append("Hello, ") append(name) appendLine("!") } } println(s) 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); stdlib
  104. System.out.appendHTML().html { body { div { a("http: // kotlinlang.org") {

    target = ATarget.blank +"Main site" } } } } kotlinx.html
  105. kotlinx.html 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) }
  106. kotlinx.html 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 kotlinx.html
  107. Let’s write some code! https://github.com/antonarhipov/kotlin-dsl-examples

  108. Kotlin More presentations available on Kotlin channel youtube.com/kotlin Thanks for

    watching!