Most monadic functions take pure arguments and return a monadic value. But there are a few that need also monadic arguments, for example:
mplus :: (MonadPlus m) => m a -> m a -> m a
finally :: IO a -> IO b -> IO a
forkIO :: m () -> m ThreadId
-- | From Control.Monad.Parallel
forkExec :: m a -> m (m a)
Each of them seems to bring up a different problem and I can't get a grasp of a generic way how to encode such actions using free monads.
In both
finally
andforkIO
the problem is that the monadic argument is of a different type than the result. But for free one would need them to be of the same type, asIO a
gets replaced by the type variable of the encoding type, likedata MyFunctor x = Finally x x x
, which would only encodeIO a -> IO a -> IO a
.In From zero to cooperative threads in 33 lines of Haskell code the author uses
Fork next next
to fist implementcFork :: (Monad m) => Thread m Bool cFork = liftF (Fork False True)
and then uses it to implement
fork :: (Monad m) => Thread m a -> Thread m ()
where the input and output have different types. But I don't understand if this was derived using some process or just an ad-hoc idea that works for this particular purpose.
mplus
is in particular confusing: a naive encoding asdata F b = MZero | MPlus b b
distributes over
>>=
and a suggested better implementation is more complicated. And also a native implementation of a freeMonadPlus
was removed from free.In freer it's implemented by adding
data NonDetEff a where MZero :: NonDetEff a MPlus :: NonDetEff Bool
Why is
MPlus
NonDetEff Bool
instead ofNonDetEff a a
? And is there a way how to make it work withFree
, where we need the data type to be a functor, other than using the CoYoneda functor?- For
forkExec
I have no idea how to proceed at all.
finally
would be more obvious if you first try to encode the "most general" exception functions (throw
,catch
?). The type offinally
is the same as<*
.. so you theoretically there is no reason a function of this type can't be encoded (maybe just not directly..). – user2407038