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

8. Composition
Written by Massimo Carli

In Chapter 2, “Function Fundamentals”, you learned that category theory is the theory of composition, which is probably the most important concept in functional programming. In this chapter, you’ll learn why composition is crucial. In particular, you’ll learn how to:

  • Implement function composition in Kotlin.
  • Use curry and uncurry to achieve composition with multi-input parameter functions.
  • Implement partial application and learn why it’s useful.
  • Compose functions with side effects.
  • Handle mutation as a special case of side effects.

As usual, you’ll do this by writing Kotlin code with some interesting exercises and challenges.

Composition in Kotlin

In Chapter 2, “Function Fundamentals”, you implemented the function in Composition.kt:

inline infix fun <B, C, A> Fun<B, C>.after(
  crossinline f: Fun<A, B>
): Fun<A, C> = { a: A ->
  this(f(a))
}

after uses the Fun<A, B> typealias you defined like:

typealias Fun<A, B> = (A) -> B

Another way to see this is:

inline infix fun <A, B, C> Fun<A, B>.compose(
  crossinline g: Fun<B, C>
): Fun<A, C> = { a: A ->
  g(this(a))
}

To revise how they work, write and run the following code Composition.kt:

fun main() {
  val double = { a: Int -> a * 2 } // 1
  val square = { a: Int -> a * a } // 2
  val stringify = Int::toString // 3
  val stringifyDoubleSquareAfter =
    stringify after square after double // 4
  val stringifyDoubleSquareCompose =
    double compose square compose stringify // 5
  println(stringifyDoubleSquareAfter(2)) // 6
  println(stringifyDoubleSquareCompose(2)) // 6
}

Here, you:

  1. Define double as a function that doubles the Int passed in. This is a pure function.

  2. Define square as another pure function returning the square of the Int passed as input.

  3. Assign the reference of the toString function of Int to stringify.

  4. Use after to create stringifyDoubleSquareAfter as a composition of double, square and toString.

  5. Use compose to create stringifyDoubleSquareCompose as another composition of double, square and toString.

  6. Invoke stringifyDoubleSquareAfter and stringifyDoubleSquareCompose, passing the value 2 in input and printing the result.

When you run the code, you get:

16
16

The two functions return the same value, which isn’t as obvious of an outcome as it seems. This works because, as you learned in Chapter 2, “Function Fundamentals”, types and pure functions create a category and associativity is one of the three properties.

Compose multi-parameter functions

So far, so good. But in the previous example, you had very simple functions with one input parameter and one output parameter. How would you compose, for instance, the following functions? Copy these into Curry.kt to explore:

fun main() {
  val double = { a: Int -> a * 2 } // 1
  val square = { a: Int -> a * a } // 1
  val sum = { a: Int, b: Int -> a + b } // 2
  val stringify = Int::toString // 3
}

In this case:

  1. double and square are the same two pure functions for calculating the double and the square of an Int value you saw earlier. The output type is Int.
  2. sum is a pure function with two input parameters of type Int. The return value is of type Int as well, and it’s the sum of the input values.
  3. stringify is the same function you met earlier that returns the String representation of the Int input value.

So, how would you compose double and square with sum to return a function that makes the sum of the double and the square of a couple of Int values, as you see in Figure 8.1?

b*b b a a*2 a*2+b*b sum stringify square double
Figure 8.1: Composition of functions with multiple input parameters

You want to create a function equivalent to the following expression:

stringify(sum(double(10), square(2)))

To do this, you need to use a magic function: the curry function. You’ll prove the curry function from a mathematical point of view in Chapter 10, “Algebraic Data Types”. In this case, you’ll use it to understand why, so far, you’ve only considered functions with a single input parameter. The truth is that single input parameter functions are all you need. Every function with multiple parameters can be represented as a higher-order function of a single parameter.

Note: The term “curry” comes from Haskell Curry, a renowned American mathematician and logician. His first name, Haskell, is also the name of one of the most important functional programming languages.

Before writing the generic curry function, how would you represent sum as a function of a single parameter? In the same Curry.kt file, write the following code:

fun sum(a: Int): (Int) -> Int = { b: Int ->
  a + b
}

Here, you define sum as a higher-order function with a single input parameter that returns, as a result, another function of type (Int) -> Int. To understand how this works, add the following code to main in Curry.kt and run it:

val addThree = sum(3) // 1
val result = addThree(4) // 2
println(result) // 3

Here, you:

  1. Use sum as a function with a single input parameter of type Int, which returns another function you save in addThree. In this case, addThree is a function that adds 3 to the value you pass in.
  2. Invoke addThree, passing 4 as an input parameter, getting 7 as the result.
  3. Print the result.

You get:

7

You may not know, but you just practiced currying!

Now, you need to answer the following two questions:

  1. How do you write curry as a generic function?
  2. How do you use curry to solve the problem in Figure 8.1?

