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

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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)
  }
fun main() {
  val sum = { a: Int, b: Int -> a + b }
  println(sum(2, 3))
  val sum2 = sum.curry().uncurry()
  println(sum2(2, 3))
}
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)
  }
fun append(a: String, b: String): String = "$a $b"
fun main() {
  val flippedAppend = ::append.flip() // 1
  println(append("First", "Second")) // 2
  println(flippedAppend("First", "Second")) // 3
}
First Second
Second First
fun runDelayed(fn: () -> Unit, delay: Long) { // 1
  sleep(delay) // 2
  fn() // 3
}
fun main() {
  // ...
  runDelayed({
    println("Delayed")
  }, 1000)
}
fun main() {
  // ...
  val runDelayed1Second =
    ::runDelayed.flip() // 1
      .curry() // 2
      .invoke(1000L) // 3
  runDelayed1Second { // 4
    println("Delayed")
  }
}

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
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
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()
(I1, I2, I3) -> O
(I1, I2) -> ((I3) -> O)
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()
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()
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
}
15
curriedSum(1)(2)(3)(4)(5)
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))
}
val result = 5 pipe (4 pipe (3 pipe (2 pipe (1 pipe curriedSum))))
infix fun <A, B> Fun<A, B>.epip(a: A): B = this(a)
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)
}

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>
val fun1: (A) -> Array<B>
val fun2: (C) -> Array<C>
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>.

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
}
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
}
1 1 1 1 1 2 1 1 2 3
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()
}
fun main() {
  // ...
  val counterFiboWithFold = counter composeWithFold fiboLength
  counterFiboWithFold(5).forEach { print("$it ") }
}
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>>
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
  }
}
fun main() {
  val waitAndReturn = { a: Int -> // 1
    Callable {
      sleep(1000)
      a
    }
  }
  val comp = waitAndReturn compose waitAndReturn  // 2
  chronoMs {
    comp(2).call() // 3
  } pipe ::println
}
2053

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

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) }
fun main() {
  val double = { a: Int -> a * 2 }
  val comp2 = unitToThree compose double
  val comp1 = three.withUnit() compose double // HERE
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now