Continuing from image editing with Functors, we will now understand Applicative by editing images with it.
Applicative
An applicative is composed of 2 things:
- A function which can wrap any value
A
with the contextF
.pure(a: A): F[A]
. So it must be thatpure
knows what theF
context means. - One of the two functions which are equivalent to each other (can rewrite one in terms of the other +
pure
):map2(a: F[A], b: F[B], f: (A, B) => C): F[C]
app(f: F[A => B], a: F[A]): F[B]
Below app
is used with the enhanced syntax:
This structure must obey the laws:
- identity
- Homomorphism
- Interchange
- Composition
These laws are a bit complicated, so again like Functors we will check them with Cats laws. The tests are more fine grained than only these 4 laws so if some fail we will get a better idea what and where to fix.
Map2
This says it can merge two contexts. The f
function knows how to merge A and B into a new value C,
but map2
knows (just like pure
) about what the F
context means, so it actually knows to merge both contexts together.
We can replace the word “context” with “effect”: map2 knows how to merge two effects.
Apply
This is more tricky to understand intuitively but given a function/program wrapped in the context/effect F
,
we can run the program with the value in a: F[A]
.
Not to be confused with the functor’s map(a: F[A], f: A => B): F[B]
- there is an extra F
over f
in apply.
Because apply
also gets as input two F
s and returns only one it means that it also knows how to merge these F
s (just like map2
)
Map2 vs Apply
So what is the difference? Formally none because we can write one in terms of the other + pure
So it’s clear that both map2
and apply
know how to merge F contexts/effects, but what about the difference in signatures?
ff: F[A => B]
from apply
is the partial application of f: (A, B) => C
from map2
with a: F[A]
.
It kinda holds a F[A]
inside. More precisely it’s a program which ran with input A will produce a F[B].
We’ll see this below on images.
Applicative is also a Functor
The complete name is: applicative functor.
But functor is not an applicative, functor does not have pure
so it does not know as much about what F
means.
Applicative on images
Given F is an Image it means we can combine any 2 images, pixel by pixel. Of course, if we can combine 2 images we can also combine any N images.
Original image A:
Original image B:
Max brightness between A and B
See through white
Disolve (creates a new image randomly taking color from A or B)
Recolor
Getting more intuition on Map2 and Apply
We define a program in our F (Image), which will generate a checkers-like pattern.
We will run this program giving it as input the original bird image, and we get
Now let’s create the same result with map2.
I’m going to repeat myself because this is awesome:
ff: F[A => B]
from apply
is the partial application of f: (A, B) => C
from map2
with a: F[A]
,
it kinda holds a F[A]
inside. We can even extract this core image from our checkerPattern program in order to see it by giving it the transparent color:
And one more fun example, combined with the bird image:
And the core image embedded in the F[A => B]
In the next post of this series we will see new capabilities implemented with the help of monads.