Abstraction for type with one hole that allows:
- map over (extends Functor)
- get current value
- duplicate one layer of abstraction It is dual to Monad (Monad allow to put value in and collapse one layer).
trait CoflatMap[F[_]] extends Functor[F] {
def coflatMap[A, B](fa: F[A])(f: F[A] => B): F[B]
}
trait Comonad[C[_]] extends CoflatMap[C] {
def extract[A](ca: C[A]): A // counit
def duplicate[A](ca: C[A]): C[C[A]] // coflatten
def extend[A, B](ca: C[A])(f: C[A] => B): C[B] = map(duplicate(ca))(f) // coflatMap, cobind
}
- Implementations:
CoflatMap/Cobind: Cats, Scalaz 7, Scalaz 8
Comonad: Cats, Scalaz 7, Scalaz 8, Haskell, Purescript - Verified implementations: statebox/idris-ct, agda-categories
If we define extract and extend:
fa.extend(_.extract) == fa
fa.extend(f).extract == f(fa)
fa.extend(f).extend(g) == fa.extend(a => g(a.extend(f)))
If we define comonad using map, extract and duplicate:
3. fa.duplicate.extract == fa
4. fa.duplicate.map(_.extract) == fa
5. fa.duplicate.duplicate == fa.duplicate.map(_.duplicate)
And if we provide implementation for both duplicate and extend:
6. fa.extend(f) == fa.duplicate.map(f)
7. fa.duplicate == fa.extend(identity)
8. fa.map(h) == fa.extend(faInner => h(faInner.extract))
The definitions of laws in Cats src Comonad , Cats src Coflatmap and Haskell Control.Comonad.
- Derived methods:
def extend[A, B](ca: C[A])(f: C[A] => B): C[B] = map(duplicate(ca))(f) // coFlatMap
Method extend can be use to chain oparations on comonads - this is called coKleisli composition.
-
Implementations of comonad can be done for: None empty list, Rose tree, Identity
-
Resources
- Scala Comonad Tutorial, Part 1 - Rúnar Bjarnason (blog post)
- Scala Comonad Tutorial, Part 2 - Rúnar Bjarnason (blog post)
- Streams for (Co)Free! - John DeGoes: (video)
- Life Is A Comonad - Elias Jordan (video) (blog post) (reddit)
- Conway's Game Of Life Using Representable And Comonads - Chris Penner (blog post)
- (Haskell) Getting a Quick Fix on Comonads - Kenneth Foner (video)
- Haskell libraries using Comonads
- (Haskell) Monads from Comonads - Edward Kmett (blog post)
- (Haskell) Monad Transformers from Comonads - Edward Kmett (blog post)
- (Haskell) More on Comonads as Monad Transformers - Edward Kmett (blog post)
- (Haskell) The Cofree Comonad and the Expression Problem - Edward Kmett (blog post)
- (Haskell) Comonads as Spaces - Phil Freeman (blog post)
- (Purescript) PS Unscripted - Comonads for UIs - Phil Freeman (video)
- A Real-World Application with a Comonadic User Interface - Arthur Xavierpdf
- (Haskell) Cofun with cofree comonads - Dave Laing (slides, video, code)
- (Haskell) matchingSub is Comonadic (obviously!) - geophf blog post
- (Haskell) Realized Constants are Comonadic - geophf blog post
Wrap value of type A with some context R.
case class CoReader[R, A](extract: A, ask: R) {
def map[B](f: A => B): CoReader[R, B] = CoReader(f(extract), ask)
def duplicate: CoReader[R, CoReader[R, A]] = CoReader(this, ask)
}
- Resources
- Scala Comonad Tutorial, Part 1 - Rúnar Bjarnason (blog post)
- (Haskell) (src Control-Comonad-Env)
- (Haskell) CoReader x CoState == Silver Bullet - @geophf (blog post)
It is like Writer monad, combines all logs (using Monid) when they are ready.
case class Cowriter[W, A](tell: W => A)(implicit m: Monoid[W]) {
def extract: A = tell(m.empty)
def duplicate: Cowriter[W, Cowriter[W, A]] = Cowriter( w1 =>
Cowriter( w2 =>
tell(m.append(w1, w2))
)
)
def map[B](f: A => B) = Cowriter(tell andThen f)
}
- Resources
- Scala Comonad Tutorial, Part 1 - Rúnar Bjarnason (blog post)
Combine power of Monad and Comonad with additiona laws that tie together Monad and Comonad methods
trait Bimonad[T] extends Monad[T] with Comonad[T]
- They simplify resolution of implicits for things that are Monad and Comonad
Resources: