I have been trying to write mfix down using Control.Arrow.loop. I came up with different definitions and would like to see which one is mfix's actual workalike.
So, the solution I reckon to be the right one is the following:
mfix' :: MonadFix m => (a -> m a) -> m a
mfix' k = let f ~(_, d) = sequenceA (d, k d)
in (flip runKleisli () . loop . Kleisli) f
As one can see, the loop . Kleisli's argument works for Applicative instances. I find it to be a good sign as we mostly have our knot-tying ruined by (>>=)'s strictness in the right argument.
Here is another function. I can tell that it is not mfix's total workalike, but the only case I found is not very natural. Take a look:
mfix'' k = let f ~(_, d) = fmap ((,) d) (return d >>= k)
in (flip runKleisli () . loop . Kleisli) f
As far as I understand, not every strict on the right-hand bind forces its argument entirely. For example, in case of IO:
GHCi> mfix'' ((return :: a -> IO a) . (1:))
[1,1,1,1,1,Interrupted.
So, I decided to fix this. I just took Maybe and forced x in Just x >>= k:
data Maybe' a = Just' a | Nothing' deriving Show
instance Functor Maybe' where
fmap = liftM
instance Applicative Maybe' where
pure = return
(<*>) = ap
instance Monad Maybe' where
return = Just'
Nothing' >>= k = Nothing'
Just' x >>= k = x `seq` k x
instance MonadFix Maybe' where
mfix f = let a = f (unJust' a) in a
where unJust' (Just' x) = x
unJust' Nothing' = errorWithoutStackTrace "mfix Maybe': Nothing'."
Having this on our hands:
GHCi> mfix ((return :: a -> Maybe' a) . (1:))
[1,1,1,1,1,Interrupted.
GHCi> mfix' ((return :: a -> Maybe' a) . (1:))
[1,1,1,1,1,Interrupted.
GHCi> mfix'' ((return :: a -> Maybe' a) . (1:))
Interrupted.
So, here are my questions:
- Is there any other example which could show that
mfix''is not totallymfix? - Are monads with such a strict bind, like
Maybe', interesting in practice? - Are there any examples which show that
mfix'is not totallymfixthat I have not found?
A small side note on IO:
mfix3 k' =
let
k = return . k'
f ~(_, d) = fmap ((,) d) (d >>= k)
in (join . flip runKleisli () . loop . Kleisli) f
Do not worry about all the returns and joins - they are here just to have mfix3's and mfix's types match. The idea is that we pass d itself instead of return d to the (>>=) on the right-hand. It gives us the following:
GHCi> mfix3 ((return :: a -> IO a) . (1:))
Interrupted.
Yet, for example (thanks to Li-yao Xia for their comment):
GHCi> mfix3 ((return :: a -> e -> a) . (1:)) ()
[1,1,1,1,1,Interrupted.
Edit: thanks to HTNW for an important note on pattern-matching in the comments: it is better to use \ ~(_, d) -> ..., not \ (_, d) -> ....