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:
-
flipis an extension function on the type(A, B) -> C. - The return type is
(B, A) -> C. - It returns a function of the parameters
bandaof typesBandA, 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:
- Define
flippedAppendas the function you get by invokingflipon::append. - Print the result of
append, passing"First"and"Second"as input values. - Print the result of
flippedAppendwith 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:
- Define
runDelayedas a function with two input parameters. The first is a lambda of type() -> Unitand the second is aLongthat represents the time you have to wait before invoking the previous lambda. -
sleepfor thedelaytime. - 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:
- Define
runDelayed1Secondas a function that allows you to run a given lambda after a one-second delay. First, you invokeflip, getting a function withLongas the first parameter and the lambda as the second. - Invoke
curry(), getting a function of type(Long) -> (() -> Unit) -> Unit. - Invoke the curried function with
1000Las an input parameter for the delay. This makes(() -> Unit) -> Unitthe type ofrunDelayed1Second. - Use
runDelayed1Second, passing the lambda expression you want to run, but delayed by1second.
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:
- Implement a simple function,
sum, that calculates the sum of the five input parameters. - Define
curriedSum, invokingcurryonsum. The type ofcurriedSumisChain5<I1, I2, I3, I4, I5, O>. - Invoke
curriedSumand 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:
- Define
composeas an infix extension function of theToArray<A, B>type, accepting an input parameter of typeToArray<B, C>. Of course, the return type isToArray<A, C>. - Return a function of the input parameter
aof typeA. - Invoke the received on
agetting anArray<B>you save inbArray. - Create a
MutableList<C>you fill with the values you get by invokinggon each element ofbArray. - Return the
Array<C>version ofMutableList<C>. This is the reason the typeCrequiresreified.
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:
- Define a utility function,
fibo, that returns thenth value in the Fibonacci sequence. - Create
counteras a function that, given anInt, returns anArray<Int>of values from0ton-1. - Define
fiboLengthas a function that, given anInt, returns anArray<Int>of the firstnvalues of the Fibonacci sequence. - Create
counterFiboas composition ofcounterandcounterFibo. - Invoke
counterFiboand print the values of the resultingArray<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:
- First,
counteris invoked with5, resulting in the array[0,1,2,3,4]. - Then, for each item in that resulting array,
fiboLengthis invoked, creating a list of the firstnFibonacci numbers. So, on the first element,0, the result is[]. The second,1, results in[1]. This pattern continues until you reach the element4, which results in[1,1,2,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:
- Define
composeas an infix extension function forWithCallable<A, B>. - Return a function of the input parameter
aof typeA. - Return a new
Callable<C>from the inner function. - Get the body of the returning
Callable<C>invokingcallon the receiver and thencallagain on theCallable<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:
-
waitAndReturnis a function that returns aCallable<Int>that waits about1second and then returns the same value you pass as input. - You compose
waitAndReturnwith itself. - Using the
chronofunction inUtil.kt, you check that by invokingcall, you’re actually invoking thecallon theWithCallable<A, B>you’re composing.
The output will be something like:
2053
Note: Remember that
sleepdoesn’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 exactly2000but 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:
-
threeis a function of type() -> Int, returning3. -
unitToThreeis a function of type(Unit) -> Int, also returning3.
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:
- Define a simple
doublefunction. - Compose
unitToThreewithdouble. This compiles. - Try to compose
threewithdouble. 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.