It’s time to have some more fun with higher-order functions.

A generic curry function

In the previous section, you implemented a version of sum that receives an Int as input and returns a function of type (Int) -> Int that adds the new parameter value to the initial value. Now it’s time to implement curry as a generic function.

In the same Curry.kt file, add the following code:

typealias Fun2<A, B, C> = (A, B) -> C

Here, you define Fun2<A, B, C> as an alias of a function of two input parameters of types A and B, returning a value of type C. In the same file, add the following code:

fun <A, B, C> Fun2<A, B, C>.curry(): (A) -> (B) -> C = { a: A -> // 1
  { b: B -> // 2
    this(a, b) // 3
  }
}

Here:

  1. You define curry as an extension function of Fun2<A, B, C>. The return value is a function with a single input parameter a of type A.
  2. The return value of the function in 1 is another function that, this time, has an input parameter b of type B.
  3. Finally, the internal function has a body with the invocation of this, using the parameters a and b.

To understand how this works, write the following code in Curry.kt:

fun main() {
  // ...
  val curriedSum = sum.curry() // 1 (Int) -> (Int) -> Int
  val addThree = curriedSum(3) // 2 (Int) -> Int
  val result = addThree(4) // 3 Int
  println(result) // 4
}

Note: You should use the two-parameter version of sum you used earlier:

val sum = { a: Int, b: Int -> a + b }

This code is very similar to what you implemented earlier. Here, you:

  1. Use curry to get the curried version of sum. The type of curriedSum is (Int) -> (Int) -> Int.
  2. Invoke curriedSum, passing 3 as input and getting a function of type (Int) -> Int, which you save in addThree. This is the function that adds 3 to the value you pass in.
  3. Invoke addThree, passing 4 as input.
  4. Print the result, 7.

Run the previous code, and you get:

7

The sum example is pretty simple. But how can you solve the problem in Figure 8.1?

A practical example

As a more complex problem, you want to compose double, square, sum and stringify to achieve what’s in Figure 8.1 and represent the following expression:

stringify(sum(double(10), square(2)))

In the same Curry.kt file, add the following code:

fun main() {
  // ...
  fun comp(a: Int, b: Int): String { // 1
    val currySum: (Int) -> (Int) -> Int = sum.curry() // 2
    val doubleComposeSum: (Int) -> (Int) -> Int =
      double compose currySum // 3
    val right: (Int) -> Int = doubleComposeSum(a) // 4
    return (square compose right compose stringify)(b) // 5
  }


  fun comp(a: Int, b: Int): String { // 1
    val right = (double compose sum.curry())(a) // 2
    return (square compose right compose stringify)(b) // 3
  }
}

In this code, you:

  1. Define an internal function, comp, which is a function of two Int parameters, a and b, to implement what’s in Figure 8.1: double a, square b, add the results and convert to a String.
  2. Curry sum to create a single input parameter version.
  3. Compose double with the curried version of sum. Remember, double has type (Int) -> Int and sum.curry has type (Int) -> (Int) -> Int. This means the composition has type (Int) -> (Int) -> Int.
  4. Invoke the composition with input a. As a result, you get a function of type (Int) -> Int in right. This function allows you to add an Int to a defined value that, in this case, is the result of double(a).
  5. Invoke the result with the value of the input parameter b. Because now right has type (Int) -> Int, you can easily compose it with square and stringify.

To test the previous function, just add and run the following code in main:

println(comp(10, 2))

Which gives you:

24

This is the correct result of 10*2 + 2*2!

In the comp implementation, you might complain that it has too many parentheses. Can you remove some of them? Of course, you can! In the same Curry.kt file add the following code:

infix fun <A, B> A.pipe(f: Fun<A, B>): B = f(this)

Note: Some developers don’t like using infix operators like pipe and prefer using parentheses. It’s also sometimes difficult to find a name everybody agrees on. Languages that allow you to create custom operators usually represent the pipe with |>.

This is a Kotlin infix extension function of a type A accepting a function f of type Fun<A, B> as an input parameter. The implementation is simple and just invokes the function f to the receiver itself, getting a value of type B.

This simple function allows you to replace the previous comp implementation with the following:

fun comp(a: Int, b: Int): String = b pipe
    (square compose (a pipe
        (double compose sum.curry())) compose stringify)

Kotlin doesn’t let you decide the precedence between compose and pipe, so you still need some parentheses. But here, you can represent all of comp with an expression on a single line.

That’s quite a loaded expression! Take a moment to break it down piece by piece to help you understand it.

For more practice, try out the following exercises and use Appendix H to check your solutions.

Exercise 8.1: Earlier, 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 into a function of type (A, B) -> C.

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

Exercise 8.3: The curry function maps a function of type Fun2<A, B, C> into 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?

Partial application

