Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 684
leftaroundabout Avatar asked Dec 22 '16 12:12

leftaroundabout


1 Answers

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)
like image 111
freestyle Avatar answered Oct 15 '22 13:10

freestyle