Chapters

Hide chapters

Functional Programming in Kotlin by Tutorials

First Edition · Android 12 · Kotlin 1.6 · IntelliJ IDEA 2022

Section I: Functional Programming Fundamentals

Section 1: 8 chapters
Show chapters Hide chapters

Appendix

Section 4: 13 chapters
Show chapters Hide chapters

H. Appendix H: Chapter 8 Exercise & Challenge Solutions
Written by Massimo Carli

Exercise 8.1

In this chapter, you implemented the generic curry function that basically maps a function of type (A, B) -> C in a function of type (A) -> (B) -> C. Can you now implement the uncurry function, which does the inverse? It’s a function that maps a function of type (A) -> (B) -> C in a function of type (A, B) -> C.

Exercise 8.1 solution

The implementation of the uncurry function is:

fun <A, B, C> ((A) -> (B) -> C).uncurry(): (A, B) -> C =
  { a: A, b: B ->
    this(a)(b)
  }

This is an extension function of the (A) -> (B) -> C type that returns a function of two input parameters, a and b, of type A and B, respectively. In the body, you just invoke the receiver function first with a and then the resulting function with b.

It’s interesting to note how, if you apply uncurry to the curry version of a function, you get the function itself. To prove this, run the following code:

fun main() {
  val sum = { a: Int, b: Int -> a + b }
  println(sum(2, 3))
  val sum2 = sum.curry().uncurry()
  println(sum2(2, 3))
}

Which gives you:

5
5

Exercise 8.2

Implement a higher-order function flip that maps a function of type (A, B) -> C in the function (B, A) -> C, flipping the order of the input parameters.

Exercise 8.2 solution

The flip function is very interesting and useful. Given you already have curry and uncurry, you can implement flip like this:

fun <A, B, C> ((A, B) -> C).flip(): (B, A) -> C =
  { b: B, a: A ->
    this(a, b)
  }

As you can see:

  • flip is an extension function on the type (A, B) -> C.
  • The return type is (B, A) -> C.
  • It returns a function of the parameters b and a of types B and A, respectively.
  • In the body, you just invoke the receiver, passing the parameters in the right order.

As a first example of the usage of this function, you can use the following:

fun append(a: String, b: String): String = "$a $b"

Now, run this:

fun main() {
  val flippedAppend = ::append.flip() // 1
  println(append("First", "Second")) // 2
  println(flippedAppend("First", "Second")) // 3
}

In this code, you:

  1. Define flippedAppend as the function you get by invoking flip on ::append.
  2. Print the result of append, passing "First" and "Second" as input values.
  3. Print the result of flippedAppend with the same parameters in the same order.

You’ll get:

First Second
Second First

Which proves flip is working.

Sometimes, using flip with curry is useful. Consider, for instance, the following function:

fun runDelayed(fn: () -> Unit, delay: Long) { // 1
  sleep(delay) // 2
  fn() // 3
}

In this code, you:

  1. Define runDelayed as a function with two input parameters. The first is a lambda of type () -> Unit and the second is a Long that represents the time you have to wait before invoking the previous lambda.
  2. sleep for the delay time.
  3. Invoke fn.

To use this code, just run:

fun main() {
  // ...
  runDelayed({
    println("Delayed")
  }, 1000)
}

You’ll see the program wait one second and then print Delayed. This is code you can improve because you pass the lambda expression as the first parameter and the interval as the second. Of course, you could use named parameters, but you can do something better instead.

Just run the following code:

fun main() {
  // ...
  val runDelayed1Second =
    ::runDelayed.flip() // 1
      .curry() // 2
      .invoke(1000L) // 3
  runDelayed1Second { // 4
    println("Delayed")
  }
}

In this code, you:

  1. Define runDelayed1Second as a function that allows you to run a given lambda after a one-second delay. First, you invoke flip, getting a function with Long as the first parameter and the lambda as the second.
  2. Invoke curry(), getting a function of type (Long) -> (() -> Unit) -> Unit.
  3. Invoke the curried function with 1000L as an input parameter for the delay. This makes (() -> Unit) -> Unit the type of runDelayed1Second.
  4. Use runDelayed1Second, passing the lambda expression you want to run, but delayed by 1 second.

