"Advanced Kotlin Programming" Notes

September 07, 2018 14 minutes

These are my personal notes. The complete course can be found at https://www.safaribooksonline.com/videos/advanced-kotlin-programming/9781491964149/

Local functions

fun global(globalParam: Int) {
    // local is accessible only inside global
    fun local(localParam: Int) {
        // Here we can access globalParam and localParam
    }
}

Infix functions

Applicable to both member functions and extension functions that have single parameters.

infix fun Int.isDivisibleBy(other: Int) = this % other == 0

fun main(args: Array<String>) {
    println(8 isDivisibleBy 2) // true
}

Anonymous functions

someOtherFunction(fun(x: Int): Int {
    if (x == 25) {
        return 50;
    } else {
        return x * 20;
    }
})

An anonymous function has two advantages over lambdas:

  • It can have an explicit return type.
  • It can have multiple return points.

Inline functions

If we add the inline modifier to a function, it’ll get inlined at every call site. This is used mostly for optimization purposes when dealing with higher-order functions, and using it with other functions that aren’t of higher-order may provide negligible benefits.

inline fun executeAndLog(op: () -> Unit) {
    println("Execution starts!")
    op()
    println("Execution has finished.")
}

If we wish to NOT inline one of the lambdas, we can add the noinline modifier to the parameter.

inline fun executeAndLog(noinline op: () -> Unit) { ... }

With that said, in the previous example it wouldn’t make sense to inline the function if the only lambda parameter isn’t going to be inlined. IntelliJ will raise a warning if we try to do this.

More information about inlining: https://kotlinlang.org/docs/reference/inline-functions.html

Returns and local returns

Wall of code incoming!

fun <T> Iterable<T>.nonInlineForEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

fun main(args: Array<String>) {
    val numbers = 1..10

    nonLocalReturn(numbers)
    defaultLabelLocalReturn(numbers)
    customLabelLocalReturn(numbers)
    anonymousFunctionLocalReturn(numbers)

    // Non local returns are allowed only in inline functions used as lambda
    //numbers.nonInlineForEach {
    //    if (it % 7 == 0) {
    //        return // this doesn't compile
    //    }
    //}

    println("main finished.")
}

fun anonymousFunctionLocalReturn(numbers: Iterable<Int>) {
    numbers.forEach(fun(element) {
        if (element % 7 == 0) {
            return
        }
    })
    println("anonymousFunctionLocalReturn")
}

fun customLabelLocalReturn(numbers: Iterable<Int>) {
    numbers.forEach myLabel@{
        if (it % 7 == 0) {
            return@myLabel
        }
    }
    println("customLabelLocalReturn")
}

fun nonLocalReturn(numbers: Iterable<Int>) {
    numbers.forEach {
        if (it % 7 == 0) {
            return // this returns out from nonLocalReturn
        }
    }
    // This will never be called.
    println("nonLocalReturn")
}

fun defaultLabelLocalReturn(numbers: Iterable<Int>) {
    numbers.forEach {
        if (it % 7 == 0) {
            return@forEach
        }
    }
    println("defaultLabelLocalReturn")
}

What will get printed after executing that is:

defaultLabelLocalReturn
customLabelLocalReturn
anonymousFunctionLocalReturn
main

Key points:

  • An unlabeled return returns out of the nearest fun keyword. While a not very elaborate explanation, is a more than adequate heuristic.
  • Unlabeled returns can only be used inside lambdas if they’re passed to inline functions.
  • Labeled returns can be used inside lambdas for both inline and non-inlined functions.

Tail recursion

Tail recursion is a common optimization that compiles a recursive operation into a looping one, preventing stack overflows and providing better performance (citation required) as long as our function meets some criteria.

Kotlin can optimize tail recursion, but it doesn’t do it by default. If the last operation of a function is a call to itself, we can add the tailrec modifier to it to convert it into tail recursive.

tailrec fun factorial(number: BigInteger, accumulator: BigInteger = BigInteger.ONE): BigInteger {
    when (number) {
        BigInteger.ZERO -> return accumulator
        else -> return factorial(number - BigInteger.ONE, accumulator * number)
    }
}

fun main(args: Array<String>) {
    val result = factorial(BigInteger.valueOf(10000))
    println(result)
}

The previous example will throw a StackOverflowException if we remove the tailrec from the factorial function.

Operator overloading

To implement an operator for our custom data type, we must create a function named accordingly as per the list that’s below, and add the operator modifier to it.

class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(this.x + other.x, this.y + other.y)
    }
}

fun main(args: Array<String>) {
    val a = Point(10, 10)
    val b = Point(5, 5)
    val c = a + b
    println("${c.x}, ${c.y}")
}

Only the following operators can be implemented in our custom data types:

Unary operators

  • +a = a.unaryPlus()
  • -a = a.unaryMinus()
  • !a = a.not()
  • a++ = a.inc()
  • a- = a.dec()

Binary operators

  • a + b = a.plus(b)
  • a - b = a.minus(b)
  • a * b = a.times(b)
  • a / b = a.div(b)
  • a % b = a.mod(b)
  • a .. b = a.rangeTo(b)
  • a in b = b.contains(a)
  • a !in b = !b.contains(a)
  • a += b = a.plusAssign(b)
  • a -= b = a.minusAssign(b)
  • a *= b = a.timesAssign(b)
  • a /= b = a.divAssign(b)
  • a %= b = a.modAssign(b)
  • a > b = a.compareTo(b) > 0
  • a < b = a.compareTo(b) < 0
  • a >= b = a.compareTo(b) >= 0
  • a <= b = a.compareTo(b) <= 0

Indexing operations

  • a[i] = a.get(i)
  • a[i, j] = a.get(i, j)
  • a[i1, i2, ..., iN] = a.get(i1, i2, ..., iN)
  • a[i] = b = a.set(i, b)
  • a[i, j] = b = a.set(i, j, b)
  • a[i1, i2, ..., iN] = b = a.set(i1, i2, ..., iN, b)

Invocation operations

  • a() = a.invoke()
  • a(x) = a.invoke(x)
  • a(x, y) = a.invoke(x, y)
  • a(x1, x2, ... xN) = a.invoke(x1, x2, ..., xN)

It’s possible to implement operator functions as extension functions too.

Lambda extensions

Extension functions can access properties of the extended class.

Lambda extensions (AKA lambdas with receivers) are lambdas that can access properties of the extended class.

It sounds simple, but… it can be confusing as fuck if we don’t start from the beginning and add stuff as we go.

class Something(val name: String)

fun Something.printName() {
    println("Inside printName. name is $name.")
}

fun executeOnThing(thing: Something, operation: Something.() -> Unit) {
    operation(thing)
}

fun main(args: Array<String>) {
    val ball = Something("Ball")
    executeOnThing(ball, Something::printName)
    executeOnThing(ball) {
        println("Inside lambda extension. name is $name.")
    }
}

While very abstract and uncreative, at least it’s not an animal example. First:

operation: Something.() -> Unit

It’s a parameter of type “function with receiver that returns Unit”. We can pass an extension function as an argument, or a lambda expression.

We did the former when we passed Something.printName() as an argument in executeOnThing(ball, Something::printName).

We did the latter when we passed a lambda function. Just like a normal extension function, the lambda will be able to access the relevant object instance by means of this.

When we call the function with receiver, we must pass an instance of the corresponding type: the receiver. We did that on the executeOnThing function.

An actual, less forced example, can be seen at https://kotlinlang.org/docs/reference/type-safe-builders.html. The course video for this particular functionality was very confusing, so it’s best to just check the official documentation.

Invoking instances

Easily missed in the operator overloading section. We can make any object instance invocable by implementing the invoke operator function.

class PowerOffCommand() : Command() {
    override fun execute() {
        // poweroff computer, etc.
    }

    operator fun invoke() {
        execute()
    }
}

val cmd = PowerOffCommand()
cmd()

Obviously this example wouldn’t make that much sense in a language with higher-order functions like Kotlin, but that’s not the point ;).

Functional constructs

Beyond passing functions from place to place, there are several other concepts that make the toolbox of a functional programmer. Currying, function composition, and several other techniques aren’t implemented OOB in Kotlin, but can be replicated by making use of the already discussed constructs.

There’s a Kotlin library that already implements several of those things for us:

I’m no functional expert, so I’m leaving further exploration to some other time.


Fields

In custom getters and setters we can access the automatically created backing field by using the keyword field.

If we can’t (or don’t want to) use the auto backing field, then we should make a private property and reference it.

Late initialization

class RegistrationService {
    lateinit var userRepository: UserRepository
}

If for whatever reason we have a property that’s not nullable, but that we are unable to assign in the constructor for whatever reason, we can add the lateinit modifier to it, telling the compiler this way that we are responsible for assigning it a value later on.

If we try to access a uninitialized property, we’ll get a kotlin.UninitializedPropertyAccessException.

Nested classes

You can declare classes inside other classes, just like in C#. By default, the outer class acts like a namespace for the inner class and not much anything else.

val instance = OuterClass.InnerClass()

It becomes different to (and much useful than) C# when we add the inner modifier to the mix. When we add the inner modifier to a nested class, it becomes a inner class, instances of which can only be created by an instance of the outer class, but in exchange it becomes able to access properties of it’s enclosing class.

Companion objects

In Kotlin there are no static methods. There are top level functions, and functions in objects, though.

