Idiomatic Kotlin: Higher-order functions and Function Types

Tompee Balauag
Familiar Android
Published in
3 min readJul 19, 2018
Photo by Miguel Sousa on Unsplash

This article is a part of the Idiomatic Kotlin series. The complete list is at the bottom of the article.

In one of the previous articles, we talked about lambda expressions. This article is somewhat related and we will soon know why.

What is a higher order function?

A higher order function is a function that accepts or return another function. In Kotlin, functions are treated as first class citizens so you can assign them to variables and pass them to other functions as parameters. This can be achieved by using lambda expressions or function references.

Function Types

Kotlin uses function types to represent the type of a function (in contrast to functional interfaces in Java). The syntax of a function type is as follows:

(Type, Type) -> ReturnType

with the left side containing the parameter types and the right side the return type. For empty returns, you can use Unit.

Function types can be instantiated in a multitude of ways. Let’s look at some of them.

  1. Lambda expressions
val filter : (Int) -> Boolean = { it < 2 }

Notice that the filter variable is explicitly defined a type. This is optional if the compiler can infer the type from the lambda expression. As an example, this is equivalent to

val filter = { x : Int -> x < 2 }

2. Anonymous functions

Anonymous functions are function declarations without a name. The above filter function, when converted to an anonymous function would look something like this

val filter = fun(x : Int) : Boolean = x < 2

Just like lambda expressions, some types in the declaration can be inferred, simplifying your code.

3. Existing function

Member functions, top level functions, or extension functions can be used as an instance of a function type. As an example, getting a reference to the String.filter function would be

val filter = String::filter

Any other function declarations, even constructors can be used.

Invoking function type instances

The syntax for invoking function type instances is pretty straightforward. You can use either

f.invoke(x)

or simply

f(x)

Why is there an invoke method in a function type? We’ll know for sure in a while.

Function types under the hood

Function types, surprisingly (or not), is implemented as interfaces under the hood. Let’s take a look at the definition of a generic function type with 1 argument.

Therefore, when you instantiate a function type, you are actually creating an instance of the FunctionN interface with the body of your function being called when the invoke method is called. This approach is similar to Java functional interfaces. Therefore, you can call higher-order functions from Java side by implementing these FunctionN interfaces (or using lambda when targeting Java 8).

The Kotlin standard library defines generic function type of up to 22 parameters, if that is useful for you.

Lambda Cost

Now that you are aware that lambda expressions are actually compiled as anonymous classes under the hood, you might already have an idea regarding the hidden cost of using them.

For every lambda expression defined, there will be a corresponding class definition for it. And if your function is capturing, there will exist an instance as well corresponding to the number of invocation. Luckily for us, there is a feature called inlining that can help with this. Inlining will be the topic of the next article so stay tuned.

Check out the other articles in the idiomatic kotlin series. The sample source code for each article can be found here in Github.

  1. Extension Functions
  2. Sealed Classes
  3. Infix Functions
  4. Class Delegation
  5. Local functions
  6. Object and Singleton
  7. Sequences
  8. Lambdas and SAM constructors
  9. Lambdas with Receiver and DSL
  10. Elvis operator
  11. Property Delegates and Lazy
  12. Higher-order functions and Function Types
  13. Inline functions
  14. Lambdas and Control Flows
  15. Reified Parameters
  16. Noinline and Crossinline
  17. Variance
  18. Annotations and Reflection
  19. Annotation Processor and Code Generation

--

--