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

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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
}
m*n d e i*5 a*4+d*y jeb wmburbuyt njaodo waujqe
Rihuqu 6.7: Tiqseyopuom ab wishpeacj tohb rurqiqdi inveb qaxuqelarz

stringify(sum(double(10), square(2)))
fun sum(a: Int): (Int) -> Int = { b: Int ->
  a + b
}
val addThree = sum(3) // 1
val result = addThree(4) // 2
println(result) // 3
7

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.

typealias Fun2<A, B, C> = (A, B) -> C
fun <A, B, C> Fun2<A, B, C>.curry(): (A) -> (B) -> C = { a: A -> // 1
  { b: B -> // 2
    this(a, b) // 3
  }
}
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
}
val sum = { a: Int, b: Int -> a + b }
7

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)))
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
  }
}
println(comp(10, 2))
24
infix fun <A, B> A.pipe(f: Fun<A, B>): B = f(this)
fun comp(a: Int, b: Int): String = b pipe
    (square compose (a pipe
        (double compose sum.curry())) compose stringify)

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
      }
    }
  }
+quvrentp(e: Geikwe,x:Xaemgi):Keeybu Tuckubovan «aktuvcoti» +cepa (bonojv:Seegho) XL «emfepvimo» Yoxkiq «esbelqotu» + pat(kcy: Dzyupy) +kamlinlj ( u: Heivde, z:Yaucwi):Qaovdo ZabloxenusRisbaszbjfr «oljarm» BuksurajipYihtotg «ugqobzuja» +hhiuca(hz: BV, yidfed: Bunben):Mitpuhitac «gkoujuh» «dabohkz»
Vujaci 6.9: PanlunapexLobyolw ejysayihwowaan

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
}
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
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
}

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:

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.

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

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
}
[0, 3, 8]
map(f).map(g) === map(f compose g)
fun main() {
  listOf(1, 2, 3)
    .map(::pureFunction).map(::pureFunction) pipe ::println
  listOf(1, 2, 3)
    .map(::pureFunction compose ::pureFunction) pipe ::println
}
[-1, 8, 63]
[-1, 8, 63]
fun main() {
  //...
  listOf(1, 2, 3).map(::functionWithEffect).map(::functionWithEffect) pipe ::println
  listOf(1, 2, 3).map(::functionWithEffect compose ::functionWithEffect) pipe ::println
}
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

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?

fun functionWithWriter(x: Int): Pair<Int, String> { // 1
  val result = x * x - 1 // 2
  return result to "Result: $result" // 3
}
// DOESN'T COMPILE
val compFunWithWriter =
  ::functionWithWriter compose ::functionWithWriter
typealias Writer<A, B> = (A) -> Pair<B, String>
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
}
// NOW COMPILES!
val compFunWithWriter =
  ::functionWithWriter compose ::functionWithWriter
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
}
Result: 624
Result: 1557503

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>
typealias Opt<A, B> = (A) -> B?
typealias Fun<A, B> = (A) -> B
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
}
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
  }
}
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
}
null
null
User(id=3, username=Max)
typealias ToArray<A, B> = (A) -> Array<B>
val fun1: (A) -> Array<B>
val fun2: (C) -> Array<C>
fun1 compose fun2

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()}"
}
fun main() {
  functionWithAnotherEffect(5) pipe ::println
  functionWithAnotherEffect(5) pipe ::println
}
Result: 24 calculated on 1632737433997
Result: 24 calculated on 1632737434014
fun functionWithAnotherEffect(time: Long, x: Int): String {
  val result = x * x - 1
  return "Result: $result calculated on $time"
}
fun main() {
  functionWithAnotherEffect(123L, 5) pipe ::println
  functionWithAnotherEffect(123L, 5) pipe ::println
}
Result: 24 calculated on 123
Result: 24 calculated on 123
fun functionWithAnotherEffect(
  time: Long = System.currentTimeMillis(), x: Int
): String {
  val result = x * x - 1
  return "Result: $result calculated on $time"
}
fun main() {
  functionWithAnotherEffect(x = 8) pipe ::println
  functionWithAnotherEffect(123L, 5) pipe ::println
  functionWithAnotherEffect(123L, 5) pipe ::println
}
Result: 63 calculated on 1632738736781 // FOR NORMAL USE
Result: 24 calculated on 123 // FOR TEST
Result: 24 calculated on 123 // FOR TEST
(Long, Int) -> String
(Long) -> (Int) -> String
fun main() {
  // ...
  val forTesting = 123L pipe ::functionWithAnotherEffect.curry() // 1
  forTesting(5) pipe ::println // FOR TEST // 2
  forTesting(5) pipe ::println // FOR TEST // 2
}
Result: 24 calculated on 123
Result: 24 calculated on 123

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
}
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
}
typealias Writer<A, B> = (A) -> Pair<B, String>
typealias WithMutation<A, B, S> = (A) -> Pair<B, Updater<S>>
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
}
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
}
324 // 1
MutableCounter(count=50) // 2

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
}
324
Counter(count=50)

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>>
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
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
}

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.

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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now