In the previous section, you learned how to use curry to compose functions with multiple input parameters. This is very useful, but, as you saw in Exercise 8.3, it can also be cumbersome in the case of many parameters. Additionally, most of the time, you don’t need to use just a single parameter at a time. To understand this concept, write this code in Partial.kt:

fun interface Logger { // 1
  fun log(msg: String)
}

fun interface Calculator { // 2
  fun multiply(a: Double, b: Double): Double
}

fun interface DB { // 3
  fun save(result: Double)
}

fun interface CalculatorFactory { // 4
  fun create(db: DB, logger: Logger): Calculator
}

val calculatorFactoryImpl =
  CalculatorFactory { db, logger -> // 5
    object : Calculator {
      override fun multiply(a: Double, b: Double): Double {
        val result = a * b
        db.save(result)
        logger.log("$a * $b = $result")
        return result
      }
    }
  }

This is object-oriented code, and in particular, you define:

  1. The Logger interface with the log operation.
  2. Calculator as an interface for the multiply operation.
  3. DB, simulating a database for persisting a value.
  4. CalculatorFactory as a factory method implementation for the Calculator given a DB and Logger.
  5. calculatorFactoryImpl as an implementation of CalculatorFactory, returning a Calculator implementation that uses Logger to log the operations and DB to persist the result.

The diagram in Figure 8.2 gives you an idea of the dependencies:

+multiply(a: Double,b:Double):Double Calculator «interface» +save (result:Double) DB «interface» Logger «interface» + log(str: String) +multiply ( a: Double, b:Double):Double CalculatorFactorylmpl «object» CalculatorFactory «interface» +create(db: DB, logger: Logger):Calculator «creates» «depends»
Figure 8.2: CalculatorFactory implementation

Add the following code to the same file as an example of the use of calculatorFactoryImpl:

fun main() {
  val db = DB { // 1
    println("Saving value: $it")
  }
  val simpleLogger = Logger { // 2
    println("Logging: $it")
  }
  val fileLogger = Logger { // 3
    println("Logging on File: $it")
  }
  val calculator1 =
    calculatorFactoryImpl.create(db, simpleLogger) // 4
  val calculator2 =
    calculatorFactoryImpl.create(db, fileLogger) // 4
  println(calculator1.multiply(2.0, 3.0)) // 5
  println(calculator2.multiply(2.0, 3.0)) // 5
}

Here, you:

  1. Create a simple DB implementation that just prints a message.
  2. Do the same for Logger.
  3. Create a different implementation for Logger. This one, you put in fileLogger.
  4. Create two different Calculator implementations. calculator1 uses the same DB as calculator2 but a different Logger implementation. To create the Calculators, you use the same calculatorFactoryImpl object.
  5. Use calculator1 and calculator2, printing the result.

Running main, you get:

Saving value: 6.0
Logging: 2.0 * 3.0 = 6.0 // HERE
6.0
Saving value: 6.0
Logging on File: 2.0 * 3.0 = 6.0 // HERE
6.0

As you see, the log differs in the output for the Logger implementation.

Is there a better way to create two different Calculator implementations that differ only in the Logger used? Yes, with partial application. CalculatorFactory defines the create function, which accepts two different parameters.

In the previous example, the value for the first parameter is the same for both Calculator implementations. The second is different. The idea of partial application is mapping the create function in CalculatorFactory in a different function with a single input parameter that returns a function of the second parameter, as you’ve seen in curry.

To understand how this works, replace the second part of main with the following:

fun main() {
  // ...
  val partialFactory = calculatorFactoryImpl::create.curry() // 1
  val partialFactoryWithDb = db pipe partialFactory // 2
  val calculator1 = partialFactoryWithDb(simpleLogger) // 3
  val calculator2 = partialFactoryWithDb(fileLogger) // 3
  println(calculator1.multiply(2.0, 3.0)) // 4
  println(calculator2.multiply(2.0, 3.0)) // 4
}

Here, you:

  1. Define partialFactory as the function you get by applying curry to the create function of calculatorFactoryImpl.
  2. Partially apply some of the parameters in common between all the Calculator implementations you want to create. You create this by invoking partialFactory with db as a unique parameter and saving the resulting function in partialFactoryWithDb.
  3. Use partialFactoryWithDb to create calculator1 and calculator2. Note how you get them by invoking partialFactoryWithDb and passing only the parameters that are different, which is the Logger implementation.
  4. Use calculator1 and calculator2 and print their results.

Running the previous code, you get the same output.

The previous example is very simple and starts from a function with just two input parameters. Partial application is more powerful when the number of input parameters is high. You noticed how the order of the parameters is significant. You could play with flip and the overloads of curry, but this would make the code very complicated. That’s why it’s important to keep partial application in mind when you design your functions.