Using composition in this way makes the code more reusable and simpler to write.

Exercise 8.3

The curry function maps a function of type Fun2<A, B, C> to a function of type (A) -> (B) -> C. How would you define an overload of curry for functions of three, four, five or, in general, n parameters?

Exercise 8.3 solution

To make the code easier to read, start by writing a typealias for each function type with a specified number of parameters, from 3 until 5 like this:

typealias Fun3<I1, I2, I3, O> = (I1, I2, I3) -> O
typealias Fun4<I1, I2, I3, I4, O> = (I1, I2, I3, I4) -> O
typealias Fun5<I1, I2, I3, I4, I5, O> =
  (I1, I2, I3, I4, I5) -> O

You can also do the same for the output types for the curry functions, like this:

typealias Chain3<I1, I2, I3, O> = (I1) -> (I2) -> (I3) -> O
typealias Chain4<I1, I2, I3, I4, O> =
  (I1) -> (I2) -> (I3) -> (I4) -> O
typealias Chain5<I1, I2, I3, I4, I5, O> =
  (I1) -> (I2) -> (I3) -> (I4) -> (I5) -> O

In the case of three parameters, you can then write the following curry overload:

fun <I1, I2, I3, O> Fun3<I1, I2, I3, O>.curry():
    Chain3<I1, I2, I3, O> = { i1: I1, i2: I2 ->
  { i3: I3 ->
    this(i1, i2, i3)
  }
}.curry()

How can you consider a function with three parameters as a function with two parameters returning another function? Basically, you consider the type:

(I1, I2, I3) -> O

As the following:

(I1, I2) -> ((I3) -> O)

This allows you to reuse the curry overload you implemented for N-1 parameters for a function of N parameters. Now, you can do the same for functions with four and five parameters, like this:

fun <I1, I2, I3, I4, O> Fun4<I1, I2, I3, I4, O>.curry():
    Chain4<I1, I2, I3, I4, O> = { i1: I1, i2: I2, i3: I3 ->
  { i4: I4 ->
    this(i1, i2, i3, i4)
  }
}.curry()

And:

fun <I1, I2, I3, I4, I5, O>
    Fun5<I1, I2, I3, I4, I5, O>.curry():
    Chain5<I1, I2, I3, I4, I5, O> =
  { i1: I1, i2: I2, i3: I3, i4: I4 ->
    { i5: I5 ->
      this(i1, i2, i3, i4, i5)
    }
  }.curry()

As an example, run the following code:

fun main() {
  val sum = { a: Int, b: Int, c: Int, d: Int, e: Int ->
    a + b + c + d + e // 1
  }
  val curriedSum = sum.curry() // 2
  println(curriedSum(1)(2)(3)(4)(5)) // 3
}

In this code, you:

  1. Implement a simple function, sum, that calculates the sum of the five input parameters.
  2. Define curriedSum, invoking curry on sum. The type of curriedSum is Chain5<I1, I2, I3, I4, I5, O>.
  3. Invoke curriedSum and print the result. Note how you pass the input parameters using ().

Of course, you’ll get the result:

15

In the previous example, you met the expression:

curriedSum(1)(2)(3)(4)(5)

As stated already, functional programmers don’t like parentheses and try, whenever possible, to avoid them. You also already met the pipe function. One possible option might be this:

fun main() {
  val sum = { a: Int, b: Int, c: Int, d: Int, e: Int ->
    a + b + c + d + e
  }
  val curriedSum = sum.curry()
  val result = 5 pipe 4 pipe 3 pipe 2 pipe 1 pipe curriedSum // HERE
  println(result)
  println(curriedSum(1)(2)(3)(4)(5))
}

Unfortunately, this code doesn’t compile. The reason is the associativity priority between the pipe infix functions, which is left to right. This means that the compiler tries to execute 5 pipe 4 first, which doesn’t exist.

To use pipe, you need to use parentheses in another way, like this:

val result = 5 pipe (4 pipe (3 pipe (2 pipe (1 pipe curriedSum))))

You basically just moved the same parentheses to another place. There’s a trick, though. Simply add the following code:

infix fun <A, B> Fun<A, B>.epip(a: A): B = this(a)

The epip function is basically the pipe reversed, but it allows you to completely remove parentheses. Just replace the previous code with the following:

fun main() {
  val sum = { a: Int, b: Int, c: Int, d: Int, e: Int ->
    a + b + c + d + e
  }
  val curriedSum = sum.curry()
  val result = curriedSum epip 1 epip 2 epip 3 epip 4 epip 5 // HERE
  println(result)
}

And everything will be fine!

Feel free to play with these curry overloads and the flip function you implemented in this exercise to change the order of your functions as you like.

Exercise 8.4

How would you apply the previous pattern for Array<T>? Basically, you need a way to compose functions of type:

 typealias ToArray<A, B> = (A) -> Array<B>

In other words, if you have two functions:

val fun1: (A) -> Array<B>
val fun2: (C) -> Array<C>

Can you implement compose so that the following will compile and fun2 is applied to all elements resulting from fun1?

fun1 compose fun2

Exercise 8.4 solution

First, you need to understand what composing functions of type ToArray<A, B> means. The first is a function receiving an input value of type A and returning an Array<B>. The second receives an input of type B and returns an Array<C>.

The composition should then be something that gets an Array<B> from the first function and applies the second function to all the elements. A possible implementation is:

inline infix fun <A, B, reified C> ToArray<A, B>.compose(
  crossinline g: ToArray<B, C> // 1
): ToArray<A, C> = { a: A -> // 2
  val bArray = this(a) // 3
  val cArray = mutableListOf<C>() // 4
  for (bValue in bArray) {
    cArray.addAll(g(bValue))
  }
  cArray.toTypedArray() // 5
}

In this code, you:

  1. Define compose as an infix extension function of the ToArray<A, B> type, accepting an input parameter of type ToArray<B, C>. Of course, the return type is ToArray<A, C>.
  2. Return a function of the input parameter a of type A.
  3. Invoke the received on a getting an Array<B> you save in bArray.
  4. Create a MutableList<C> you fill with the values you get by invoking g on each element of bArray.
  5. Return the Array<C> version of MutableList<C>. This is the reason the type C requires reified.

Now you can create your own example to test how this works. For instance, write the following:

val fibo = { n: Int -> // 1
  tailrec fun fiboHelper(a: Int, b: Int, fiboN: Int): Int =
    when (fiboN) {
      0 -> a
      1 -> b
      else -> fiboHelper(b, a + b, fiboN - 1)
    }
  fiboHelper(1, 1, n)
}

fun main() {
  val counter = { a: Int -> Array(a) { it } } // 2
  val fiboLength = { n: Int -> Array(n) { fibo(it) } } // 3
  val counterFibo = counter compose fiboLength // 4
  counterFibo(5).forEach { print("$it ") } // 5
}

Here, you:

  1. Define a utility function, fibo, that returns the nth value in the Fibonacci sequence.
  2. Create counter as a function that, given an Int, returns an Array<Int> of values from 0 to n-1.
  3. Define fiboLength as a function that, given an Int, returns an Array<Int> of the first n values of the Fibonacci sequence.
  4. Create counterFibo as composition of counter and counterFibo.
  5. Invoke counterFibo and print the values of the resulting Array<Int>.

Running the previous code, you get:

1 1 1 1 1 2 1 1 2 3

To understand this output a bit better, walk through what it’s doing:

  1. First, counter is invoked with 5, resulting in the array [0,1,2,3,4].
  2. Then, for each item in that resulting array, fiboLength is invoked, creating a list of the first n Fibonacci numbers. So, on the first element, 0, the result is []. The second, 1, results in [1]. This pattern continues until you reach the element 4, which results in [1,1,2,3].
  3. The results of each of these interations are combined into the final resulting list that you see printed at the end.

In Chapter 12, “Monoids & Semigroups”, you’ll learn how to use a very important function called fold. If you already know how to use it, a possible alternate solution is:

