Functors define map which have type
trait Functor[F[_]] {
def map[A, B](f: A => B)(v: F[A]): F[B]
}
Monads are functors which support two additional operations:
trait Monad[M[_]] extends Functor[M] {
def pure[A](v: A): M[A]
def join[A](m: M[M[A]]): M[A]
}
Join flattens nested values e.g. if m
is List
then join
has type
def joinList[A](l: List[List[A]]): List[A]
If you have a monad m
and you map
over it, what happens if b
is the same monadic type? For example:
def replicate[A](i: Int, value: A): List[A] = ???
val f = new Functor[List] {
def map[A, B](f: A => B)(v: List[A]) = v.map(f)
}
then
f.map(x => replicate(x, x))(List(1,2,3)) == List(List(1), List(2,2), List(3,3,3))
This has type List[List[Int]]
while the input is a List[Int]
. It's fairly common with a chain of operations to want each step to return the same input type. Since List
can also be made into a monad, you can easily create such a list using join
:
listMonad.join(List(List(1), List(2,2), List(3,3,3))) == List(1,2,2,3,3,3)
Now you might want to write a function to combine these two operations into one:
trait Monad[M] {
def flatMap[A, B](f: A => M[B])(m: M[A]): M[B] = join(map(f)(m))
}
then you can simply do:
listMonad.flatMap(List(1,2,3), x => replicate(x, x)) == List(1,2,2,3,3,3)
Exactly what flatMap
does depends on the monad type constructor M
(List
in this example) since it depends on map
and join
.