Note: You might have noticed some similarities between the previous example and what happens with dependency injection. What you’ve seen is an example of how to handle dependency injection in a functional way. In this case, object-orientated and functional programming aren’t so different. Partial application is basically what Dagger calls “assisted injection”. Dependency injection is outside the scope of this book, but if you want to learn all about it, Dagger by Tutorials is the right place for you.

Designing for partial application

As mentioned earlier, partial application is more powerful when the function has many input parameters. You understand how the order of the parameters is important. Just imagine you have a function of six parameters, and you’d like to partially apply just the first, third and last parameters. Using curry and flip is possible, but it would make the code unreadable. On the other hand, it’s very difficult to know how the function will eventually be partially applied, so what you can do is put the parameters:

  1. Less likely to change first.
  2. More likely to change last.

Often, existing functions already follow this pattern, which is handy for you.

As a final tip on this topic, you should also consider that having functions with too many parameters is generally bad practice.

Compose functions with side effects

What you’ve seen so far about composition involves pure functions, which are functions without any side effects and whose bodies are referentially transparent expressions. But what happens if the function isn’t pure because of some side effects? To understand what happens, start with a simple pure function and add some side effects later.

Note: If you need a reminder about pure functions or referential transparency, skip back to Chapter 3, “Functional Programming Concepts”.

Open SideEffects.kt and add the following code:

fun pureFunction(x: Int) = x * x - 1

This is a basic function that removes 1 from the square of the input. It doesn’t really matter what this function does, but you know that:

  1. x * x - 1 is a referentially transparent expression.
  2. It has no side effects because you can invoke pureFunction(5) infinite times, and you’ll always get the same result as output, as you can see when you run the following code:
fun main() {
  pureFunction(5) pipe ::println
  pureFunction(5) pipe ::println
  pureFunction(5) pipe ::println
}

Getting:

24
24
24

So far, so good. Now, it’s time to add a side effect with the following code you write in the same file:

fun functionWithEffect(x: Int): Int { // 1
  val result = x * x - 1 // 2
  println("Result: $result") // 3
  return result // 4
}

In this code, you:

  1. Define functionWithEffect, which returns the same result as pureFunction but has a side effect.
  2. Calculate and store the result in result.
  3. Use println to print a log message, which is a side effect.
  4. Return the result.

Now, test functionWithEffect by running the following code:

fun main() {
  // ...
  functionWithEffect(5) pipe ::println
  functionWithEffect(5) pipe ::println
  functionWithEffect(5) pipe ::println
}

The output is:

Result: 24
24
Result: 24
24
Result: 24
24

As you see, with the same input value, pureFunction and functionWithEffect return the same value as output. However, they’re different because functionWithEffect also logs some messages in the standard output. As you learned in Chapter 3, “Functional Programming Concepts”, running functionWithEffect changes the world because of a side effect. Because of this, functionWithEffect isn’t pure.

It’s important to note, again, how the result value doesn’t tell you anything about the side effect, which you can see only because of the console. You might also think that it’s not so bad because it’s just a message in the standard output.

The problem is that this is just an example, and a side effect could be something more important, like writing to a database or file or sending a request to a server. Reading the function signature, you don’t have any information about what the side effect is. More importantly, how would you test the functionWithEffect function? This isn’t the only problem.

Side effects break composition

In Chapter 11, “Functors”, you’ll learn all about the map function. But to whet your appetite a little, map is a function that allows you to apply a function to all the elements in a container. To understand how it works, run the following code in the same SideEffect.kt file.

fun main() {
  // ...
  listOf(1, 2, 3) // 1
     .map(::pureFunction) pipe ::println // 2, 3
}

Here, you:

  1. Use listOf to create a List<Int> of three elements.
  2. Invoke map, passing the reference to pureFunction.
  3. Print the result.

Running the previous code, you get:

[0, 3, 8]

This is the List<Int> you get when invoking pureFunction on each element of the initial input. The map function has a very important property that says:

map(f).map(g) === map(f compose g)

This says that invoking map with the function f first and then with the function g is equivalent of invoking map on the composition of f and g. This means you can prove this by running the following code:

fun main() {
  listOf(1, 2, 3)
    .map(::pureFunction).map(::pureFunction) pipe ::println
  listOf(1, 2, 3)
    .map(::pureFunction compose ::pureFunction) pipe ::println
}

And getting:

[-1, 8, 63]
[-1, 8, 63]

It’s interesting to test if the same is true for functionWithEffect. To see, you just need to run the following code:

fun main() {
  //...
  listOf(1, 2, 3).map(::functionWithEffect).map(::functionWithEffect) pipe ::println
  listOf(1, 2, 3).map(::functionWithEffect compose ::functionWithEffect) pipe ::println
}

This time, the output is:

