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)
// )