inline infix fun <A, B, reified C> ToArray<A, B>.composeWithFold(
  crossinline g: ToArray<B, C>
): ToArray<A, C> = { a: A ->
  this(a).fold(mutableListOf<C>()) { acc, item ->
    for (bValue in g(item)) { // HERE
      acc.add(bValue)
    }
    acc
  }.toTypedArray()
}

As you’ll learn, to use fold, you need to define what it means for a type to be composable. To test this implementation, just add and run this code:

fun main() {
  // ...
  val counterFiboWithFold = counter composeWithFold fiboLength
  counterFiboWithFold(5).forEach { print("$it ") }
}

Which gives you the same output:

1 1 1 1 1 2 1 1 2 3

Challenge 1: Callable stuff

In the chapter, you learned how to implement the compose function in different scenarios following a common pattern. Consider, now, the following function type:

typealias WithCallable<A, B> = Fun<A, Callable<B>>

How would you implement compose for WithCallable<A, B>? This is using java.util.concurrent.Callable defined as:

interface Callable<V> {
  @Throws(Exception::class)
  fun call(): V
}

Challenge 1 solution

Following the same pattern you learned in the chapter, you can implement compose like this:

infix fun <A, B, C> WithCallable<A, B>.compose( // 1
  g: WithCallable<B, C>
): WithCallable<A, C> = { a: A -> // 2
  Callable<C> { // 3
    g(this(a).call()).call() // 4
  }
}

Here, you:

  1. Define compose as an infix extension function for WithCallable<A, B>.
  2. Return a function of the input parameter a of type A.
  3. Return a new Callable<C> from the inner function.
  4. Get the body of the returning Callable<C> invoking call on the receiver and then call again on the Callable<B> you get in the first place.

Test the previous code with:

fun main() {
  val waitAndReturn = { a: Int -> // 1
    Callable {
      sleep(1000)
      a
    }
  }
  val comp = waitAndReturn compose waitAndReturn  // 2
  chronoMs {
    comp(2).call() // 3
  } pipe ::println
}

Here:

  1. waitAndReturn is a function that returns a Callable<Int> that waits about 1 second and then returns the same value you pass as input.
  2. You compose waitAndReturn with itself.
  3. Using the chrono function in Util.kt, you check that by invoking call, you’re actually invoking the call on the WithCallable<A, B> you’re composing.

The output will be something like:

2053

Note: Remember that sleep doesn’t allow you to wait a specific amount of time but rather a minimum amount of time. This is because it guarantees that the thread scheduler puts the current thread in a runnable state for the time you pass as an input parameter. A thread in a runnable state is a candidate to run, but this doesn’t mean it’ll run soon. This is also why the previous output isn’t exactly 2000 but a little bit more.

Challenge 2: Parameters or not parameters?

Suppose you have the following functions:

val three = { 3 } // 1

val unitToThree = { a: Unit -> 3 } // 2

In this code:

  1. three is a function of type () -> Int, returning 3.
  2. unitToThree is a function of type (Unit) -> Int, also returning 3.

They look like the same function, but they’re actually not. This is because you need a Unit to invoke unitToThree. This also has consequences when you compose. Consider the following code:

fun main() {
  val double = { a: Int -> a * 2 } // 1
  val comp2 = unitToThree compose double // 2  COMPILE
  val comp1 = three compose double // 3  DOESN'T COMPILE
}

Here, you:

  1. Define a simple double function.
  2. Compose unitToThree with double. This compiles.
  3. Try to compose three with double. This doesn’t compile.

The reason is that you don’t have any compose overload with the type () -> T as a receiver. The type (Unit) -> T instead falls into Fun<A, B>.

Can you implement a higher-order function, addUnit, that converts a function of type () -> T in the equivalent (Unit) -> T and removeUnit that does the opposite? Using these functions, how would you fix the code in the previous main?

Challenge 2 solution

The solution to this challenge is very simple. You just need to define the following functions:

fun <A> (() -> A).addUnit() = { unit: Unit -> this() }

fun <A> ((Unit) -> A).removeUnit() = { this(Unit) }

The previous example becomes:

fun main() {
  val double = { a: Int -> a * 2 }
  val comp2 = unitToThree compose double
  val comp1 = three.withUnit() compose double // HERE

Invoking withUnit on three makes it composable with double.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.