Result: 0   // 1
Result: 3   // 2
Result: 8   // 3
Result: -1  // 4
Result: 8   // 5
Result: 63  // 6
[-1, 8, 63] // 7
Result: 0   // 1
Result: -1  // 2
Result: 3   // 3
Result: 8   // 4
Result: 8   // 5
Result: 63  // 6
[-1, 8, 63] // 7

This time, you can see how the output for rows 2, 3 and 4 are different. They’re different because in the first example, all of the contents of the list are transformed using the first invocation of functionWithEffect before working through the second.

Where, in the second example, the first element is transformed by the composed functionWithEffects before moving to the second element.

This means that the side effect you get from the composition isn’t the composition of the side effect. This is proof that functions with side effects don’t compose. How can you solve this problem, then?

Note: Using map with the composition is also an improvement in performance because it allows you to iterate over the elements in List<T> just once.

A composable effect

In the previous example, you proved that functions with side effects don’t compose well. One of the reasons is that composition means using the result of a first function as the input of the second. If the side effect isn’t part of the output, this makes composition difficult. What about removing the side effect from the body of the function and passing the same information as part of the value as output?

In the same SideEffects.kt file, add the following code you already met in part in Chapter 3, “Functional Programming Concepts”:

fun functionWithWriter(x: Int): Pair<Int, String> { // 1
  val result = x * x - 1 // 2
  return result to "Result: $result" // 3
}
  1. Now, you define functionWithWriter as a function that returns Pair<Int, String>.
  2. The first Int property of the resulting Pair<Int, String> is the same result as functionWithEffect.
  3. The second String property is the message you used to print in functionWithEffect.

Now, functionWithWriter doesn’t have any side effects, but the String you want to print is part of the output. This makes functionWithWriter a pure function. functionWithWriter doesn’t print anything, but it delegates the responsibility of handling the side effect to the caller. But now you have a bigger problem: functionWithWriter doesn’t compose with itself, and the following code doesn’t compile:

// DOESN'T COMPILE
val compFunWithWriter =
  ::functionWithWriter compose ::functionWithWriter

This is because the compose function you created doesn’t match the signature of functionWithWriter, which has an Int as input type and a Pair<Int, String> as output. You know how to fix this, remembering that compFunWithWriter is basically a Writer<Int> where:

typealias Writer<A, B> = (A) -> Pair<B, String>

In Chapter 3, “Functional Programming Concepts”, you also learned how to implement the compose function for Writer. In the same SideEffects.kt file, along with the previous typealias, write the following code:

infix fun <A, B, C> Writer<A, B>.compose( // 1
  g: Writer<B, C>
): Writer<A, C> = { a: A -> // 2
  val (b, str) = this(a) // 3
  val (c, str2) = g(b) // 4
  c to "$str\n$str2\n" // 5
}

Here, you:

  1. Define compose as an extension function of Writer<A, B> that accepts another Writer<B, C> as an input parameter.
  2. Return a function with a parameter of type A.
  3. Invoke the receiver with a and, using de-structuring, get the two parts of the resulting Pair<Int, String> respectively in Int b and String str.
  4. Invoke g, passing the value of b you got at the previous instruction, de-structuring the result in c and String str2 again.
  5. Return Pair<Int, String> using the result c and the concatenations of str and str2.

Now, this code will compile:

// NOW COMPILES!
val compFunWithWriter =
  ::functionWithWriter compose ::functionWithWriter

Of course, you can compose multiple functions of type Writer<T>, as you can see by running the following code:

fun main() {
  val square = { a: Int -> a * a } // 1
  val double = { a: Int -> a * 2 } // 1
  val squareFunAndWrite = square compose ::functionWithWriter // 2
  val doubleFunAndWrite = double compose ::functionWithWriter // 3
  val compFunWithWriter = squareFunAndWrite compose doubleFunAndWrite // 4
  compFunWithWriter(5).second pipe ::println // 5
}

Here, you:

  1. Define square and double as simple lambda expressions.
  2. Define squareFunAndWrite as composition of square and functionWithWriter.
  3. Define doubleFunAndWrite as composition of double and functionWithWriter.
  4. Define compFunWithWriter as a composition of squareFunAndWrite and compFunWithWriter.
  5. Finally, invoke compFunWithWriter, printing the result of the second property.

You’ll get:

Result: 624
Result: 1557503

As you learned in Chapter 3, “Functional Programming Concepts”, this is something related to the Kleisli category, but it’s also a very important pattern in the world of functional programming.

A common composition pattern

What you saw in the previous example is a common pattern in functional programming, and it works with different types of functions. Instead of handling composition for the type:

typealias Writer<A, B> = (A) -> Pair<B, String>

How would you implement composition of the following type you define by adding this to GeneralComposition.kt:

typealias Opt<A, B> = (A) -> B?

In Chapter 9, “Data Types”, you’ll learn much more about the optional type along with many other fundamental data types. In this case, it’s interesting to see how you’d compose functions of type Opt<A, B>.

