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?
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With