I.
Appendix I: Chapter 9 Exercise & Challenge Solutions
Written by Massimo Carli
Exercise 9.1
In this chapter, you learned what the Optional<T> data type is, and you implemented some important functions for it like lift, empty, map and flatMap. Kotlin defines its own optional type represented by ?. How would you implement the lift, empty and getOrDefault functions for it?
Exercise 9.1 solution
You can implement the previous functions like this:
fun <T : Any> T.lift(): T? = this // 1
fun <T : Any> T.empty(): T? = null // 2
fun <T : Any> T?.getOrDefault(defaultValue: T): T =
this ?: defaultValue // 3
In this code, you define:
-
liftas an extension function of the typeT. Note that the receiver typeTisn’t optional because of the constraintT: Any. The important thing here is thatliftreturns the same receiver but as a reference of the optional typeT?. -
emptyas an extension function of the not-optional typeT, returningnull, but as the value of a reference of typeT?. -
getOrDefaultalso as an extension function of the optional typeT?. Note how the return type is the not-optional typeT. In the body, you just check ifthisisnull, returningdefaultValueif it is.
Run the following code for a better understanding of how all this works:
fun main() {
val optStr = "10".lift() // 1
optStr pipe ::println
val empty = String.empty() // 2
empty pipe ::println
optStr // 3
.getOrDefault("Default")
.pipe(::println)
empty // 4
.getOrDefault("Default")
.pipe(::println)
}
And you get the output:
10 // 1
null // 2
10 // 3
Default // 4
Here, you use:
-
liftto convert aString,"10", into an optionalString?you then print. -
emptyto getnullthrough a reference of typeString?you also print. -
getOrDefaultonoptStr, getting10as the result. -
getOrDefaultonempty, getting the default valueDefaultyou pass as a parameter.
Exercise 9.2
In this chapter, you learned what the Optional<T> data type is, and you implemented some important functions for it like lift, empty, map and flatMap. Kotlin defines its own optional type represented by ?. How would you implement the map and flatMap functions?
Exercise 9.2 solution
A possible implementation of map for the Kotlin optional type is:
fun <A : Any, B : Any> A?.map(fn: Fun<A, B>): B? =
if (this != null) fn(this).lift() else null
This code has several important details:
-
mapis an extension function for the optional typeA?. -
mapaccepts a single parameter of typeFun<A, B>. - You return
nullif the receiver isnull. - If the receiver isn’t
null, you pass it as an input parameter of the functionfnand return the lifted result of typeB?.
A possible implementation of flatMap for the Kotlin optional type is:
fun <A : Any, B : Any> A?.flatMap(fn: Fun<A, B?>): B? =
if (this != null) fn(this)?.lift() else null
In this case, you note that flatMap:
- Is an extension function of the optional type
A?. - Accepts a single parameter of type
Fun<A, B?>. It’s important to note the optionalB?as a return type for the function, which differs frommap. - Returns
nullif the receiver isnullor iffnreturns null. - Returns the result of invoking
fnif the receiver isn’tnull.
Exercise 9.3
How would you replicate the example you implemented in OptionalTest.kt using T? instead of Optional<T>? Use the solutions of Exercise 9.1 and Exercise 9.2 to implement this example.
Exercise 9.3 solution
You can test the code you created in Exercise 9.1 and Exercise 9.2 by running the following code:
fun strToInt(value: String): Int? = // 1
try {
value.toInt().lift()
} catch (nfe: NumberFormatException) {
null
}
fun <T : Any> T?.getOrDefault(defaultValue: T): T = // 2
if (this == null) defaultValue else this
fun main() {
"10" // 3
.lift()
.flatMap(::strToInt)
.map(::double)
.getOrDefault(-1)
.pipe(::println)
"10sa" // 4
.lift()
.flatMap(::strToInt)
.map(::double)
.getOrDefault(-1)
.pipe(::println)
}
In this code, you:
- Define
strToIntas a function that converts aStringinto theIntit contains, if possible. If that isn’t possible, it returnsnull. This is a function of typeFun<String, Int?>you can pass as input toflatMap. - Create
getOrDefault, checking the receiver’s value and returningdefaultValueif it’s null. - Use the same structure you used with
Optional<T>with a validString. - And again, use the same structure with an invalid
String.
The output is:
20
-1
Exercise 9.4
Implement a function that reverses a String using one of the folding functions you’ve implemented in this chapter.
Exercise 9.4 solution
A String is just an array of Chars. This means that a possible implementation for the reverse function is:
fun reverse(str: String) =
str.toCharArray().toList() // 1
.declarativeFoldRight(StringBuilder()) { c, acc -> // 2
acc.append(c) // 3
acc
}.toString() // 4
In this code, you:
- Convert the
Stringpassed as input to aList<Char>. - Invoke
declarativeFoldRight, passing aStringBuilderas the initial state for the accumulator. - Append the character to the previous accumulator state in the combination function.
- Return the content of
StringBuilderas aString.
To test the previous code, just run the following code:
fun main() {
reverse("supercalifragilisticexpialidocious") pipe ::println
}
Getting:
suoicodilaipxecitsiligarfilacrepus
Exercise 9.5
In this chapter, you implemented declarativeFold and declarativeFoldRight as extension functions for List<T>. How would you implement them for Iterable<T>?
Exercise 9.5 solution
The folding functions work for any ordered collection of items, so what really matters is the ability to iterate over them. A possible implementation for declarativeFold on Iterable is:
fun <T, S> Iterable<T>.iterableFold(
start: S,
combineFunc: (S, T) -> S
): S { // 1
tailrec fun helper(iterator: Iterator<T>, acc: S): S { // 2
if (!iterator.hasNext()) { // 3
return acc
}
return helper(iterator, combineFunc(acc, iterator.next())) // 4
}
return helper(iterator(), start) // 5
}
In this code:
- You create
iterableFoldas an extension function forIterable<T>. The name is different from your previous implementations, so there are no conflicts. - You define
helperas a function accepting anIterator<T>. In fact, you just need to check if you’re at the end of theIterator<T>or not, which you do withhasNext. - If you’re at the end of the
Iterator<T>, you just returnacc. - Otherwise, you recursively call
helper, passing the sameiteratorand the result you get combiningaccwith the next element. - You start everything, invoking
helperwith theIterator<T>you get fromiterator. This is possible because the receiver is anIterable<T>.
Using the same approach, you can also implement iterableFoldRight like this:
fun <T, S> Iterable<T>.iterableFoldRight(
start: S,
combineFunc: (T, S) -> S
): S {
fun helper(iterator: Iterator<T>): S {
if (!iterator.hasNext()) {
return start
}
return combineFunc(iterator.next(), helper(iterator))
}
return helper(iterator())
}
To test how they work, run the following code:
fun main() {
"supercalifragilisticexpialidocious".asIterable()
.iterableFoldRight(StringBuilder()) { item, acc ->
acc.append(item)
acc
} pipe ::println
"supercalifragilisticexpialidocious".asIterable()
.iterableFold(StringBuilder()) { acc, item ->
acc.append(item)
acc
} pipe ::println
}
Getting:
suoicodilaipxecitsiligarfilacrepus
supercalifragilisticexpialidocious
Challenge 9.1: Filtering
How would you implement a filter function on a List<T> using fold or foldRight? You can name it filterFold. Remember that given:
typealias Predicate<T> = (T) -> Boolean
The filterFold function for a List<T> should have this signature:
fun <T> List<T>.filterFold(predicate: Predicate<T>): List<T> {
// Implementation
}
Challenge 9.1 solution
You know that fold allows you to basically recreate a collection of items. If you add an item after the evaluation of a predicate, you basically implement the filter function. One possible solution is:
fun <T> List<T>.filterFold(predicate: Predicate<T>): List<T> =
fold(mutableListOf()) { acc, item -> // 1
if (predicate(item)) { // 2
acc.add(item) // 3
}
acc
}
In this code, you:
- Invoke
foldusing aMutableList<T>as a starting value. - Evaluate the predicate against the current value.
- Add the element if the predicate evaluates to
true.
To test the previous code, simply run:
fun main() {
listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filterFold { it % 2 == 0 }
.forEach(::println)
}
And the output is:
2
4
6
8
10
Challenge 9.2
How would you implement the length function for a List<T> that returns its size using fold or foldRight?
Challenge 9.2 solution
A possible implementation is:
fun <T> List<T>.length(): Int =
fold(0) { acc, _ ->
acc + 1
}
In this case, you don’t care about the items, but you increment acc for each of them. To test how this works, just run the following code:
fun main() {
val list = List<Int>(37) { it }
list.length() pipe ::println
}
And you get:
37
In this case, using fold or foldRight doesn’t make any difference.
Challenge 9.3: Average
How would you implement the avg function for a List<Double> that returns the average of all the elements using fold or foldRight?
Challenge 9.3 solution
The solution here is simple, and is basically the implementation of the definition of average: the sum of all the elements divided by the number of elements:
fun List<Double>.average(): Double =
fold(0.0) { acc, item -> acc + item } /
fold(0.0) { acc, _ -> acc + 1 }
Run this code to test the solution:
fun main() {
val list = List<Int>(37) { it }
list.average() pipe ::println
}
You get:
18.0
Challenge 9.4: Last
How would you implement the lastFold function for a List<T> that returns the last element using fold or foldRight? What about firstFold?
Challenge 9.4 solution
One possible implementation is:
fun <T> List<T>.lastFold(): T? =
fold(null as T?) { _, item -> item }
In this case, it’s curious to see how the initial value matters only if the receiver is empty. Otherwise, only the last item matters. To test how it works, run this code:
fun main() {
val list = List<Int>(37) { it }
list.lastFold() pipe ::println
val empty = emptyList<Int>()
empty.lastFold() pipe ::println
}
Getting:
36
null
Note that to get the first element, you just need to use foldRight instead, like this:
fun <T> List<T>.firstFold(): T? =
foldRight(null as T?) { item, acc -> item }
To test this, just run this code:
val list = List<Int>(37) { it }
list.firstFold() pipe ::println
And you get:
0