Forget
Forget[R, A, B]
is a data type shaped like a Profunctor, that forgets the B
and returns value of type R
case class Forget[R, A, B](runForget: A => R)
Profunctor[_, _] is a type constructor that takes 2 type parameters. Forget[R, A, B]
is a type that has 3 type parameters, so we need
to fix one of the type parameters of Forget
in order to create an instance of Profunctor of Forget
. We can use Scala's type lambda syntax:
implicit def profunctorForget[R]: Profunctor[({ type P[A, B] = Forget[R, A, B] })#P] =
new Profunctor[({ type P[A, B] = Forget[R, A, B] })#P] {
override def dimap[A, B, C, D](fab: Forget[R, A, B])(f: C => A)(g: B => D): Forget[R, C, D] =
Forget(fab.runForget compose f)
}
or we can use the kind projector compiler plugin:
implicit def profunctorForget[R]: Profunctor[Forget[R, *, *]] = new Profunctor[Forget[R, *, *]] {
override def dimap[A, B, C, D](fab: Forget[R, A, B])(f: C => A)(g: B => D): Forget[R, C, D] =
Forget(fab.runForget compose f)
}
Now that the R
of the Forget
is fixed only A
and B
can vary, but the Forget
type does not use the type B
or rather,
forgets it. The type argument B
is used here as a phantom type, which means that it exists just to
satisfy some type constraints, but it does not have any effect on the runtime. in this example it serves as the second type argument for the Profunctor.
runForget: A => R
So a function A => R
where you can vary the A
forms a Profunctor.
Forget as a fold encoding
Forget
is a type that is used to implement folds. In order to encode a fold within a Profunctor, we need to come up with a type
that takes two type parameters, and encapsulates the notion of fold.
After fixing the R
in Forget[R, A, B]
, we got a type that takes two type parameters, and met the first requirement. Now we need
to address the second requirement.
foldMap
foldMap
is a function that takes a foldable structure S
and a mapping function f
from A
into R
, and then
combining them using the given Monoid[R]
instance.
def foldMap[S, A, R: Monoid](s: S)(f: A => R): R
We can implement all fold functions in terms of foldMap
def fold[S, A: Monoid](s: S): A = foldMap[S, A, A](s)(identity)
def foldLeft[S, A, R: Monoid](s: S)(r: R)(f: (R, A) => R): R = foldMap[S, A, R](s)(f(r, _))
def foldRight[S, A, R: Monoid](s: S)(r: R)(f: (A, R) => R): R = foldMap[S, A, R](s)(f(_, r))
The Forget
type wraps a foldMap
function runForget: A => R
within, thus making itself an appropriate
type that can form a Profunctor. The Monoid[R]
can be found in the apply
signature of Fold_[S, T, A, B]
:
abstract class Fold_[S, T, A, B] {
def apply[R: Monoid](forget: Forget[R, A, B]): Forget[R, S, T]
}
Let's see how foldMap
is actually implemented in Fold
def foldMap[R: Monoid](s: S)(f: A => R): R = self(Forget(f)).runForget(s)
The apply
function of Fold_[S, T, A, B]
takes a Forget[R, A, B]
and an implicit instance of Monoid[R]
and returns
a new Forget[R, S, T]
. In foldMap
we call the apply
function with a Forget
instance that wraps our fold
function R => A
, which gives us a new instance of Forget[R, S, T]
.
The Forget[R, S, T]
wraps a function S => A
and forgets the T
. In order to get an R
for the return type of foldMap
, we just need to
unwrap the Forget[R, S, T]
using runForget
and invoke the function with the supplied argument of S
.