I've been playing with Cofree, and can't quite grok it.

For example, I want to play with Cofree [] Num in ghci and can't quite get any interesting examples.

For example, if I construct a Cofree type:

let a = 1 :< [2, 3]

I would expect extract a == 1, but instead I get this error:

No instance for (Num (Cofree [] a0)) arising from a use of ‘it’
    In the first argument of ‘print’, namely ‘it’
    In a stmt of an interactive GHCi command: print it

And a type of:

extract a :: (Num a, Num (Cofree [] a)) => a

Can I get some simple examples, even trivial, for how to use Cofree with, say, functors: [], or Maybe, or Either, that demonstrates

  • extract
  • extend
  • unwrap
  • duplicate?

Cross Posted: https://www.reddit.com/r/haskell/comments/4wlw70/what_are_some_motivating_examples_for_cofree/

EDIT: Guided by David Young's comment, here are some better examples that show where my first attempts were misguided, however I'd still love some examples that can guide an intuition of Cofree:

> let a = 1 :< []
> extract a
    1
> let b = 1 :< [(2 :< []), (3 :< [])]
> extract b
    1
> unwrap b
    [2 :< [],3 :< []]
> map extract $ unwrap b
    [2,3]
share|improve this question
    
Num is a type class, not a type. Something like Cofree [] Int would be more appropriate. Also note, with this type, that the type of the constructor becomes (:<) :: Int -> [Cofree [] Int] -> Cofree [] Int, so the second argument should be a list of cofree values. A simple example would be let a = 1 :< []. Another small example would be let a = 1 :< [x], where x is another Cofree [] Int value. – David Young Aug 7 '16 at 18:16
    
@DavidYoung Awesome, that helped with a major chunk of my understanding, and I edited my post. I still miss an intuition of cofree, but I see now that 1 :< [2, 3] makes the 1, 2, 3 somehow get coerced to some funky type I don't know how to interpret in my mind, and I want the a in Cofree f a to just by Int, not some funky type. Thanks! – Josh.F Aug 7 '16 at 18:24
1  
I think the best way to build up intuition for Cofree is to try it out on a bunch of functors. Cofree Identity is an infinite stream. Cofree Maybe is a non-empty stream. Cofree ((->) b) is (a bad encoding of) a potentially-infinite Moore machine. Cofree [] is a rose tree, equivalent to the one in Data.Tree. – Edward KMETT Aug 7 '16 at 20:39
1  
Of course you cannot grok it. You have not even consumed it yet. – JK. Aug 9 '16 at 3:55
up vote 40 down vote accepted

Let's just recap the definition of the Cofree datatype.

data Cofree f a = a :< f (Cofree f a)

That's at least enough to diagnose the problem with the example. When you wrote

1 :< [2, 3]

you made a small error that's reported rather more subtly than is helpful. Here, f = [] and a is something numeric, because 1 :: a. Correspondingly you need

[2, 3] :: [Cofree [] a]

and hence

2 :: Cofree [] a

which could be ok if Cofree [] a were also and instance of Num. Your definition thus acquires a constraint which is unlikely to be satisfied, and indeed, when you use your value, the attempt to satisfy the constraint fails.

Try again with

1 :< [2 :< [], 3 :< []]

and you should have better luck.

Now, let's see what we've got. Start by keeping it simple. What's Cofree f ()? What, in particular, is Cofree [] ()? The latter is isomorphic to the fixpoint of []: the tree structures where each node is a list of subtrees, also known as "unlabelled rose trees". E.g.,

() :< [  () :< [  () :< []
               ,  () :< []
               ]
      ,  () :< []
      ]

Similarly, Cofree Maybe () is more or less the fixpoint of Maybe: a copy of the natural numbers, because Maybe gives us either zero or one position into which to plug a subtree.

zero :: Cofree Maybe ()
zero = () :< Nothing
succ :: Cofree Maybe () -> Cofree Maybe ()
succ n = () :< Just n

An important trivial case is Cofree (Const y) (), which is a copy of y. The Const y functor gives no positions for subtrees.

pack :: y -> Cofree (Const y) ()
pack y = () :< Const y

Next, let's get busy with the other parameter. It tells you what sort of label you attach to each node. Renaming the parameters more suggestively

data Cofree nodeOf label = label :< nodeOf (Cofree nodeOf label)

When we label up the (Const y) example, we get pairs

pair :: x -> y -> Cofree (Const y) x
pair x y = x :< Const y

When we attach labels to the nodes of our numbers, we get nonempty lists

one :: x -> Cofree Maybe x
one = x :< Nothing
cons :: x -> Cofree Maybe x -> Cofree Maybe x
cons x xs = x :< Just xs

And for lists, we get labelled rose trees.

0 :< [  1 :< [  3 :< []
             ,  4 :< []
             ]
     ,  2 :< []
     ]

These structures are always "nonempty", because there is at least a top node, even if it has no children, and that node will always have a label. The extract operation gives you the label of the top node.

extract :: Cofree f a -> a
extract (a :< _) = a

That is, extract throws away the context of the top label.

Now, the duplicate operation decorates every label with its own context.