You might think you can use the existing compose function because Opt<A, B> is somehow included in the following, considering the B of Fun<A, B> as the B? of Opt<A, B>:

typealias Fun<A, B> = (A) -> B

But the following code doesn’t compile:

data class User(val id: Int, val username: String)

fun main() {
  val strToInt = { str: String ->
    try {
      str.toInt()
    } catch (nfe: NumberFormatException) {
      null
    }
  }
  val findUser = { id: Int ->
    if (id == 3) User(3, "Max") else null
  }
  val strToUser = strToInt compose findUser // DOESN'T COMPILE
}

The reason is that strToInt returns an optional Int? but findUser accepts an Int. You can repeat the same pattern you learned for Writer<T> by adding the following compose function:

infix fun <A, B, C> Opt<A, B>.compose( // 1
  g: Opt<B, C> // 2
): Opt<A, C> = { a: A -> // 3
  val b = this(a) // 4
  if (b != null) { // 5
    g(b) // 6
  } else {
    null // 7
  }
}

In this function, you:

  1. Define compose as an infix extension function of Opt<A, B>.
  2. Declare g as the input parameter of type Opt<B, C>.
  3. Return a function with the input parameter a of type A and Opt<A, C> as the output type.
  4. Invoke the receiver function with the value of a. What you get is an optional B?.
  5. Check if b is null.
  6. Invoke g with b and return the result of type C? if b isn’t null.
  7. Return null if b is null.

In the previous code, you understand how the logic in the body of the function might be different, but it follows a common pattern you’ll see many more times in the following chapters.

Note: Notice how similar this pattern is to using the Kotlin safe-call operator. The difference is that your above compose is composing functions rather than simply calling a function on an object.

Now, update main like this:

fun main() {
  val strToInt = { str: String ->
    try {
      str.toInt()
    } catch (nfe: NumberFormatException) {
      null
    }
  }
  val findUser = { id: Int ->
    if (id == 3) User(3, "Max") else null
  }
  val strToUser = strToInt compose findUser // 1
  strToUser("a") pipe ::println // 2
  strToUser("2") pipe ::println // 3
  strToUser("3") pipe ::println // 4
}

Here, you:

  1. Create strToUser as a composition of strToInt and findUser. Now, strToUser returns null if either strToInt or findUser returns null.
  2. Use strToUser for an invalid user ID.
  3. Use strToUser for a missing user ID.
  4. Use strToUser for an existing user ID.

Run the code, and you get:

null
null
User(id=3, username=Max)

Where only the third case returns something that isn’t null.

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

Give it a try, and check your solution with the one in Appendix H.

Currying again

Implementing compose for a specific type of function is a pattern you’ll see many times in this book, and in general, when you use functional programming. In the previous example, you learned how to compose a function with a particular side effect. The overloaded println function you used for printing Int values is a function of type (Int) -> Unit. You also used the overload of type (String) -> Unit. In any case, it’s a function with a String input and Unit as output. Open CurryAgain.kt and write the following code:

fun functionWithAnotherEffect(x: Int): String {
  val result = x * x - 1
  return "Result: $result calculated on ${System.currentTimeMillis()}"
}

This function isn’t pure because the expression it represents isn’t referentially transparent. It depends on some external state that, in this case, you access through the currentTimeMillis method of System. To prove that, just add and run the following code:

fun main() {
  functionWithAnotherEffect(5) pipe ::println
  functionWithAnotherEffect(5) pipe ::println
}

And you get something similar to:

Result: 24 calculated on 1632737433997
Result: 24 calculated on 1632737434014

Every time you invoke functionWithAnotherEffect with the same input, you get different values as output. So, how would you make functionWithAnotherEffect pure, and how would you handle composition?

In the println example, you had a function of type (Int) -> Unit and you just moved the input for the effect to the output. Now, the function System::currentTimeMillis has type () -> Long. A possible solution is moving the value you get from System::currentTimeMillis to an input parameter like this:

fun functionWithAnotherEffect(time: Long, x: Int): String {
  val result = x * x - 1
  return "Result: $result calculated on $time"
}

Now, functionWithAnotherEffect is pure because the output depends only on the input parameters. This allows you to test the function very easily. Just replace the previous main with the following:

fun main() {
  functionWithAnotherEffect(123L, 5) pipe ::println
  functionWithAnotherEffect(123L, 5) pipe ::println
}

Run it, and you’ll get what you expect:

Result: 24 calculated on 123
Result: 24 calculated on 123

At this point, you need to solve two problems:

  1. You don’t always want to pass a first parameter value to functionWithAnotherEffect. You only need it when you’re testing the function. When you just want to use it, you don’t always want to pass the value you get from System.currentTimeMillis().
  2. You broke composition.

