Solutions to Scala with Cats: Chapter 4
April 4, 2023These are my solutions to the exercises of chapter 4 of Scala with Cats.
Table of Contents
- Exercise 4.1.2: Getting Func-y
- Exercise 4.3.1: Monadic Secret Identities
- Exercise 4.4.5: What is Best?
- Exercise 4.5.4: Abstracting
- Exercise 4.6.5: Safer Folding using Eval
- Exercise 4.7.3: Show Your Working
- Exercise 4.8.3: Hacking on Readers
- Exercise 4.9.3: Post-Order Calculator
- Exercise 4.10.1: Branching out Further with Monads
Exercise 4.1.2: Getting Func-y
We have pure
and flatMap
to define map
. We want to start from an F[A]
and get to an F[B]
from a function A => B
. As such, we want to call
flatMap
over the value. We can’t use func
directly, though. However, we can
produce a function that would lift our value to an F
using pure
(a =>
pure(func(a))
):
Exercise 4.3.1: Monadic Secret Identities
pure
, map
and flatMap
for Id
can be implemented as follows:
Since Id[A]
is just a type alias for A
, we can notice that we avoid all
boxing in the implementations and, due to that fact, flatMap
and map
are
identical.
Exercise 4.4.5: What is Best?
The answer depends on what we are looking for in specific instances, but some things that the previous examples for error handling don’t cover are:
- We can’t accumulate errors. The proposed examples all fail fast.
- We can’t tell exactly where the error was raised.
- It’s not easy to do error recovery.
Exercise 4.5.4: Abstracting
A possible implementation for validateAdult
is the following:
If age
is greater than or equal to 18, we summon an Applicative
for F
(which we must have in scope due to MonadError
) and lift the age
to F
. If
age
is less than 18, we use the MonadError
instance we have in scope to lift
an IllegalArgumentException
to F
.
Exercise 4.6.5: Safer Folding using Eval
One way to make the naive implementation of foldRight
stack safe using Eval
is the following:
We defer the call to the recursive step and then map over it to apply fn
, all
within the context of Eval
.
Exercise 4.7.3: Show Your Working
A possible rewrite of factorial
so that it captures the log messages in a
Writer
is the following:
We can show that this allows us to reliably separate the logs for concurrent
computations because we have the logs for each instance captured in each
Writer
instance:
Exercise 4.8.3: Hacking on Readers
To create a type alias for a Reader
that consumes a Db
we want to fix the
first type parameter of Reader
to Db
, while still leaving the result type as
a type parameter:
The findUsername
and checkPassword
functions can be implemented as follows:
The checkLogin
method can be implemented as follows:
We are making use of the findUsername
and checkPassword
methods. There are
two scenarios in which checkLogin
can return a false
for a given Db
: when
the username doesn’t exist and when the password doesn’t match.
Exercise 4.9.3: Post-Order Calculator
A possible implementation of evalOne
with no proper error handling is the
following:
We’re not told which operands to support, so I assumed at least +
, *
, -
and /
.
For the evalAll
implementation, we’re not told what to do in case the input is
empty. I assumed it would be OK to just have an exception thrown (since that was
the case before), and relied on reduce
over the evalOne
calls:
The evalInput
method can rely on a call to evalAll
after splitting the
input by whitespaces:
Exercise 4.10.1: Branching out Further with Monads
One implementation of Monad
for Tree
is the following:
However, tailRecM
isn’t tail-recursive. We can make it tail-recursive by
making the recursion explicit in the heap. In this case, we’re using two mutable
stacks: one of open nodes to visit and one of already visited nodes. On that
stack, we use None
to signal a non-leaf node and a Some
to signal a leaf
node.