But what if we really, really need to use static functions?

class ExampleClass {
    companion object Objeto {
        @JvmStatic fun exampleStaticMethod() {
            println("Static method!")
        }
    }
}

fun main(args: Array<String>) {
    ExampleClass.Objeto.exampleStaticMethod() // syntax for non companion objects
    ExampleClass.exampleStaticMethod() // companion object syntax
}

Notes

  • We can have only ONE (1) object marked as the companion object.
  • If we mark an object as the companion object, then it doesn’t need to have a name (i.e. we could delete “Objeto” from the source example).
  • If we want Java-bois to be able to access the companion object method as a static method, then we’ll need to add the JvmStatic annotation to our function. Otherwise they’ll need to access it with the ExampleClass.Objeto.exampleStaticMethod syntax if the companion object is called Objeto (as per our example), or ExampleClass.Companion.exampleStaticMethod if the companion object has no name.

Hiding constructors

class ExampleClass private constructor() {
    // ...
}

We explicitly declare the main constructor as private. That’s it.

Sealed classes

https://kotlinlang.org/docs/reference/sealed-classes.html

Sealed classes are classes that can only be subclassed by other classes in the same file. They’re used to represent restricted class hierarchies, like for example possible results of an operation (e.g. results from an application layer service call).

The big selling point for sealed classes is to use them in when expressions (not statements), as when checking for types of a sealed class, then we can make sure we’re covering all cases, voiding the need of a superfluous else case.

fun eval(result: Result): Boolean = when(expr) {
    is Success -> true
    is Failure -> false
}

Yes, there are enums too, but enums have a single instance for each enum value. With a sealed class’ derived classes we can have several instances for each derived type, where we can include additional context as needed.

Type aliases

typealias Point = Pair<Int, Int>

var point: Point = Point(5, 7)

No new type is created, any function that takes a Pair<Int, Int> will also be able to take a Point.


The concept of delegation

In OOP it’s possible to make use of other classes by either inheriting from them or by composition. After more-than-enough unmaintainable hierarchies in practice, it has become common practice to prefer composition over the alternative.

Delegation is the practice of… err… delegating some functionality to another class.

class Rectangle(val width: Int, val height: Int) {
    fun area() = width * height
}

class Window(val bounds: Rectangle) {
    // Delegation
    fun area() = bounds.area()
}

Delegating member functions

Kotlin supports delegating member function calls automatically by means of the by keyword.

class Controller(repository: Repository): Repository by repository {
    fun index(): String {
        getById() // this is automatically delegated to repository
    }
}

Delegating properties

https://kotlinlang.org/docs/reference/delegated-properties.html

class ExampleClass {
    var someProperty: String by SomeOtherClass()
}

class SomeOtherClass {
    var backingField = "Default"
    operator fun getValue(service: Service, property: KProperty<*>): String {
        return backingField
    }
    operator fun setValue(service: Service, property: KProperty<*>, value: String) {
        backingField = value
    }
}

property contains metadata about the delegated property. Creating custom delegates is something better covered in the official documentation, so I’m omitting further study of it for now.

Built-in delegated properties

https://kotlinlang.org/docs/reference/delegated-properties.html#standard-delegates https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/index.html

There are at least three notable standard delegated properties:

observable example:

var name: String by Delegates.observable("<no name>") { prop, old, new ->
    println("$old -> $new")
}

vetoable example:

var max: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
        newValue > oldValue
}

Local delegates

Local variables can also be delegated properties.

val cachedComputation by lazy(computeResult)

computeResult will be executed once, to initialize cachedComputation. Later accesses to the later will not execute computeResult again.

Extension properties

https://kotlinlang.org/docs/reference/extensions.html#extension-properties

They can’t have a backing field, nor an initializer (which would need a the backing field). Their behavior must be explicitly defined in their getter and setter.

val Int.isTheAnswer: Boolean
    get() = this == 42

Generic constraints

By default, any generic type parameter is constrained to Any?.

We can add a single constraint by defining the type parameter as <T: MyConstraint>.

We can add multiple constraints by defining the type parameter as <T> where T: MyFirstConstraint, T: SecondConstraint

For the record, using constrained generics in functions is done as follows: fun <T:Serializable> etc(myObj: T) { }

Invariance

Given Employee is a subtype of Person.

And given Structure<Employee> it’s not a subtype of Structure<Person>

And given Structure<Person> it’s not a subtype of Structure<Employee>

We say that Structure is invariant.

As long as Structure meets some criteria, we can make it covariant or contravariant.

Covariance & contravariance

See: https://blog.sinenie.cl/covariance-and-contravariance/

Type projections

Variance in Kotlin is usually defined at declaration-site.

It’s possible to define it at the call site, which will create a restricted form of the type. This is known as type projection.

Star projections ?? if (element is List<*>) safe way to indicate a subtype of a projection??


An introduction to reflection & metaprogramming

Metaprogramming is about observing and modifying programs in runtime. For this purpose, Kotlin can both use the classic Java reflection API, and it’s own API.

Scarce notes are taken about this topic, as I still don’t think I need to deep dive into reflection and metaprogramming. I still struggle with the “programming” part of Kotlin.

Using Java reflection

To access the Java Class object for an instance, call javaClass on it.

To access the Java Class object for a class: MyClass::class.java. Omitting the latter java function call would leave us with a KClass object, which is part of the Kotlin reflection API.

Using Kotlin reflection

Due to size concerns, and given that only a small percentage of projects actually need to use reflection functionality, the Kotlin reflection API is distributed as a separate .jar, not included with kotlin-stdlib.

To include it in out Maven project, add the following to dependencies:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
    <version>${kotlin.version}</version>
</dependency>

Further documentation: https://kotlinlang.org/docs/reference/reflection.html

Type erasure on the JVM

JVM doesnt not maintain information about type parameters actually used in a generic class. This is called type erasure.

It mostly means that we can’t do this:

// This doesn't compile.
fun <T> printList(list: List<T>) {
    when (list) {
        // cannot check type of erased type.
        is List<String> -> println("This is list of String")
        is List<Int> -> println("This is list of Int")
    }
}

And at the very best we can do THIS:

fun <T> printList(list: List<T>) {
    if (list is List<*>) { // is this a list of something?
        println("This is a list")
    }
}

We can still do some checks on non generic data types, for example:

fun <T> printThing(thing: T) {
    when (thing) {
        is String -> println("This is String")
        is Int -> println("This is Int")
    }
}

Reified generics

https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters https://github.com/JetBrains/kotlin/blob/master/spec-docs/reified-type-parameters.md https://stackoverflow.com/questions/45949584/how-does-the-reified-keyword-in-kotlin-work

Simplified example:

inline fun <reified T> printTypeParameter() {
    println(T::class)
}

fun main(args: Array<String>) {
    printTypeParameter<String>()
}

Somewhat “solving” our previous issue:

inline fun <reified T> printList(list: List<T>) {
    when (T::class) {
        String::class -> println("T is String")
        Int::class -> println("T is Int")
    }
}

fun main(args: Array<String>) {
    val myInts = listOf(1, 2, 3)
    val myStrings = listOf("Hello", "World")
    printList(myInts)
    printList(myStrings)
}

Custom annotations

https://kotlinlang.org/docs/reference/annotations.html

// YO DAWG
@Target(AnnotationTarget.CLASS)
annotation class Table(val name: String)

@Target(AnnotationTarget.PROPERTY)
annotation class Field(val name: String)

@Table(name = "tbl_contact")
data class Contact(val id: Int, val name: String, @Field(name = "email") val email: String)

fun main(args: Array<String>) {
    // Access class annotations
    val annotations = Contact::class.annotations

    // Access email property annotations
    val emailAnnotations  = Contact::class.memberProperties
            .first { it.name == "email" }
            .annotations
}

Asynchronous Kotlin Programming

https://kotlinexpertise.com/kotlin-coroutines-guide/ https://kotlinlang.org/docs/reference/coroutines.html

  • Threading
  • Async/Await pattern
  • Futures
  • Promises (kinda the JS implementation of futures)
  • Reactive extensions
  • Callbacks (please no)

Kotlin tries not to lock the developer into any of those possiblities for async programming, but by itself it brings something really interesing into the table: couroutines. Some of the listed patterns are implemented by making use of coroutines under the hood.

Coroutines & coroutine implementation

https://github.com/Kotlin/kotlinx.coroutines https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md

NOTE: Not a trivial topic. Must investigate further.

Async and await

async and await aren’t keywords, but rather library functions. This pattern is implemented by means of coroutines.

import kotlinx.coroutines.async
import java.util.concurrent.CompletableFuture

private fun startLongAsyncOperation(v: Int) =
    CompletableFuture.supplyAsync {
        Thread.sleep(1000)
        "Result: $v"
    }

fun main(args: Array<String>) {
    val future = async<String> {
        (1..5).map {
            await(startLongAsyncOperation(it))
        }.joinToString("\n")
    }
    println("Before future")
    println(future.get())
    println("After future")
}

Yield (generators)

Neither a keyword.

import kotlinx.coroutines.generate

fun main(args: Array<String>) {
    val sequence = generate<Int> {
        for (i in 1..5) {
            yield(i)
        }
    }
    println(sequence.joinToString(" "))
}

Reactive extensions

TODO: Investigate further.