The first problem is easy to solve using Kotlin’s optional parameter. Just update functionWithAnotherEffect like this:

fun functionWithAnotherEffect(
  time: Long = System.currentTimeMillis(), x: Int
): String {
  val result = x * x - 1
  return "Result: $result calculated on $time"
}

Now, you can run this code where you used the explicit value as input for the time parameter just when you want to test functionWithAnotherEffect.

fun main() {
  functionWithAnotherEffect(x = 8) pipe ::println
  functionWithAnotherEffect(123L, 5) pipe ::println
  functionWithAnotherEffect(123L, 5) pipe ::println
}

Here, you’ll get:

Result: 63 calculated on 1632738736781 // FOR NORMAL USE
Result: 24 calculated on 123 // FOR TEST
Result: 24 calculated on 123 // FOR TEST

What about composition, then? Well, that problem is solved with just a little bit of curry!

Just note how ::functionWithAnotherEffect has type:

(Long, Int) -> String

This means that ::functionWithAnotherEffect.curry() has type:

(Long) -> (Int) -> String

To get the function to use during tests, you just need to use the following code:

fun main() {
  // ...
  val forTesting = 123L pipe ::functionWithAnotherEffect.curry() // 1
  forTesting(5) pipe ::println // FOR TEST // 2
  forTesting(5) pipe ::println // FOR TEST // 2
}

Here, you:

  1. Invoke curry on ::functionWithAnotherEffect and then invoke the resulting function with an input value of type Long, which is the time value you use during tests.
  2. Verify that the output is always the same for the same input.

Running the previous code, you get what you’d expect:

Result: 24 calculated on 123
Result: 24 calculated on 123

For ::functionWithAnotherEffect, you can reuse all the things you learned in the section “Compose multi-parameter functions”.

Compose mutation

In this final case of handling composition, it’s time to have some fun. The goal is to handle composition when the side effect of a function implies mutation. To understand how this works, open Mutation.kt and add the following code:

data class MutableCounter( // 1
  var count: Int = 1
)

val counter = MutableCounter() // 2

fun squareWithMutationEffect(x: Int): Int { // 3
  val result = x * x
  counter.count *= 10
  return result
}

fun doubleWithMutationEffect(x: Int): Int { // 4
  val result = x * 2
  counter.count /= 2
  return result
}

The code is quite easy to understand. Here:

  1. MutableCounter is a mutable data class wrapping a simple count variable of type Int, initialized to 1.
  2. You create counter as a MutableCounter instance.
  3. squareWithMutationEffect is a simple function that returns the square of the input Int. It also has a side effect that multiplies the current value in the MutableCounter by 10.
  4. doubleWithMutationEffect is another simple function that doubles the input Int and divides the current value of the counter by 2 as a side effect.

You have the same problem you solved in the section “A common composition pattern”, but now the effect is a mutation of a shared state you represent with an instance of MutableCounter.

How can you now make squareWithMutationEffect and doubleWithMutationEffect pure and somehow compose the effects? What if you also handle mutation using immutable objects instead? It looks quite challenging, but you can actually do this with what you’ve learned so far.

First, comment out squareWithMutationEffect and doubleWithMutationEffect and add the following code:

typealias Updater<T> = (T) -> T // 1

fun squareWithEffect(x: Int): Pair<Int, Updater<MutableCounter>> { // 2
  val result = x * x // 3
  return result to { counter -> counter.count *= 10; counter } // 4
}

fun doubleWithEffect(x: Int): Pair<Int, Updater<MutableCounter>> { // 2
  val result = x * 2 // 3
  return result to { counter -> counter.count /= 2; counter } // 4
}

Things are getting more interesting. In this code, you:

  1. Define Updater<T> as the abstraction of any function that maps objects in another object of the same type. Of course, identity would be a special case of Updater<T> because it wouldn’t do anything.
  2. Replace squareWithMutationEffect and doubleWithMutationEffect with squareWithEffect and doubleWithEffect, respectively, which differ for the return type that’s now Pair<Int, Updater<MutableCounter>>. first is the result of the function and second is the function you need to run on MutableCounter to update its state.
  3. Calculate the result.
  4. Return Pair<Int, Updater<MutableCounter>> using a lambda expression as Updater<MutableCounter>.

Now, here’s the interesting part. How would you compose functions like this? The types squareWithEffect and doubleWithEffect are similar to Writer<A, B>, but in that case, it was defined as:

typealias Writer<A, B> = (A) -> Pair<B, String>

Now, the second element of the Pair<A, B> isn’t String, but Updater<S>. This doesn’t change so much because you can apply what you learned in the section “A common composition pattern”.

In the same Mutation.kt file, add the following code:

typealias WithMutation<A, B, S> = (A) -> Pair<B, Updater<S>>

This defines WithMutation<A, B, S> as the counterpart of Writer<A, B> for this use case:

  • A and B are respectively the input and output of a function.
  • S is the type of object for the function to mutate.

