Comonad
API Documentation: Comonad
Comonad is a Functor and provides duals of the Monad pure
and flatMap functions. A dual to a function has the same types but the
direction of the arrows are reversed. Whether or not that is useful, or even possible,
depends on the particular type. For a more formal definition of duality, please
refer to https://ncatlab.org/nlab/show/duality.
extract
Monads have pure from Applicative which gives you the ability to wrap
a value A using the type constructor giving an F[A]. Comonad has
extract which instead takes an F[A] and extracts the A. Therefore, to be
able to implement extract we must have a type of which we are certain
we can get an A from an F[A]. For example we cannot always get an A
from a List[A] because if the list is empty there is nothing to get.
For the same reason, Option doesn't have a Comonad instance, because we
cannot always get an A from an Option, it may be empty too.
Some examples that we can implement Comonad for include OneAnd, Tuple2
and the "non empty" collections.
First some imports.
import cats._
import cats.data._
import cats.syntax.all._
import cats.instances.list._
NonEmptyList has a Comonad instance and its implementation of extract
simply returns the head element of the list, which we know we will always
have.
NonEmptyList.of(1,2,3).extract
// res0: Int = 1
coflatMap
coflatMap is the dual of Monad's flatMap. While flatMap allows us to chain
together operations in a monadic context, coflatMap takes a value in some context
F[A] and a function F[A] => B and returns a new value in a context F[B].
The default implementation of coflatMap for NonEmptyList will pass the supplied
function with the whole list, then the tail of that, then the tail of that and so
on. This is illustrated below.
NonEmptyList.of(1,2,3,4,5).coflatMap(identity)
// res1: NonEmptyList[NonEmptyList[Int]] = NonEmptyList(
// head = NonEmptyList(head = 1, tail = List(2, 3, 4, 5)),
// tail = List(
// NonEmptyList(head = 2, tail = List(3, 4, 5)),
// NonEmptyList(head = 3, tail = List(4, 5)),
// NonEmptyList(head = 4, tail = List(5)),
// NonEmptyList(head = 5, tail = List())
// )
// )
CoflatMap
While FlatMap is a weaker version of Monad that doesn't have the pure function,
CoflatMap is a Comonad without the extract function. There are many instances
of type classes in Cats that implement CoflatMap but not Comonad.
For example we cannot write extract for Option[A] because there's no way to
pull an A out of nowhere if the Option is empty.
def extract[A](fa : Option[A]): A = fa match {
case Some(a) => a
case None => ??? // What now?
}
Another example is List. Remember we cannot write extract for list because lists
can be empty, but we can implement the coflatMap and it works identically to the
one shown above for NonEmptyList.
List(1,2,3,4,5).coflatMap(identity)
// res2: List[List[Int]] = List(
// List(1, 2, 3, 4, 5),
// List(2, 3, 4, 5),
// List(3, 4, 5),
// List(4, 5),
// List(5)
// )