duplicate :: Cofree f a -> Cofree f (Cofree f a)
duplicate a :< fca = (a :< fca) :< fmap duplicate fca  -- f's fmap

We can get a Functor instance for Cofree f by visiting the whole tree

fmap :: (a -> b) -> Cofree f a -> Cofree f b
fmap g (a :< fca) = g a :< fmap (fmap g) fca
    --                     ^^^^  ^^^^
    --                 f's fmap  ||||
    --                           (Cofree f)'s fmap, used recursively

It's not hard to see that

fmap extract . duplicate = id

because duplicate decorates every node with its context, then fmap extract throws away the decoration.

Note that fmap gets to look only at the labels of the input to compute the labels of the output. Suppose we wanted to compute output labels depending on each input label in its context? E.g., given an unlabelled tree, we might want to label each node with the size of its entire subtree. Thanks to the Foldable instance for Cofree f, we should be able to count nodes with.

length :: Foldable f => Cofree f a -> Int

So that means

fmap length . duplicate :: Cofree f a -> Cofree f Int

The key idea of comonads is that they capture "things with some context", and they let you apply context-dependent maps everywhere.

extend :: Comonad c => (c a -> b) -> c a -> c b
extend f = fmap f       -- context-dependent map everywhere
           .            -- after
           duplicate    -- decorating everything with its context

Defining extend more directly saves you the trouble of duplication (although that amounts only to sharing).

extend :: (Cofree f a -> b) -> Cofree f a -> Cofree f b
extend g ca@(_ :< fca) = g ca :< fmap (extend g) fca

And you can get duplicate back by taking

duplicate = extend id -- the output label is the input label in its context

Moreover, if you pick extract as the thing to do to each label-in-context, you just put each label back where it came from:

extend extract = id

These "operations on labels-in-context" are called "co-Kleisli arrows",

g :: c a -> b

and the job of extend is to interpret a co-Kleisli arrow as a function on whole structures. The extract operation is the identity co-Kleisli arrow, and it's interpreted by extend as the identity function. Of course, there is a co-Kleisli composition

(=<=) :: Comonad c => (c s -> t) -> (c r -> s) -> (c r -> t)
(g =<= h) = g . extend h

and the comonad laws ensure that =<= is associative and absorbs extract, giving us the co-Kleisli category. Moreover we have

extend (g =<= h)  =  extend g . extend h

so that extend is a functor (in the categorical sense) from the co-Kleisli category to sets-and-functions. These laws are not hard to check for Cofree, as they follow from the Functor laws for the node shape.

Now, one useful way to see a structure in a cofree comonad is as a kind of "game server". A structure

a :< fca

represents the state of the game. A move in the game consists either of "stopping", in which case you get the a, or of "continuing", by choosing a subtree of the fca. For example, consider

Cofree ((->) move) prize

A client for this server must either stop, or continue by giving a move: it's a list of moves. The game is played out as follows:

play :: [move] -> Cofree ((->) move) prize -> prize
play []       (prize :< _) = prize
play (m : ms) (_     :< f) = play ms (f m)

Perhaps a move is a Char and the prize is the result of parsing the character sequence.

If you stare hard enough, you'll see that [move] is a version of Free ((,) move) (). Free monads represent client strategies. The functor ((,) move) amounts to a command interface with only the command "send a move". The functor ((->) move) is the corresponding structure "respond to the sending of a move".

Some functors can be seen as capturing a command interface; the free monad for such a functor represents programs that make commands; the functor will have a "dual" which represents how to respond to commands; the cofree comonad of the dual is the general notion of environment in which programs that make commands can be run, with the label saying what to do if the program stops and returns a value, and the substructures saying how to carry on running the program if it issues a command.

For example,

data Comms x = Send Char x | Receive (Char -> x)

describes being allowed to send or receive characters. Its dual is

data Responder x = Resp {ifSend :: Char -> x, ifReceive :: (Char, x)}

As an exercise, see if you can implement the interaction

chatter :: Free Comms x -> Cofree Responder y -> (x, y)
share|improve this answer
1  
More on annihilation of matter and antimatter on Kmett's blog – Benjamin Hodgson Aug 7 '16 at 21:05
3  
@pigworker For the free-cofree "cancellation" to work, does it require some particular property from the category in which the functors live? – danidiaz Aug 7 '16 at 23:08
    
awesome because of data Cofree nodeOf label = label :< nodeOf (Cofree nodeOf label), explaining ties to co-kleisli arrows, and foldable, as well as the motivating example of the game (plus a useful exercise at the end). Thanks! – Josh.F Aug 8 '16 at 15:57
1  
@danidiaz You seem to be alluding to something. Just say what you mean. In my mind, Haskell gives me (too much, but at least) enough semantics to run pure total programs in the category of sets and functions. In that setting, it's important to distinguish inductive types (like free monads: the client has a terminating mission) from coinductive types (like cofree comonads: the server must stick around however long the client wants). My preprocessor lets me write codata as a keyword, for documentation. – pigworker Aug 9 '16 at 8:22
    
@pigworker Mind turning this into a Documentation? – wheaties Aug 18 '16 at 12:50

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.