Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced Kotlin

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Advanced Kotlin

droidcon NYC 2018 talk where I go over some more advanced features of the Kotlin language and of the stdlib.

Avatar for Brent Watson

Brent Watson

August 28, 2018

Other Decks in Technology

Transcript

  1. Topics • delegates • `infix` • `where` keyword • annotation

    use-site targets • `tailrec` • sealed classes • in/out (covarience / contravarience) • reflection • DSLs
  2. Delegates private val someString by lazy { “init” } private

    val parser by lazy { SnapshotParser<DataProvider>(getKey()) }
  3. Delegates - observable Returns a property delegate for a read/write

    property that calls a specified callback function when changed. var maxCount: Int by observable(initialValue = 0) { property, oldValue, newValue -> println("${property.name} is being changed from $oldValue to $newValue") }
  4. Delegates - vetoable Called before a var is modified. Lambda

    returns true|false if value is allowed to change. var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue -> newValue > 0 }
  5. Delegates - notNull Referencing this before it is set results

    in a IllegalStateException var age by notNull<Int>() fun main(args: Array<String>) { println(age) // IllegalStateException }
  6. Delegates - Creating Your Own • Delegates used by vals

    need a getValue method. • Delegates used by vars need a getValue and setValue method. • But, Delegate is not an interface, these are operators.
  7. Delegates - Creating Your Own A Delegate Interface is not

    needed, but the stdlib does give you a few so that you don’t mess up... • ReadOnlyProperty • ReadWriteProperty • ObservableProperty
  8. Delegates - Creating your own class SimpleDelegate<T> : ReadWriteProperty<Any, T?>

    { private var value: T? = null override fun getValue(thisRef: Any, property: KProperty<*>) = value override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) { this.value = value } } private var someNum by SimpleDelegate<Int>() private var someStr by SimpleDelegate<String>() <R> (“Any”) is the containing class (“thisRef”), so usually Any is suffiicient.
  9. Delegates - Creating your own class SimpleDelegate<T> { private var

    value: T? = null operator fun getValue(thisRef: Any, property: KProperty<*>) = value operator fun setValue(thisRef: Any, property: KProperty<*>, value: T?) { this.value = value } } // Lazy.kt does it as an extension function public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
  10. Delegates - Creating your own class IdempotentDelegate<out T>(private val id:

    T) : ReadOnlyProperty<Any, T> { override fun getValue(thisRef: Any, property: KProperty<*>) = id } private val zero by IdempotentDelegate(0)
  11. Delegates - ObservableProperty public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?,

    T> { private var value = initialValue /** * If the callback returns `true` the value of the property is being set to the new value, * and if the callback returns `false` the new value is discarded. */ protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {} public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { val oldValue = this.value if (!beforeChange(property, oldValue, value)) { return } this.value = value afterChange(property, oldValue, value) } public override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value }
  12. Delegates - Something useful... class SharedPrefDelegate<T>(val key: String, val defaultValue:

    T) { val prefs: SharedPreferences by lazy { context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE) } operator fun getValue(thisRef: Any?, property: KProperty<*>) = with(prefs) { when (defaultValue) { is Boolean -> getBoolean(key, defaultValue) is Int -> getInt(key, defaultValue) ... else -> throw IllegalArgumentException() } } as T operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = with(prefs.edit()) { when (value) { is Boolean -> putBoolean(key, value) is Int -> putInt(key, value) ... }.apply() } } val myStringPref by SharedPrefDelegate<String>(key, defaultValue = "") val myIntPref by SharedPrefDelegate<Int>(key, defaultValue = 0)
  13. Infix mapOf( "one" to 1, "two" to 2, "three" to

    3 ) mapOf( "one".to(1), "two".to(2), "three".to(3) ) public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) for (i in 10 downTo 1) { ... }
  14. Infix Rules: - Simply add infix keyword before to method

    signature - Single parameter (no varargs) - No default value allowed - Member functions (or extension function)
  15. enum class Suit { HEARTS, SPADES, CLUBS, DIAMONDS } enum

    class Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE; } data class Card(val rank: Rank, val suit: Suit) val card = Card(SEVEN, DIAMONDS)
  16. enum class Suit { HEARTS, SPADES, CLUBS, DIAMONDS } enum

    class Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE; fun of(suit: Suit) = Card(this, suit) } data class Card(val rank: Rank, val suit: Suit) val card = SEVEN.of(DIAMONDS)
  17. enum class Suit { HEARTS, SPADES, CLUBS, DIAMONDS } enum

    class Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE; infix fun of(suit: Suit) = Card(this, suit) } data class Card(val rank: Rank, val suit: Suit) val card = SEVEN of DIAMONDS
  18. Infix - Don’t overuse them - They cause mental pause

    when encountered. - Great for DSLs: // From nhaarman/mockito-kotlin: infix fun <T> OngoingStubbing<T>.doReturn(t: T): OngoingStubbing<T> { ... } infix fun <T> BDDMyOngoingStubbing<T>.willAnswer(value: () -> T) { ... } // Usage... val mockResult = mock<GoogleSignInResult> { on { isSuccess } doReturn true on { signInAccount } doReturn mock<GoogleSignInAccount>() } given(mock) { calling { platform } willAnswer { "android" } }
  19. stdlib infix functions public class Int ... /** Shifts this

    value left by the [bitCount] number of bits. */ public infix fun shl(bitCount: Int): Int /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with copies of the sign bit. */ public infix fun shr(bitCount: Int): Int /** Shifts this value right by the [bitCount] number of bits, filling the leftmost bits with zeros. */ public infix fun ushr(bitCount: Int): Int /** Performs a bitwise AND operation between the two values. */ public infix fun and(other: Int): Int /** Performs a bitwise OR operation between the two values. */ public infix fun or(other: Int): Int /** Performs a bitwise XOR operation between the two values. */ public infix fun xor(other: Int): Int
  20. stdlib infix functions public inline infix fun CharSequence.matches(regex: Regex): Boolean

    public infix fun CharSequence.zip(other: CharSequence): List<Pair<Char, Char>> public infix fun <T, R> Iterable<T>.zip(other: Array<out R>): List<Pair<T, R>> public infix fun <T> Iterable<T>.intersect(other: Iterable<T>): Set<T> public infix fun <T> Iterable<T>.union(other: Iterable<T>): Set<T> public infix fun <T> Iterable<T>.subtract(other: Iterable<T>): Set<T>
  21. where keyword // No Bounds fun <T> printUntil(items: List<T>, stopAt:

    T) = items.takeWhile { it != stopAt }.let { println(it) } // Only one upper bound can be specified inside the angle brackets // Number does not implement Comparable fun <T : Number> printUntil(items: List<T>, stopAt: T) = items.takeWhile { it < stopAt }.let { println(it) }
  22. where keyword fun <T> printUntil(items: List<T>, stopAt: T) where T

    : Number, T : Comparable<T> { items.takeWhile { it > stopAt }.let { println(it) }
  23. where keyword fun <T> printUntil(items: List<T>, stopAt: T) where T

    : Number, T : Comparable<T> { items.takeWhile { it > stopAt }.let { println(it) } // Alternative formatting: fun <T> printUntil(items: List<T>, stopAt: T) where T : Number, T : Comparable<T> { items.takeWhile { it > stopAt }.let { println(it) }
  24. @file:JvmName("NewName") package com.brentwatson.droidcon.targets annotation class GetAnnotation annotation class SetAnnotation annotation

    class SetAnnotation2 class Person( @get:GetAnnotation val first: String, @set:[SetAnnotation SetAnnotation2] var last: String ) { // ... }
  25. @file:JvmName("NewName") package com.brentwatson.droidcon.targets annotation class GetAnnotation annotation class SetAnnotation annotation

    class SetAnnotation2 class Person( @get:GetAnnotation val first: String, @set:[SetAnnotation SetAnnotation2] var last: String ) { // ... } public final class Person { @NotNull private final String first; @NotNull private String last; @GetAnnotation @NotNull public final String getFirst() { return this.first; } @NotNull public final String getLast() { return this.last; } @SetAnnotation @SetAnnotation2 public final void setLast(@NotNull String var1) { this.last = var1; } public Person(@NotNull String first, @NotNull String last) { super(); this.first = first; this.last = last; } }
  26. package com.brentwatson.droidcon.targets annotation class ParamAnnotation annotation class FieldAnnotation class Person(

    @param:ParamAnnotation val first: String, @field:FieldAnnotation val last: String ) { // ... } public final class Person { @NotNull private final String first; @FieldAnnotation @NotNull private final String last; @NotNull public final String getFirst() { return this.first; } @NotNull public final String getLast() { return this.last; } public Person(@ParamAnnotation @NotNull String first, @NotNull String last) { super(); this.first = first; this.last = last; } }
  27. package com.brentwatson.droidcon.targets annotation class ReceiverAnnotation fun @receiver:ReceiverAnnotation String?.isNotNullOrBlank() = !this.isNullOrBlank()

    public final class Temp { public static final boolean isNotNullOrBlank( @ReceiverAnnotation @Nullable String $receiver ) { CharSequence var1 = (CharSequence)$receiver; return var1 != null && !StringsKt.isBlank(var1); } }
  28. // "annotations with this target are not visible to Java"

    annotation class PropertyAnnotation class SomeClass1( @property:PropertyAnnotation val constructorProp: String ) { @property:PropertyAnnotation val regProp = "Test" } fun main(args: Array<String>) { for (property in SomeClass1::class.memberProperties) { println("Property ${property.name} has annotations ${property.annotations}") } val primaryConstructor = SomeClass1::class.primaryConstructor!! for (param in primaryConstructor.parameters) { println("Parameter ${param.name} has annotations ${param.annotations}") } } Property constructorProp has annotations [@com.brentwatson.droidcon.targets.PropertyAnnotation()] Property regProp has annotations [@com.brentwatson.droidcon.targets.PropertyAnnotation()] Parameter constructorProp has annotations []
  29. package com.brentwatson.droidcon.targets annotation class DelegateAnnotation class MyDel { @delegate:DelegateAnnotation @get:GetAnnotation

    val name by lazy { "something" } } public final class MyDel { @DelegateAnnotation @NotNull private final Lazy name$delegate; @GetAnnotation @NotNull public final String getName() { Lazy var1 = this.name$delegate; KProperty var3 = $$delegatedProperties[0]; return (String)var1.getValue(); } public MyDel() { this.name$delegate = LazyKt.lazy((Function0)null.INSTANCE); } }
  30. package com.brentwatson.droidcon.targets annotation class SetParamAnnotation class SetParamAnnTest() { @setparam:SetParamAnnotation @set:SetAnnotation

    var something: String? = null } public final class SetParamAnnTest { @Nullable private String something; @Nullable public final String getSomething() { return this.something; } @SetAnnotation public final void setSomething( @SetParamAnnotation @Nullable String var1 ) { this.something = var1; } }
  31. tailrec recursion vs corecursion Kotlin can turn corecursive functions into

    loops for you (using `tailrec`) but it cannot do the same for recursive functions.
  32. tailrec Recursive functions will call themselves during computation Must finishes

    calculating before returning a value Is limited by the heap size. Corecursive functions calls itself indefinitely (like a infinite loop) Each step is evaluated as it happens Builds up values Is not limited by the heap size Usually easiest to think about solving a problem backwards
  33. recursion vs corecursion example // 5! == 5 x 4

    x 3 x 2 x 1 == 120 // recursion fun factorial(n: Int): Int = if (n == 1) n else n * factorial(n - 1)
  34. recursion vs corecursion example // 5! == 5 x 4

    x 3 x 2 x 1 == 120 // recursion fun factorial(n: Int): Int = if (n == 1) n else n * factorial(n - 1) // corecursion fun factorial(next: Int, total: Int): Int = when (next) { 1 -> total else -> factorial(next - 1, total * next) }
  35. recursion vs corecursion example // 5! == 5 x 4

    x 3 x 2 x 1 == 120 // recursion tailrec fun factorial(n: Int): Int = if (n == 1) n else n * factorial(n - 1) // corecursion tailrec fun factorial(next: Int, total: Int): Int = when (next) { 1 -> total else -> factorial(next - 1, total * next) }
  36. // 5! == 5 x 4 x 3 x 2

    x 1 == 120 // recursion tailrec fun factorial(n: Int): Int = if (n == 1) n else n * factorial(n - 1) // corecursion tailrec fun factorial(next: Int, total: Int): Int = when (next) { 1 -> total else -> factorial(next - 1, total * next) } public final class RecursionKt { public static final int factorial(int n) { return n == 1 ? n : n * factorial(n - 1); } public static final int factorial(int next, int total) { while(next != 1) { int var = next - 1; total *= next; next = var; } return total; } }
  37. Tailrec - another example // "54321" "5432" "543" "54" "5"

    fun countDown(str: String): String = if (str.length == 1) { str } else { str + countDown(str.dropLast(1)) }
  38. Tailrec - another example tailrec fun countDown(str: String, output: String):

    String = if (str.isEmpty()) { output } else { countDown(str.dropLast(1), output + str) }
  39. Tailrec - another example fun countDown(str: String): String { tailrec

    fun doCountDown(str: String, output: String): String = if (str.isEmpty()) { output } else { doCountDown(str.dropLast(1), output + str) } return doCountDown(str, "") }
  40. Sealed Classes sealed class ApiResult { class Success : ApiResult()

    class Failure(val code: Int) : ApiResult() class Error(val err: Throwable) : ApiResult() }
  41. Sealed Classes sealed class ApiResult { class Success : ApiResult()

    class Failure(val code: Int) : ApiResult() class Error(val err: Throwable) : ApiResult() } fun logForResult(): String { return when(resp){ is ApiResult.Success -> "success" is ApiResult.Failure -> "not cool: ${resp.code}" is ApiResult.Error -> "ruh roh: ${resp.err}" } }
  42. Sealed Classes sealed class BillingResult { object Success : BillingResult()

    sealed class Failure(val log: String): BillingResult() { object Generic : Failure("generic failure") sealed class Api(code: String) : Failure("Response $code") { class Known(val code: Int) : Api(code.toString()) object Unknown : Api("Unknown") } } }
  43. Sealed Classes sealed class BillingResult { object Success : BillingResult()

    sealed class Failure(val log: String): BillingResult() { object Generic : Failure("generic failure") sealed class Api(code: String) : Failure("Response $code") { class Known(val code: Int) : Api(code.toString()) object Unknown : Api("Unknown") } } } fun logFailures() { if (resp is BillingResult.Failure.Api.Known) { println(resp.log) } }
  44. Sealed Classes sealed class BillingResult { object Success : BillingResult()

    sealed class Failure(val log: String): BillingResult() { object Generic : Failure("generic failure") sealed class Api(code: String) : Failure("Response $code") { class Known(val code: Int) : Api(code.toString()) object Unknown : Api("Unknown") } } } fun logFromFailures(): String? { return when (resp) { is BillingResult.Failure.Generic -> resp.log is BillingResult.Failure.Api.Known -> resp.code.toString() } }
  45. Q: What is the most overloaded keyword in kotlin? Example:

    import kotlinx.android.synthetic.main.view_upgrade_prompt.view.close_button as closeButton val sheetDialog = dialog as BottomSheetDialog?
  46. A: `in` • specifies the object being iterated in a

    for loop for (item in items) { • used as an infix operator to check that a value belongs to a range (or anything that defines the contains() method). val isInRange = 5 in 1..10 • in when expressions for the same purpose when(size) { in 1..10 -> println("small") in 11..100 -> println("medium") in 101..1000 -> println("large") else -> println("Fuhgeddaboudit") } • marks a type parameter as contravariant
  47. in/out class Giver<out T> { fun gives(): T {} }

    class Taker<in T> { fun takes(t: T) {} }
  48. in/out class Giver<out T> { fun gives(): T {} }

    class Taker<in T> { fun takes(t: T) {} }
  49. covarience / contravarience class Giver<out T> { fun gives(): T

    {} } class Taker<in T> { fun takes(t: T) {} }
  50. sealed class Hardware { sealed class Phone : Hardware() {

    class Android( val manufacturer: String, val sdkVersion: Int ) : Phone() class BlackBerry( val obsolete: Boolean = true, val sdkVersion: Int ) : Phone() } }
  51. sealed class Hardware { sealed class Phone : Hardware() {

    class Android( val manufacturer: String, val sdkVersion: Int ) : Phone() class BlackBerry( val obsolete: Boolean = true, val sdkVersion: Int ) : Phone() } } class Manufacturer<out T: Hardware> { fun create(): T { } } class MyDriveWay<in T: Hardware> { fun pleaseShatterMyScreen(t: T) {} }
  52. sealed class Hardware { sealed class Phone : Hardware() {

    class Android( val manufacturer: String, val sdkVersion: Int ) : Phone() class BlackBerry( val obsolete: Boolean = true, val sdkVersion: Int ) : Phone() } } class Manufacturer<out T: Hardware> { fun create(): T { } } class MyDriveWay<in T: Hardware> { fun pleaseShatterMyScreen(t: T) {} } val producer = Manufacturer<Hardware.Phone>() val consumer = MyDriveWay<Hardware.Phone>() val phone: Hardware.Phone = producer.create() consumer.pleaseShatterMyScreen(phone)
  53. sealed class Hardware { sealed class Phone : Hardware() {

    class Android( val manufacturer: String, val sdkVersion: Int ) : Phone() class BlackBerry( val obsolete: Boolean = true, val sdkVersion: Int ) : Phone() } } class Manufacturer<out T: Hardware> { fun create(): T { } } class MyDriveWay<in T: Hardware> { fun pleaseShatterMyScreen(t: T) {} } val producer = Manufacturer<Hardware.Phone>() val consumer = MyDriveWay<Hardware.Phone>() val hardware: Hardware = producer.create() val phone: Hardware.Phone = producer.create() consumer.pleaseShatterMyScreen(phone) consumer.pleaseShatterMyScreen(android)
  54. sealed class Hardware { sealed class Phone : Hardware() {

    class Android( val manufacturer: String, val sdkVersion: Int ) : Phone() class BlackBerry( val obsolete: Boolean = true, val sdkVersion: Int ) : Phone() } } class Manufacturer<out T: Hardware> { fun create(): T { } } class MyDriveWay<in T: Hardware> { fun pleaseShatterMyScreen(t: T) {} } val producer = Manufacturer<Hardware.Phone>() val consumer = MyDriveWay<Hardware.Phone>() val hardware: Hardware = producer.create() val phone: Hardware.Phone = producer.create() val android: Hardware.Phone.Android = producer.create() consumer.pleaseShatterMyScreen(hardware) consumer.pleaseShatterMyScreen(phone) consumer.pleaseShatterMyScreen(android)
  55. public interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {

    public val isFinal: Boolean public val isOpen: Boolean public val isAbstract: Boolean public val isSealed: Boolean public val isData: Boolean public val isInner: Boolean public val isCompanion: Boolean public val visibility: KVisibility? public val simpleName: String? public val qualifiedName: String? override val members: Collection<KCallable<*>> // KDeclarationContainer override val annotations: List<Annotation> // KAnnotatedElement public val constructors: Collection<KFunction<T>> public val nestedClasses: Collection<KClass<*>> public val supertypes: List<KType> public val typeParameters: List<KTypeParameter> }
  56. public interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {

    ... } @Suppress("UPPER_BOUND_VIOLATED") public val <T> KClass<T>.java: Class<T> @JvmName("getJavaClass") get() = (this as ClassBasedDeclarationContainer).jClass as Class<T>
  57. class Demo(val p1: String, val p2: Int) { fun callMe(input:

    String): String { println(input) return "called" } } val demo = Demo("", 0) val func = demo::callMe val res = func.call("some input") println(res)
  58. val constructor: KFunction<Demo>? = Demo::class.primaryConstructor val property: KProperty1<Demo, *> =

    Demo::class.memberProperties.first() val getter: KProperty1.Getter<Demo, Any?> = property.getter val setter: KMutableProperty.Setter<String>? = (property as KMutableProperty<String>).setter val demo = constructor?.call("param1", 99) println(getter.call(demo)) setter?.call(demo, "changed") println(demo?.p1)
  59. You will see KProperty used in the stdlib var maxCount:

    Int by observable(initialValue = 0) { property: KProperty<*>, oldValue, newValue -> println("${property.name} is being changed from $oldValue to $newValue") }
  60. You will see KProperty used in the stdlib var maxCount:

    Int by observable(initialValue = 0) { property: KProperty<*>, oldValue, newValue -> println("${property.name} is being changed from $oldValue to $newValue") } fun KProperty<*>.log() { println("${this.name} is set to ${this.getter.call()}") } demo::p1.log()
  61. DSLs - Lambdas with Receivers val out = with(StringBuilder()) {

    append("hello") append("world") reverse() toString() } val accountManager = AccountManager() with(accountManager) { val account = findAccount() account.updateBalance(newBal) saveAccount(account) }
  62. DSLs - Lambdas with Receivers fun reg(l: (StringBuilder) -> Unit)

    { l(StringBuilder()) } fun rec(l: StringBuilder.() -> Unit) { l(StringBuilder()) }
  63. DSLs - Lambdas with Receivers fun reg(l: (StringBuilder) -> Unit)

    { l(StringBuilder()) } fun rec(l: StringBuilder.() -> Unit) { l(StringBuilder()) } fun main(args: Array<String>) { reg { sb -> sb.append("test") } rec { append("test") } }
  64. DSLs - Lambdas with Receivers fun reg(l: (StringBuilder, Int, Int)

    -> Unit) { l(StringBuilder(), 0, 0) } fun rec(l: StringBuilder.(Int, Int) -> Unit) { l(StringBuilder(), 0, 0) }
  65. DSLs - Lambdas with Receivers fun reg(l: (StringBuilder, Int, Int)

    -> Unit) { l(StringBuilder(), 0, 0) } fun rec(l: StringBuilder.(Int, Int) -> Unit) { l(StringBuilder(), 0, 0) } fun main(args: Array<String>) { reg { sb, i1, i2 -> sb.append(i1) sb.append(i2) } rec { i1, i2 -> append(i1) append(i2) }
  66. class Thing1 { fun t1(t2: Thing2.() -> Unit) {} }

    class Thing2 { fun t2(t3: Thing3.() -> Unit) {} } class Thing3 { fun done() {} } fun makeThing() = Thing1()
  67. class Thing1 { fun t1(t2: Thing2.() -> Unit) {} }

    class Thing2 { fun t2(t3: Thing3.() -> Unit) {} } class Thing3 { fun done() {} } fun makeThing() = Thing1() fun main(args: Array<String>) { makeThing().t1 { t2 { done() } } }
  68. class Thing1 {} class Thing2 {} class Thing3 {} fun

    Thing1.t1(t2: Thing2.() -> Unit) {} fun Thing2.t2(t3: Thing3.() -> Unit) {} fun Thing3.done() {} fun makeThing() = Thing1() fun main(args: Array<String>) { makeThing().t1 { t2 { done() } } }
  69. class Html {} class Body {} class H1 {} fun

    Html.body(body: Body.() -> Unit) {} fun Body.h1(h1: H1.() -> Unit) {} fun H1.text(text: String) {} fun createHtml() = Html() fun main(args: Array<String>) { createHtml().body { h1 { text("header") } } }
  70. class Html {} class Body {} class H1 {} class

    H2 {} ... fun Html.body(body: Body.() -> Unit) {} fun Body.h1(h1: H1.() -> Unit) {} fun Body.h2(h2: H2.() -> Unit) {} ... fun H1.text(text: String) {} fun H2.text(text: String) {} ... fun createHtml() = Html() fun main(args: Array<String>) { createHtml().body { h1 { text("header") } h2 { text("sub-header") } } }
  71. Further reading • 10 Kotlin Tricks in 10 ish minutes

    by Jake Wharton • Kotlin 102 - Beyond the basics by Hadi Hariri • Christina Lee - Advanced Kotlin (Øredev 2017) • Kotlin Puzzlers by Anton Keks (KotlinConf 2017)