Using this, you can finally write the compose overload like this:

inline infix fun <A, B, C, S> WithMutation<A, B, S>.compose(
  crossinline g: WithMutation<B, C, S> // 1
): WithMutation<A, C, S> = { a: A -> // 2
  val (b, op) = this(a) // 3
  val (c, op2) = g(b) // 4
  c to (op compose op2) // 5
}

In this code, you:

  1. Define compose as an infix extension function of WithMutation<A, B, S> in the usual way.
  2. Return a function with the input parameter a of type A.
  3. Invoke the receiver, passing a and de-structuring the result in a variable b of type B and a function op of type Updater<S>.
  4. Invoke g, passing b as an input parameter. Using de-structuring, you get the result you save in c of type C along with the Updater<S> you save in op2.
  5. Return Pair<C, Updater<S>> where the second property is the composition of op and op2.

Remember, the definition of Updater<S> is basically a Fun<S, S> for which you already have a compose overload.

As a quick test, add and run the following code:

fun main() {
  val composed = ::squareWithEffect compose
      ::doubleWithEffect compose ::squareWithEffect // 1
  val counter = MutableCounter() // 2
  val (result, compUpdate) = composed(3) // 3
  result pipe ::println // 4
  counter pipe compUpdate pipe ::println // 5
}

Here, you:

  1. Use compose to compose ::squareWithEffect with ::doubleWithEffect and then ::squareWithEffect again.
  2. Save the shared mutable state in counter. Its type is MutableCounter.
  3. Use de-structuring to get the result in result and the composed mutation in compUpdate.
  4. Print the value of result.
  5. Apply compUpdate to counter and print the current state.

Run the code, and you get:

324 // 1
MutableCounter(count=50) // 2

This is because:

  1. The result of ((3 * 3) * 2) * ((3 * 3) * 2) is 18 * 18.
  2. The counter starts at 1. Then, you multiply by 10, getting 10. Next, you divide by 2, getting 5. Finally, you multiply by 10 again, getting 50.

Of course, the client is responsible for the execution of the effect. But, can you do something better? Of course, you can. Do you really need a MutableCounter?

Composition with immutable objects

In the previous example, you used MutableCounter, but the good news is that you don’t have to change much if you want to use an immutable Counter. Open ImmutableComposition.kt, and add the following code:

data class Counter(  // 1
  val count: Int = 1
)

fun squareWithImmutableEffect(x: Int): Pair<Int, Updater<Counter>> {
  val result = x * x
  return result to { counter -> Counter(counter.count * 10) } // 2
}

fun doubleWithImmutableEffect(x: Int): Pair<Int, Updater<Counter>> {
  val result = x * 2
  return result to { counter -> Counter(counter.count / 2) } // 3
}

fun main() {
  val composed = ::squareWithImmutableEffect compose
    ::doubleWithImmutableEffect compose
    ::squareWithImmutableEffect
  val counter = Counter() // 4
  val (result, compUpdate) = composed(3)
  result pipe ::println
  counter pipe compUpdate pipe ::println
}

This code has a few significant differences from the one in the previous section:

  1. You define Counter as an immutable class.
  2. squareWithImmutableEffect doesn’t change the current shared and mutable state. However, it creates a new Counter using the data of the one in input.
  3. doubleWithImmutableEffect does the same, creating a new Counter.
  4. You print the result in the same way as the previous example.

Run this code, and you get:

324
Counter(count=50)

This is the same result you got using MutableCounter, but this time you used Counter, which is immutable.

Challenges

This is one of the most important chapters of the book because composition is the essence of functional programming. You already did some interesting exercises, so now it’s time for a couple challenges.

Challenge 8.1: Callable stuff

In this 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 8.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?

Key points

  • Composition is the most important concept of functional programming.
  • Category theory is the theory of composition.
  • Functions with a single input parameter are all you need. Using curry, each function with multiple parameters can be mapped into higher-order functions of a single parameter.
  • The name curry comes from Haskell Curry, an American mathematician and logician.
  • Partial application is a generalized version of currying. It allows you to decide what parameters to provide initially and what to provide later.
  • You can think of partial application as a way to implement dependency injection in a functional way.
  • The Kleisli category helps you understand how to implement composition of functions with side effects. The idea is to bring the effect as part of the return type, but this usually breaks composition.
  • Writer<T> leads you to a general pattern in the implementation of composition.
  • You can use the same pattern to manage composition of functions that contain mutation logic.

Where to go from here?

Wow! In this chapter, you’ve done a great job! Congratulations. Composition is probably the most fascinating part of functional programming and gives you a lot of gratification when you see your code compile and work.

This chapter completes the first section of the book. In the following section, you’ll enter the core of functional programming, starting with the concept of data types. See you there!

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.