Unit 35: Loggable
Learning Objectives
After completing this unit, students should be able to:
- explain why naïve function composition breaks down when additional context (e.g. logging) is introduced
- construct a simple abstraction that combines a value with auxiliary information
- distinguish between
mapandflatMapin terms of what kinds of transformations they support - use
flatMapto compose computations that produce contextualized results - generalize a concrete abstraction into a type-parameterized one using Java generics
Overview
In earlier units, we learned how to compose pure functions and use higher-order operations such as map to build more complex behavior. This worked well as long as our functions only transformed values.
In practice, however, computations often need to carry additional context, such as log messages. Once this extra information is introduced, simple function composition breaks down and map is no longer sufficient.
In this unit, we start from a concrete logging example1 and gradually build an abstraction that restores composability. Through this process, we uncover why flatMap is needed, where it comes from, and how it allows us to compose context-carrying computations in a disciplined way.
Function Composition with Logging
In this unit, we are going to build a general abstraction step-by-step, reach a limitation, and see how flatMap resolves this issue. Through this exercise, you will gain an appreciation of flatMap.
Let's start with some methods that operate on int values. 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 each takes one argument and produces 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 no longer compose the logging 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.
| Loggable v0.1 | |
|---|---|
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 | |
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.
However, map only allows us to 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 | |
Note that the log from the new computation is prepended to the existing log to preserve execution order.
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.
| Loggable v0.2 (Generic with flatMap) | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
-
This note is inspired by The Best Introduction to Monad. Another excellent note on category theory is by Bartosz Milewski ↩