Continuing from image editing with Applicative, we will now understand Monad by editing images with it.
Monad
A monad is composed of 2 things:
- A function which can wrap any value
A
with the contextF
:pure(a: A): F[A]
(A monad is also an applicative) - One of the two sets of functions which are equivalent to each other (can rewrite one in term of the other)
flatMap(fa: F[A], f: A => F[B]): F[B]
- map +
flatten(fa: F[F[A]]): F[A]
Just like applicative, these functions also have to obey some laws:
Below flatMap
is used with the enhanced syntax:
- left-identity
- right-identity
- associativity
They can be checked automatically with cats-laws:
FlatMap
flatMap(fa: F[A], f: A => F[B]): F[B]
This takes a value in a context and a function f
from a value to a value in a context and returns its result.
It seems a bit strange that f
returns a F[B]
not just a B
(as the functor’s map), so unlike the functor this is not
just a simple transformation of F[A]
, it’s a transformation of A into B plus a new F
context/effect.
If applicative lets us merge effects then flatMap
provides the ability to chain new effects having the original wrapped value as input.
Flatten
flatten(fa: F[F[A]]): F[A]
This is merging effects. The difference from applicative, these are “serial” effects F[F[A]]
while applicative merges parallel effects in map2(a: F[A], b: F[B], f: (A, B) => C): F[C]
FlatMap vs Flatten and Map
These are equivalent
Monad on Images
Before and even after I implemented Monad on Images it was very unclear for a while how to do anything useful. What does it mean to have flatMap on Images ?
Given a color image, from each color build a new image and somehow return a single image back? Well apparently yes,
the new image is what the function f
draws on top of it, given the color at each location.
The implementation returns a new image, which for each pixel/Location returns the color of f’s Image[B] at that location, and since f has the power to build new images given a color, it can decide each location what color it has, it can be the original input color A or ignore it and provide its own different color.
Functor lets us modify one image pixel by pixel, Applicative lets us merge 2 or more images by merging the colors for each location, Monad allows us to draw another image on top of another image. We can say the f function is one which given a color A draws an Image. Let’s see some examples of these type of functions:
Draw a red circle on a bg Color background.
Draw c color stripes on a bg background.
Fill the top half with c color and the bottom half with bg color
Add a 10px black border on a c Color background
We can see these type of functions can easily be the primitive tools in an image drawing/editing program. We can draw lines, patterns, fill color, etc.
With the help of flatMap
we can draw them one on top of another, so it looks like Monads on Images add the drawing in layers feature.
Examples
Draw red circe on green background, add white stripes, draw another red circle, add black border.
Starting from a blue background draw a red circle but keep only the top half of the circle, the bottom is the original blue background.
Starting from a blue background draw stripes on it where the color of the stripes is the color taken fro, the bird image at the respective location.
So the actual color of the stripes is not blue, but the color from the bird image.
On the bird image, draw a red circle but only keep the bottom half,
then over draw stripes but with the color taken from the crayons image at corresponding location and here keep only the top half,
then over draw a red circle,
then over add a black border.
Drawing with functional programming, this is becoming addictive fast. We can do a lot of image editing already with Functors, Applicatives and Monads, but we are limited on operations on pixels at the same location:
- functors - apply color transformation on the original pixel,
- applicative - blend two color pixels together,
- monad - draw a completely new image given the original pixel color and blend back this new image in the original one.
For now its impossible to move/switch pixels around for example resize/skew/mirror the image or create swirl effects. We’ll try to do this in the next post.