Is there a generic way to decompose the free comonad over a failure monad into a “values stream and final error”?

201 views Asked by At

The Cofree comonad is useful for iterating partial functions in a way that's polymorphic on the error type. Its coiter resembles forM-looping in an error monad, but it gathers the produced values in a pure/lazy manner and you only see an error at the end, down in the data struct.

For example, Cofree Identity (no failure permitted!) is an infinite stream, whereas Cofree Maybe is isomorphic to NonEmpty, and Cofree (Either e) a is basically (NonEmpty a, e) (list of successfull-iteration values plus an error that occurs at the end).

Now I wonder what's the best way to evaluate the results, without specific pattern-matching on a single error monad. Extracting all the values is very easy thanks to the Foldable instance (e.g. toList), but I'm not sure how to best get hold of the errors. It would be possible to exploit Foldable for that to just get rid of the values and leave the error part:

vals'n'err :: (Monad m, Foldable m)
          => Cofree m a -> (NonEmpty a, (m ()))
vals'n'err (a :< q) = case toList q of
        [] -> (a:|[], const () <$> q)
        l  -> first (pure a<>)
           $ foldr1 (\(bs,e) (cs,f) -> (bs<>cs, e>>f)) $ vals'n'err<$>l

but this feels a bit hackish. Is there a better solution?

1

There are 1 answers

4
freestyle On

I think it is bad transformation for large streams, because you can have space leak by laziness. But for small streams it maybe usable.

We can divide this transformation on two:

vals :: Foldable f => Cofree f a -> NonEmpty a
vals = NonEmpty.fromList . Foldable.toList

err :: Monad m => Cofree m a -> m b
err (_ :< m) = m >>= err

And then combine together:

vals'n'err :: (Monad m, Foldable m) => Cofree m a -> (NonEmpty a, m b)
vals'n'err w = (vals w, err w)