Unit 35: Loggable
Learning Objectives
After this unit, students should understand:
- why we need
flatMap
operation.
So far in the class, we have seen very general abstractions that support the flatMap
operation. But, it is not clear where this operation comes from, why is it fundamental, nor why is it useful1
In this unit, we are going to build a general abstraction step-by-step, get stuck at some point, and see how flatMap
comes to our rescue. Hopefully, through this exercise, you will get some appreciation of flatMap
.
Function Composition
Let's start with some methods that we wish to operate over int
. Let's use some trivial functions so that we don't get distracted by its details.
1 2 3 4 5 6 7 |
|
These methods are pure functions without side effects, they take in one argument and produce a result.
Just like mathematical functions, we can compose them together in arbitrary order to form more complex operations.
1 2 |
|
Loggable
with Pair
Suppose now we want to return not only an int
but some additional information related to the operation on int
. For instance, let's suppose we want to return a string describing the operation (for logging). Java does not support returning multiple values, so let's return a Pair
.
1 2 3 4 5 6 7 |
|
Now, we can't compose the methods as cleanly as before. This is because the return value of absWithLog
is a Pair<Integer, String>
but incrWithLog
accepts an int
as its parameter.
1 |
|
We will need to change our methods to take in Pair<Integer, String>
as the argument.
1 2 3 4 5 6 7 |
|
We can now compose the methods.
1 |
|
Loggable
Class
Let's do it in a more OO way, by writing a class to replace Pair
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
We can use the class above as follows:
1 2 |
|
Note that we can now chain the methods together to compose them. Additionally, the log messages get passed from one call to another and get "composed" as well.
Making Loggable
general
There are many possible operations on int
, and we do not want to add a method fooWithLog
for every function foo
. One way to make Loggable
general is to abstract out the int
operation and provide that as a lambda expression to Loggable
. This is what the map
method does.
1 2 3 |
|
We can use it like this:
1 |
|
We can still chain the methods together to compose them.
But, map
allows us to only apply the function to the value. What should we do to the log messages? Since the given lambda returns an int, it is not sufficient to tell us what message we want to add to the log.
To fix this, we will need to pass in a lambda expression that takes in an integer but returns us a pair of an integer and a string. In other words, it returns us a Loggable
. We call our new method flatMap
.
1 2 3 4 |
|
By making flatMap
take in a lambda that returns a pair of an integer and a string, Loggable
can rely on these lambda expressions to tell it how to update the log messages. Now, if we have methods like this:
1 2 3 4 5 6 7 |
|
We can write:
1 2 3 |
|
to now compose the methods incr
and abs
together, along with the log messages!
Making Loggable
More General
We started with an operation on int
, but our Loggable
class is fairly general and should be able to add a log message to any operation of any type. We can make it so by making Loggable
a generic class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
-
This note is inspired by The Best Introduction to Monad. Another excellent note on category theory is by Bartosz Milewski ↩