We can describe monad, as the computational context, and monad implementation exactly preserves the meaning of that context.
For example Option - the context meaning is that the value might exist.
Given the Option datatype, the only implementation which makes sense is pure = some, flatMap f = {none => none; some x => f x }
As my understanding of monads, by following the type signatures - there is only one reasonable implementation for any monad. In other words, if you want to add some meaningful context to the value/computation, there is only one way to do it for any particular monad.
On the other hand, when it comes to comonad, it suddenly starts feeling completely strange, like there're many many ways to implement a comonad for a given type, and you might even give a certain meaning for every implementation.
Consider, NEL, with copure = head
. cojoin
is implemented via tails
, which perfectly satisfies the types. If we implement cojoin
by permutations
or as fa map (_ => fa) map f
it will not satisfy comonad laws.
But circling implementation is valid:
override def cobind[A, B](fa: NonEmptyList[A])(f: (NonEmptyList[A]) => B): NonEmptyList[B] = {
val n: NonEmptyList[NonEmptyList[A]] = fa.map(_ => fa).zipWithIndex.map { case (li , i ) =>
val(h: List[A], t: List[A]) = li.list.splitAt(i)
val ll: List[A] = t ++ h
NonEmptyList.nel(ll.head, ll.tail)
}
n map f
}
The reason of such ambiguity for Command, even with having the laws restricting us, as I see it, that if in Monad we restrict ourselves in some context (we kind of can't “create" new information), in Comonad, we are extending that context further( there are quite a ways to make a List of Lists from List), which gives us a way more possibilities of doing it. In my head metaphor is: for Monad, we are standing on the road and want reach some destination point A = hence there’re only meaningful shortest way to chose. In command, we are standing in A, and wanna go somewhere from it, so there’re way more ways of doing it.
So my question is - am I actually right? Can we implement command in a different ways, every time making another meaningful abstraction? Or only tails implemantation is reasonable beacause of the abstraction comonad supposes to bring in.