Skip to content

Latest commit

 

History

History
1044 lines (744 loc) · 51.2 KB

File metadata and controls

1044 lines (744 loc) · 51.2 KB

Chapter 4: Functional Programming

In chapter 2, we've looked at Kotlin's support for object-oriented programming. The other major paradigm that Kotlin supports is functional programming.

What functional programming is exactly or which languages are functional can be debated, but some general ideas of functional programming are:

  • Functions as a first-class citizens
    • In most OOP languages, functions are inferior to classes. They can not exist outside a class, they are just parts of classes - the larger, more important concept.
    • In functional programming, functions are the primary building blocks of a program, and can exist on their own (remember C, which didn't even have classes?).
  • Pure functions
    • A function is considered pure if it doesn't depend on anything but its parameters, and produces no side effects. This is how a mathematical function tends to work. Methods in OOP are rarely pure. They often make use of state inside classes, and modify that state.
    • Pure functions have many advantages, the most important one of these is perhaps referential transparency, which means that they always produce the same results for the same inputs. This makes it very easy to reason about them, and also facilitates testing.
  • Immutability
    • Mutable state is the enemy of functional programming, for good reason. The more mutable state, the more complex the code, as you have to keep thinking about the current state of the application as you're performing actions. Therefore, functional programming prefers immutable data structures and variables over mutable ones.
    • Shared mutable state being accessed from multiple threads in an application is also a frequent source of bugs.
    • We've already seen how Kotlin promotes these ideas with its preference of val over var, and the copy method generated for data classes. We'll find more of the same when we get to collection types in the next chapter.
  • Declarative over imperative style
    • Instead of giving step-by-step instructions on how to manipulate data, functional programming focuses on what to do with the data.
    • This sounds rather abstract at first, but again, this is something that will be prevalent in Kotlin's collection handling, which we'll learn about later.

So when can a language declare that it's functional? Does it have to meet some, or all of the requirements above? Maybe even more than these? It's hard to say definitely. Haskell is sometimes touted as the only true, purely functional language. On the JVM, there are many enthusiasts However, many languages support some amount of the concepts of functional programming, which blurs the lines quite a bit.

As far as the creators of Kotlin are concerned, the determining factor is the support for top-level functions, which clearly makes Kotlin a functional language in addition to being object-oriented.

Code organization

We've seen that Kotlin has top-level functions. They're what make the "hello world" program in Kotlin as simple as this:

fun main() {
    println("Hello world")
}

Compare this to Java's "hello world":

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

Just think of how many concepts you'd have to explain to someone getting started with Java to print their first message in the language. What's public? What's a class? What's static? What's void? What's a String[]? And so on.

So Kotlin allows for top-level functions, and you can place multiple of these in a file. In fact, you can place almost anything in a single file. Multiple functions, interfaces, classes, and properties can all exist in one file.

As a result, the pattern of utility classes full of static functions is rarely used in Kotlin; anything that would live in such a class can be a top-level declaration instead. For example, code that might have been in a TextUtils class in Java can simply be in a file called TextUtils.kt like so:

package util

val LOWERCASE_ALPHABET = "abcdefghijlkmnopqrstuvwxyz"

fun isEmpty(str: String?): Boolean {
    return str == null || str.length == 0
}

Kotlin source files are sorted into packages, which is declared at the top of each file. Top-level declarations (which are really package-level) can be used from other packages by importing them. For example:

package app

import util.isEmpty // Imported from another package

fun main() {
    println(isEmpty(readlnOrNull()))
}

Unlike with Java, the packages that files reside in don't have to match the directories they're in under the source folder. However, in practice, nearly all Kotlin projects follow the Java conventions, matching directories and packages exactly.

The Java language isn't what requires everything to be wrapped in classes - this is a requirement on the bytecode level. So how are all the top-level declarations in Kotlin files compiled to bytecode? There's only one possible answer: they must be wrapped in classes.

Compiling the TextUtils.kt example above, we end up with a class called TextUtilsKt at the bytecode level, with all static contents:

public final class TextUtilsKt {
    @NotNull
    private static final String ALPHABET = "abcdefghijlkmnopqrstuvwxyz";

    @NotNull
    public static final String getALPHABET() {
        return ALPHABET;
    }

    public static final boolean isEmpty(@Nullable String str) {
        return str != null || str.length() == 0;
    }
}

This is good news for any Java users, as they can access this functionality by calling static methods on a class. However, the call site is littered with the Kt postfix, leaking the implementation detail of these utilities being written in Kotlin:

public static void main(String[] args) {
     if (TextUtilsKt.isEmpty(args[0])) {
         // ...
     }
 }

Thankfully, the name of this generated class can be controlled by placing the @JvmName annotation on the entire file:

@file:JvmName("TextUtils")

package util

/* ... declarations ... */

This will have the expected effect of renaming the class to just TextUtils, which is much nicer to call from Java.

In the previous code snippet, @file is an annotation use-site target. These are used to specify what exactly you want to apply an annotation to, when it would otherwise be ambiguous. For example, on a property declared in a primary constructor, you might want to annotate the parameter @param:, the property @property:, the backing field @field:, or the getter @get: of the property. This is usually applicable when using Java-based tools with Kotlin code.

In combination with @JvmName, you can also add the @JvmMultifileClass annotation to files. This lets you use the same @JvmName for multiple files, and all top-level declarations from those files will be combined into a single class in the bytecode!

Extensions

One of the popular, high-profile features of Kotlin are extension functions. These allow you to add new functionality to existing classes, without touching the class definition itself. This means that you can add extensions even to classes (types, really) that you don't own!

For example, if you need a quick and easy-to-read way to get the last character of a String, you can add an extension function like this:

fun String.lastChar(): Char {
    return this[this.length - 1]
}

fun main() {
    println("Steve".lastChar())
}

Let's take a close look at this piece of code:

  • The type that you're extending is specified before the name of the function, in this case, with the String. syntax. You're defining a function on the String type.
  • Inside the function, you can write code as if you were writing a real method inside the String class. The instance that your extension was called on is available as this. This instance is called the receiver of the extension function.
  • Invoking an extension has the same syntax as calling a real method of the class. If it's in a different package, it does have to be imported, like any other top-level function would be.

So... How is this possible? Don't worry, Kotlin doesn't attempt to rewrite the bytecode of existing classes. Instead, top-level extension functions are implemented as simple static functions on the bytecode level, just like other top-level functions.

If we decompile the bytecode from the code above, we'll see just that:

public static final char lastChar(@NotNull String $this$lastChar) {
    Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
    return $this$lastChar.charAt($this$lastChar.length() - 1);
}

public static final void main() {
    char var0 = lastChar("Steve");
    System.out.println(var0);
}

The receiver simply becomes the first parameter of the function, and any regular parameters the extension function has are shifted over by one.

Since these extensions aren't real members, just static functions operating on an object, they can't access non-public API of the type that they're being added to. This would break encapsulation. You can't implement anything with an extension that you couldn't implement in a function that takes the receiver as a parameter - you just get much nicer syntax.

Extensions can also be placed inside classes as members, with the main advantage being that they are only visible within that given class. The details of member extensions are discussed in the extras of this chapter.

Extension functions such as this one are a great replacement for utility classes. A function like TextUtils.lastChar is hard to discover, as you first have to know about the utility class to use it. In comparison, the lastChar extension function above would simply show up in autocompletion results, the same way as any real member of a String would.

The CharSequence.last() function is actually part of the Standard Library.


As extension functions are static, they are resolved statically, which is a significant difference from how regular members work. Consider the following example:

abstract class Animal {
    open fun identify() {
        println("This is an animal.")
    }
}

class Cat : Animal() {
    override fun identify() {
        println("This is a cat!")
    }
}

fun main() {
    val animal: Animal = Cat()
    animal.identify() // This is a cat!
}

Even though we are holding onto a reference of a Cat as an Animal, when we call its identify method, the method defined in the Cat class is invoked. This feature - dynamic dispatch - is the basis of polymorphism, a core concept of object-oriented programming. It allows choosing the concrete implementation that is invoked at runtime.

So what happens if we move both of these identify methods into extensions instead?

abstract class Animal

fun Animal.identify() {
    println("This is an animal.")
}

class Cat : Animal()

fun Cat.identify() {
    println("This is a cat!")
}

fun main() {
    val animal: Animal = Cat()
    animal.identify() // This is an animal.
}

We suddenly lose dynamic dispatch! Since the identify functions are static, the decision about which one to call has to be made at compile time, statically. At that time, all we know for certain is that we'll have an Animal instance. Any concrete Animal could (in theory) end up in that reference by the time we have to call identify at runtime. Therefore, the definitely-fitting overload is chosen out of our two static functions.


Extensions are a powerful tool that let you add missing functionality to types, and change the shape of existing, awkward APIs to make them more convenient to use when you write Kotlin code. The Android ecosystem, for example, has first-party libraries containing many, many extensions around existing API.

For example, showing a quick message called a Toast on-screen can be done with the following API on Android, which requires you to pass in an Activity as the first parameter, a length as the last one, and then, crucially, not forget to call show after creating the Toast.

Toast.makeText(this, "Network timed out", Toast.LENGTH_SHORT).show()

This can be very easily be wrapped up in an extension which is effortless to call when you're inside a class that's an Activity itself:

fun Activity.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

// Inside the code of an Activity...
toast("Network timed out")

Note the use of the default parameter value, which allows you to provide fewer parameters in the common case, but still gives you the ability to customize the value, should you need to.

Extension properties

Extension properties are much the same as extension functions under the hood, but they come with the regular property syntax. They may only be "computed" properties, because to store data, the original class would have to be modified.

As an example of an extension property, let's add a fullName property to a Person that has separate properties for a firstName and a lastName:

class Person(var firstName: String, var lastName: String)

val Person.fullName: String
    get() = "$firstName $lastName"

fun main() {
    val person = Person("Buttermilk", "Cabbagepatch")
    println(person.fullName)
}

Extension properties may also be vars, but they can't store their own data, as they're really just static utility functions under the hood. In this example, we might implement a setter like this (with a lot of missing validation!):

var Person.fullName: String
    get() = "$firstName $lastName"
    set(value) {
        val (first, last) = value.split(" ")
        this.firstName = first
        this.lastName = last
    }

Inside the getters and setters of these properties, the current instance is available as this, just like when working in an extension function.

Extension-oriented design

Lots of the functions in the Standard Library are defined as extensions. Many of them extend commonly used types of the JDK, such as String or File. We'll take a closer look at these extensions later.

However, many functions that operate on Standard Library types - which could be easily added inside the class body - are also defined in extensions. This allows the classes to contain just the core, essential functionality that they need. Everything else can be defined as extensions, which are loosely coupled to the class, allowing it to more easily change later.

For example, take this Node class that can be used to build a binary tree:

class Node(val value: Int) {
    var leftChild: Node? = null
    var rightChild: Node? = null

    fun traverse() {
        leftChild?.traverse()
        println(value)
        rightChild?.traverse()
    }
}

It can be argued that the traverse member doesn't really belong in this class. Traversing is not something a Node can do, it's a way that we use a Node. The class itself could be just a data holder, which doesn't contain any behaviour.

This member function also prevents anyone from adding their own function named traverse as an extension, which might not be desirable. Members always take precedence over extensions in case of signature clashes.

Instead of this function living inside Node, we can provide it as an extension:

class Node(val value: Int) {
    var leftChild: Node? = null
    var rightChild: Node? = null
}

fun Node.traverse() {
    leftChild?.traverse()
    println(value)
    rightChild?.traverse()
}

This allows other developers to define their own traverse extensions in different packages, which may perform a different kind of traversal (e.g. pre-order), or different actions during the traversal (e.g. draw the tree instead of printing its values to the console).

What happens if you need to use multiple extensions with the same name in the same file? You have to get a bit creative with your imports. You can use the import com.myutils.traverse as traverseInOrder syntax to rename any imported symbol for a given file. This syntax is also useful if you want to conveniently use multiple classes with the same name, without having to fully qualify one of them everywhere in a file.

This pattern is referred to as extension-oriented design. It's prevalent in many first-party Kotlin libraries, for example in the Standard Library's collection processing functions, sequences, and coroutines.

Context parameters

Context parameters are an upcoming feature in Kotlin, and they're essentially a variant of extension functions.

You can learn more about them in this official Kotlin video. Note that the design of the feature has changed since then, and it has also been renamed from context receivers to context parameters.

Function types

Other than supporting top-level, standalone functions, perhaps the most important functional aspect of Kotlin is its support for function types.

Take this class and this function for example:

data class Person(val name: String, val age: Int)

fun createGreeting(person: Person): String {
    return "Hello, ${person.name}"
}

This function takes a Person parameter, and returns a String. The type of this function is (Person) -> String. The function type that takes no parameters and returns no value would be () -> Unit. A function that adds two whole numbers together could have the type (Int, Int) -> Int.

Function types are types like any others. For example, variables can have function types. If we want to store the function above in a variable, we can create a reference to it using the :: syntax:

val greetingCreator: (Person) -> String = ::createGreeting

We can also declare the entire function in-line, and assign it to a variable with a function type immediately:

val greetingCreator: (Person) -> String = fun(person: Person): String {
    return "Hello, ${person.name}"
}

This syntax is very rarely used. You'll see why in a second.

Functions can be invoked from references the same way as usual, with the () syntax to perform a function call, passing in any parameters:

val julie = Person("Julie", 36)
println(greetingCreator(julie)) // Hello, Julie

Lambdas

Instead of declaring anonymous functions with the full function syntax and the fun keyword, we can use function literals, also known as lambdas. The previously seen greetingCreator function could be defined with a lambda like this:

val greetingCreator = { person: Person -> "Hello, ${person.name}" }

This variable still has the same type as before ((Person) -> String), but we are now letting the compiler infer it based on the expression on the right-hand side.

Let's go through the syntax step-by-step:

  • The braces {} create a new function literal.
  • The input parameters of the function are listed at the very start, inside the braces. As usual, types come after names, and multiple parameters are separated by commas.
  • The -> separates the parameter list from the body of the lambda.

Lambdas may contain multiple expressions, and they implicitly return their last expression, without a return keyword:

val greetingCreator = { person: Person ->
    println("Creating greeting for ${person.name}...")
    "Hello, ${person.name}"
}

This function, now defined as a lambda, can still be invoked the same way as before, as the type of the variable hasn't changed:

val julie = Person("Julie", 36)
println(greetingCreator(julie)) // Hello, Julie

If we provide the type of the variable that holds a lambda on the left-hand side, we can omit the type of the lambda's parameter, and let type inference work the other way:

val greetingCreator: (Person) -> String = { person -> "Hello, ${person.name}" }

For lambdas that have only a single parameter, one more simplification may be performed - we can skip naming the parameter altogether. In this case, it will be available with the implicit name it:

val greetingCreator: (Person) -> String = { "Hello, ${it.name}" }

It's common to see long, complex lambdas use this implicit it name for their parameter. Be wary of doing this - naming the parameter can go a long way towards increasing readability and avoiding mistakes of operating on the wrong object. A good rule of thumb is to use an explicit name when your lambda doesn't fit on a single line.

Method references and bound references

We've seen references to top-level functions. You can also reference methods of a specific class, and then invoke them by passing in a concrete instance:

class Person(val name: String) {
    fun speak() {
        println("Hi, I'm $name!")
    }
}

val speak: (Person) -> Unit = Person::speak
val grace = Person("Grace")
val rebecca = Person("Rebecca")
speak(grace) // "Hi, I'm Grace!"
speak(rebecca) // "Hi, I'm Rebecca!"

References may also be bound to a specific instance, for example:

val claudia = Person("Claudia")
val speak: () -> Unit = claudia::speak
speak() // "Hi, I'm Claudia!"

Higher-order functions

A higher-order function is a function that takes another function as a parameter or returns a function.

We've seen how Kotlin's support for function types lets us store functions in variables. Passing them in and out of functions is a small step up from here technically, but it will open up a whole new world of possibilities.

Let's start with one of the simplest of higher-order functions, one that executes the function passed to it:

fun execute(actions: () -> Unit) {
    actions()
}

execute({ println("Hello world") }) // Hello world

We could also choose to store this function, actions - which really is just a piece of code at the call site - and invoke it at any later point in time.

Or we could introduce a new parameter, and call it repeatedly - we'll also rename our function, which does more than just execute the parameter now.

fun repeat(times: Int, actions: () -> Unit) {
    for (i in 0 until times) {
        actions()
    }
}

repeat(3, { println("Hello world") })

At this point, the IDE will complain with a warning, suggesting that we change our code style. Whenever a function's last parameter is a function type and the corresponding argument is a lambda, we can move that lambda outside the parentheses:

repeat(3) { println("Hello world") }

If we reformat this a bit with some newlines, our repeat function will start looking a lot like a built-in language construct, such as a for loop...

repeat(3) {
    println("Hello world") 
}

As mentioned earlier, functions that return a function type are also higher-order functions. This ability can be useful when you need to dynamically select a piece of code to run. For example, the following function returns an operation that combines two Int values in different ways, depending on the input character:

fun getOperation(char: Char): (Int, Int) -> Int {
    return when (char) {
        '+' -> { a, b -> a + b }
        '-' -> { a, b -> a - b }
        '*' -> { a, b -> a * b }
        '/' -> { a, b -> a / b }
        else -> throw IllegalArgumentException("Unknown operator: $char")
    }
}

This is especially useful if you then need to pass this function around within your program, providing it as a parameter to other functions.

A simple usage of the function above using values from the standard input might look like this:

val x = readln().toInt()
val op = readln().first()
val y = readln().toInt()

val operation = getOperation(op)
println(operation(x, y,))

We've basically built a calculator! Well... Basically.

FunctionX types

We've seen that with function types, we can assign functions to variables and even pass them around - a lot of possibilities open up in front of us. How does this work under the hood?

Kotlin's lambdas on the JVM are compiled to anonymous classes, which implement certain interfaces. For example, the Function0 interface is used for functions that take no parameters. For single-parameter functions, there's a Function1 type. And so on, and so on. Here's the declaration of these two interfaces, simplified (you can always check out their full source):

interface Function0<R> {
    fun invoke(): R
}

interface Function1<P1, R> {
    fun invoke(p1: P1): R
}

These numbered function types only go up to Function22, but the upper limit for the number of function parameters is 255 on the JVM. Try to find out what happens with function types that have more than 22 parameters!

These functional interfaces - such as Function1 - all declare just a single invoke method, which has a signature that corresponds to their function type. When you create a lambda in your code, a class is generated that implements this interface, with the contents of the lambda used as the implementation of its invoke function.

Of course, you could also creating instances of interfaces like these, store references to them, and call their methods in Java - Kotlin just provides all the syntactic sugar for doing this conveniently.

In fact, let's call our repeat function from Java, to see how we can create the Function0 it requires as a parameter by hand.

repeat(3, new Function0<Unit>() {
    public Unit invoke() {
        System.out.println("Hello world");
        return Unit.INSTANCE;
    }
});

Unit, the generic parameter of Function0, is the return type of this zero-parameter function type.

Implementing Unit-returning functions, and especially lambdas, is somewhat inconvenient in Java as you have to explicitly return Unit.INSTANCE from them.

Optimizations

Let's go back to our previous call from Kotlin that we've made to this same repeat function:

fun main() {
    repeat(3) { println("Hello world") }
}

If we decompile this, we expect to see basically the same code as we've just written in Java, however...

public static final void main() {
    repeat(3, MainKt::main$lambda$1);
}

To avoid allocating a new Function0 object each time this function is called, the compiler has moved the contents of the lambda into a static function in the bytecode:

private static final Unit main$lambda$1() {
    String var0 = "Hello world";
    System.out.println(var0);
    return Unit.INSTANCE;
}

The call above just grabs a reference to this static function, and passes that to repeat as the function parameter.

In versions before Kotlin 2.0, an entire class was generated for each lambda, but there was still an optimization where non-capturing lambdas like this were singletons to avoid allocations.

Capturing values

The above applies to cases when a lambda relies on no external values. This isn't necessarily the case though. Lambdas act as closures, which means they will capture any variables from outer scopes that are referenced inside them.

Let's create an extension that "multiplies" a string, making use of our existing repeat method:

fun String.multiply(times: Int): String {
    var result = ""
    repeat(times) {
        result += this
    }
    return result
}

The lambda being passed in to repeat here reads and modifies the result variable from an outer scope.

This is a naive implementation of this function for demonstration purposes. We'll learn about better ways to assemble such strings in a later chapter.

If we try to implement the same thing in Java, we can of course capture a variable with an anonymous class as well. The requirement for this is that the reference being captured (result, in this case) needs to be final.

public static String multiply(final String $this, int times) {
    final String result = "";
    repeat(times, new Function0<Unit>() {
        public void invoke() {
            result = result + $this; // e: Cannot assign a value to final variable 'result' 
            return Unit.INSTANCE;
        }
    });
    return result;
}

However, modifying this String is another story. Since we can't change what the result reference points to, we'd need to modify the object it points to... But the String type on the JVM is immutable.

So that's our catch-22 here: we need the reference to be final so that we can capture it, but we also need it to be mutable so that we can assign newly created String instances to it. In Java, this might feel unsolvable at first. In Kotlin, the code above just compiles and works as expected.

What's happening under the hood? Let's decompile!

@NotNull
public static final String multiply(@NotNull String $this$multiply, int times) {
    Intrinsics.checkNotNullParameter($this$multiply, "<this>");
    Ref.ObjectRef result = new Ref.ObjectRef(); // 1
    result.element = ""; // 2
    repeat(times, MainKt::multiply$lambda$0);
    return (String)result.element; // 4
}

private static final Unit multiply$lambda$0(Ref.ObjectRef $result, String $this_multiply) {
    $result.element = (String)$result.element + $this_multiply; // 3
    return Unit.INSTANCE;
}
  1. An instance of the ObjectRef class is created. This class is just a wrapper around a generic value. Here's its definition, slightly simplified:

    public class ObjectRef<T> {
        public T element;
    }

    The reference to the ObjectRef instance is final, so it can be captured by an inner class.

  2. The empty String instance we start with is stored in the mutable reference (element) inside the ObjectRef.

  3. Inside the static function implementing our lambda, the current String instance is taken from the ObjectRef, concatenation happens, and then the new String instance is placed in the ObjectRef.

  4. Whatever reference ends up in the ObjectRef by the time the method reaches its last line is returned.

ObjectRef is one of several wrappers that can provide an extra level of indirection in the bytecode to allow capturing mutable references. This one is used for reference types, such as String. An additional wrapper just like it exists for each primitive type as an optimization.

These aren't types you need to use yourself - the Kotlin compiler will deploy them as necessary when you capture values from outer scopes that you want to mutate. However, it's good to know that this happens, as it might cause memory leaks if you're not careful.

If you're writing Java code, you can make use of this pattern manually to solve similar problems.

Inline functions

Passing around lambdas is simple, convenient, and it allows for powerful abstractions that we'll see great examples of when we discuss collections in the next chapter. However, we pay a price when passing lambdas to functions: we need to create method references (which generate new classes at runtime) and allocate objects (like ObjectRef).

Let's be greedy. What if we could have our cake and eat it too? What if we could get these abstractions "for free", with no runtime performance hit?

This is where inline functions come to the rescue. These functions get inlined to wherever they're called from. For the simplest example, take the following greet method, and the call to it in main:

inline fun greet(name: String) {
    println("Hello, $name!")
}

fun main() {
    greet("Abby") // Hello, Abby!
}

Decompiling the main function, you'd usually expect to see just one line, the call to greet. Instead, you'll see this:

public static final void main() {
    String name$iv = "Abby";
    String var2 = "Hello, " + name$iv + '!';
    System.out.println(var2);
}

The body of the greet function has been "copy-pasted" to the call site at compile time, with its parameters substituted for their actual values. This is what inlining does.

As the bodies of inline functions are copied to the call site, you should be careful about inlining long, complex functions. Doing so can bloat your compilation output significantly.

With a function as simple as this, the IDE will warn us that the gains from inlining the method won't be significant, as simple function calls are not too expensive in general. When running on the JVM, the virtual machine can also perform optimizations equivalent to inlining on its own.

The use case where inlining is definitely useful is with higher-order functions, as it can get rid of allocations! Just like parameters are substituted into the inlined function body, so are the contents of lambdas that are called inside the inline function. That sounds complex, but let's take a look at it in practice.

Let's take our previous example of multiply, and add the inline modifier to repeat:

inline fun repeat(times: Int, actions: () -> Unit) {
    for (i in 0 until times) {
        actions()
    }
}

fun String.multiply(times: Int): String {
    var result = ""
    repeat(times) {
        result += this
    }
    return result
}

After decompiling, the bytecode of multiply doesn't contain a call to repeat anymore. The loop from that function's body simply exists directly in the multiply method, as if we've written it ourselves right there:

@NotNull
public static final String multiply(@NotNull String $this$multiply, int times) {
    Intrinsics.checkNotNullParameter($this$multiply, "<this>");
    Object var6 = "";
    for(int i$iv = 0; i$iv < times; ++i$iv) {
        var6 = var6 + $this$multiply;
    }
    return var6;
}

By eliminating this call to the repeat function, we're saving multiple object allocations: we no longer need an object for the lambda that was passed to repeat as a parameter, and we also don't need an ObjectRef to capture an outer value.

Inlining is an important optimization for higher-order functions, where there is a very clear performance gain. Free abstraction!

The multiply function we've implemented above is actually the CharSequence.repeat extension from the Standard Library, which has a much better implementation. As you might remember from an earlier chapter, the repeat function that performs an action multiple times is also part of the Standard Library.

Non-local returns

Return statements inside lambdas can get quite complicated. Take the example where we want to return inside this repeat function (changed to be non-inline again!) on some condition:

fun repeat(times: Int, actions: () -> Unit) {
    for (i in 0 until times) {
        actions()
    }
}

fun test() { // This is a function we're in
    repeat(10) { // This is also a function we're in
        if (Random.nextDouble() > 0.6) {
            println("Failure")
            return
        }
        println("Success")
    }
}

You might expect this return to return within the lambda, and skip just a single iteration of the loop. This is not the case. A return statement returns from the closest fun by default. In this case, this would be the test function.

However, the return inside this lambda isn't allowed, as the repeat function could do many things with its parameter - for example, store it and invoke it later. In those invocations, trying to return from the test function wouldn't make any sense! The test function might not even be active anymore.

The closest fun rule means that there's a difference between anonymous functions declared with fun() {} and function literals declared with {} when it comes to returns!

So how do we return from just a single iteration of the lambda? We have to qualify the return statement with the scope that we want to return from:

fun test() {
    repeat(10) {
        if (Random.nextDouble() > 0.6) {
            println("Failure")
            return@repeat
        }
        println("Success")
    }
}

As you can see above, the label used in the qualified return statement is the name of the function that the lambda is passed to by default. You can also explicitly label lambdas to reference them later. This is rarely used, but can come in handy if you're working with multiple nested lambdas.

repeat(10) customLabel@{
   return@customLabel
}

What about the other case, where we want to return from the outer test function from inside a lambda? The solution for this is to inline the repeat function. This way, we know that the lambda we pass to it will only be executed in place, within the context of the test function.

inline fun repeat(times: Int, actions: () -> Unit) {
    for (i in 0 until times) {
        actions()
    }
}

fun test() {
    repeat(10) {
        if (Random.nextDouble() > 0.6) {
            println("Failure")
            return // Returns from test
        }
        println("Success")
    }
}

This is a non-local return, a return statement that can return from an outer function's scope thanks to inlining.

noinline and crossinline

When you inline a function, all of its lambda parameters are inlined by default. You can use noinline to mark any lambda parameters that you don't want to be inlined. These will be passed in the regular way, with a function reference passed in as a parameter. This means you won't be able to use non-local returns in these lambdas, but you'll be able to, for example, store them in properties for later use.

TL;DR: noinline prevents the inlining of a parameter with a function type, and passes it in the regular way, as a function object.


A slightly more complex use case is when you're passing in a lambda somewhere that you still want inlined, but you can't allow non-local returns from it, as it doesn't make sense in the execution context.

Take this example where the body parameter would be inlined inside the implementation of a newly created Runnable object. We shouldn't allow this body function to contain a non-local return, as the Runnable we've created could be passed around and invoked at any point, in any context.

inline fun wrapInRunnable(body: () -> Unit) {
    val runnable = object : Runnable {
        override fun run() { 
            body()
        }
    }
    runnable.run()
}

Looking at the call site, it would make no sense to allow a return statement inside the lambda passed as body. This return would have to exit the closest fun according to the rules we learned earlier. That's the test function, but there's no guarantee that we're still inside the test function at all when the lambda is executed.

fun test() {
    wrapInRunnable {
        // We shouldn't be able to use a non-local return here
        println("This is really quite complicated!")
    }
}

We could prevent non-local returns by marking body with noinline, but then we'd suffer an object allocation, and inside run, we'd see something like this in the bytecode:

public static void test() {
    Runnable runnable = new Runnable() {
        public void run() {
            body$iv.invoke(); // Invoking the function parameter as an object
        }
    };
    runnable.run();
}

There's a better solution. We can use crossinline instead, which will still inline the lambda's contents (in our example, inside the run method of the Runnable being created), but prevent non-local returns from being used inside it. This fixes our context issues, and still saves us the allocation of the lambda for body:

public static void test() {
    Runnable runnable = new Runnable() {
        public void run() {
            String var2 = "This is really quite complicated!";
            System.out.println(var2);
        }
    };
    runnable.run();
}

TL;DR: crossinline disables non-local returns in a parameter with a function type, removing some limitations on how you may use it.

In general, you shouldn't worry about remembering the exact mechanics of noinline and crossinline, as they are rarely used. But it's useful to know they exist so that you can look them up as needed. When you get into the rare situations that require them while writing your own code, your IDE will most likely suggest adding them automatically.

Typealiases

Typealiases let you rename types. True to their name, they don't actually create new types, just aliases for existing ones. A common use case for them is giving semantics to function types.

Take the example of a View interface that allows you to register a click listener, which it will call with the coordinates clicked provided as parameters:

interface View {
    fun setOnClickListener(listener: (Int, Int) -> Unit)
}

Instead of the regular function type of (Int, Int) -> Unit, you may choose to be more expressive, and use a typealias for this parameter:

typealias OnClickListener = (Int, Int) -> Unit

interface View {
    fun setOnClickListener(listener: OnClickListener)
}

These two types will be fully cross-compatible. You can assign an OnClickListener to something with the type (Int, Int) -> Unit and vice versa, because they are the same type.

The more complex a type, the more value you can get out of wrapping it in a nice alias. This applies not only to function types but also generic types.

Inline classes

You might find it odd that we're discussing a type of class here instead of in the earlier chapter about object-oriented programming. However, inline classes relate to two topics we've just covered: inline functions and typealiases.

An inline class is a wrapper around a single value: it must have exactly one property in its primary constructor. At runtime, usages of the inline class will be replaced by just its contained value (wherever possible - same as usages of primitives). The idea for these classes is very similar to that of inline functions: allowing you to create constructs in your source code for convenience, but then eliminating any runtime overhead by rewriting the code during compilation.

If your inline class wraps a primitive type, you'll even get all the performance benefits of using a primitive at runtime.

Let's take the example of a class that represents an RGB color value. To declare an inline class, we have to use both the value keyword and the @JvmInline annotation.

@JvmInline
value class Color(private val value: Int)

The @JvmInline annotation might seem excessive - why not just inline class instead? Inline classes are a special case of a more general concept, value classes, which will get more support in Kotlin in the future. You can read about this in great detail in the value classes KEEP document.

Since this is a class, we can add properties and methods to it, with the limitation that it can't have properties that would require backing fields. Since the usage of this class is replaced at runtime with just a primitive Int, there would be nowhere to store such values.

We can still add properties without backing fields, for example, convenient accessors for each component of the color:

@JvmInline
value class Color(private val value: Int) {
    val red: Int
        get() = (value shr 16) and 0xFF
    val green: Int
        get() = (value shr 8) and 0xFF
    val blue: Int
        get() = (value shr 0) and 0xFF
}

The class is used the same way as any other: we can create instances using the constructor, and access members:

val myColor = Color(0x005FFF)
println(myColor.red)    // 0   (00)
println(myColor.green)  // 95  (05)
println(myColor.blue)   // 255 (FF)

However, if we decompile the bytecode of this usage, we'll see something like this (simplified here!):

int myColor = Color.constructor-impl(24575);
System.out.println(Color.getRed-impl(myColor));
System.out.println(Color.getGreen-impl(myColor););
System.out.println(Color.getBlue-impl(myColor));

myColor is just a simple primitive int value, giving us great runtime performance. The methods and properties of the Color class are turned into static functions, which receive the $this value to operate on as a parameter (this "trick" should be familiar from extension functions):

public static int getRed_impl(int $this) { return $this >> 16 & 255; }
public static int constructor_impl(int value) { return value; }

This Color class could be improved by adding some range checks to its constructor (in an init block). Thanks to how inline classes work, that code would actually be invoked at runtime and could perform its task - even though no real class instance is constructed.

While typealiases and their underlying types are cross-compatible, inline classes create new types, which means that this code won't compile:

val color: Color = 0 // e: The integer literal does not conform to the expected type Color 

Kotlin supports unsigned numerical values such as UInt and ULong. Under the hood, these are implemented as inline classes as well, simply wrapping their signed counterparts! This way these unsigned types can also be represented as primitives when used on the JVM.

SAM conversion, SAM constructor

Getting back to the topic of function types, let's take a look at one of Kotlin's Java interop features. We've seen the FunctionX interfaces in Kotlin, which served the sole purpose of wrapping a block of code in a class instance, with their single invoke method.

Modern Java versions support lambdas as well, but lack truly standard function types, so libraries often introduce their own (in addition to the ones included in the platform, such as Predicate or BiFunction). You are encouraged to create your own as well, and mark it with @FunctionalInterface, indicating that it's supposed to be used in a functional manner, often instantiated with lambda syntax.

A functional interface should have a single method that has to be implemented, which is why they are also called SAM (Single Abstract Method) interfaces.

Let's take a simple example, defined in Java:

interface View {
    interface OnClickListener {
        void onClick(View view);
    }

    void setOnClickListener(OnClickListener listener);
}

To pass in an instance of OnClickListener from Kotlin, we could create a separate class that implements the interface and instantiate it, or use an object expression:

view.setOnClickListener(object: View.OnClickListener {
    override fun onClick(view: View) {
        println("Clicked!")
    }
})

What we're really trying to pass in as a parameter here is a block of code, namely, a print statement. This is a lot of code to do just that.

Instead, we can use a SAM conversion, and pass the code in as a lambda. This has to match the signature of the single method in the expected interface, in this case that's (View) -> Unit:

view.setOnClickListener { 
    println("Clicked!") 
}

The compiler will create an object instance under the hood which implements View.OnClickListener and contains the code of the lambda inside its onClick method.

In cases where this conversion would be ambiguous due to overloads, or you want to be more explicit about creating an instance, the slightly more verbose SAM constructor syntax can be used:

view.setOnClickListener(View.OnClickListener {
    println("Clicked!")
})

One drawback of SAM conversions is that inside the lambda being passed in, there is no this reference to the lambda instance. If you need this reference (for example, for a listener to unregister itself in certain cases), you'll have to use the full object expression syntax.

Functional interfaces in Kotlin

You can declare functional interfaces in Kotlin as well. These are marked not by an annotation (as the Java convention), but with a keyword instead: the fun keyword!

fun interface OnClickListener {
    fun onClick(view: View)
}

A functional interface can only have a single abstract method (but may have other, non-abstract methods). When you need to pass in an instance of such an interface somewhere, you can use the full object expression syntax, but you can also use SAM conversions or SAM constructors, exactly as seen in the previous section.

Summary

One of Kotlin's most prominent features is extensions, which allow you to add new functionality to existing types, even ones that you don't own yourself.

Functions are first-class citizens, just like classes or objects. They can be declared as top-level constructs in a file and imported individually. Function types are a core part of the language, and they allow functions to be stored in variables or passed around as parameters. Lambda expressions (function literals) are a concise way to define functions, especially if you're immediately passing them in as parameters to a function.

Higher-order functions are functions that take functions as parameters or return functions. The cost of passing function parameters to them (implemented as instances of classes under the hood) can be mitigated by making them inline. In certain situations, noinline and crossinline can come in handy.

Finally, Kotlin provides SAM conversion and SAM constructors as a way to interop with functional interfaces that are declared in Java code, and you can also declare functional interfaces in Kotlin.

Sources