L.
Appendix L: Chapter 13 Exercise Solutions
Written by Massimo Carli
Exercise 13.1
How would you make the Optional<T> data type you created in Chapter 9, “Data Types”, a monad?
Exercise 13.1 solution
In Chapter 9, “Data Types” you implemented the Optional<T> type like this:
sealed class Optional<out T> {
companion object {
@JvmStatic
fun <T> lift(value: T): Optional<T> = Some(value)
@JvmStatic
fun <T> empty(): Optional<T> = None
}
}
object None : Optional<Nothing>()
data class Some<T>(val value: T) : Optional<T>()
To give Optional<T> the monad superpowers you initially need to make it a functor by adding the map function, implement it like this:
fun <A, B> Optional<A>.map(fn: Fun<A, B>): Optional<B> =
when (this) {
is Some<A> -> Some(fn(this.value))
is None -> None
}
In the chapter, you learned that you can implement the fish operator starting from the implementation of the optionalFlatten function, which is a function of type (Optional<Optional<T>>) -> Optional<T>. After that, you just need to implement optionalBind and then optionalFish in the same way you saw in the chapter.
A possible implementation for optionalFlatten is:
fun <T> Optional<Optional<T>>.optionalFlatten(): Optional<T> = when (this) {
is Some<Optional<T>> -> when (this.value) {
is Some<T> -> Optional.lift<T>(this.value.value)
is None -> Optional.empty()
}
is None -> Optional.empty()
}
Here, you just return Some<T> if both the Optionals are Some.
Now, you can implement optionalBind like this:
infix fun <B, C> Optional<B>.optionalBind(
g: Fun<B, Optional<C>>
): Optional<C> =
map(g).optionalFlatten()
Finally, you can implement optionalFish like this:
infix fun <A, B, C> Fun<A, Optional<B>>.optionalFish(
g: Fun<B, Optional<C>>
): Fun<A, Optional<C>> = { a: A ->
this(a).optionalBind(g)
}
Exercise 13.2
What’s the relation between the fish operator, >=>, and flatMap? Can you express the latter in terms of the former for Optional<T>?
Exercise 13.2 solution
The >=> operator for Optional<T> has type:
((A) -> Optional<B>, (B) -> Optional<C>) -> (A) -> Optional<C>
The flatMap has type (Optional<A>, (A) -> Optional<B>) -> Optional<B>.
A possible way to implement flatMap is the following:
fun <A> Optional<A>.lift(value: A): Optional<A> =
Optional.lift(value) // 1
fun <A, B> Optional<A>.flatMap(fn: Fun<A, Optional<B>>): Optional<B> =
map(::lift optionalFish fn).optionalFlatten() // 2
In this code, you:
- Define
liftas an extension function ofOptional<A>. Here, you simply invoke theliftyou defined as a static function. - Implement
flatMapusingoptionalFishfor creating a function composingliftof type(A) -> Optional<A>andgof type(A) -> Optional<B>, getting a function of type(A) -> Optional<B>. If you use the function as a parameter formap, you get a value of typeOptional<Optional<B>>that you know how to flatten usingoptionalFlatten.