2

fmap for Monads often defaults to liftM:

liftM   :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1 = do { x1 <- m1; return (f x1) }

Yet, as one can see it uses binding (as the right hand desugars into m1 >>= \ x1 -> return (f x1)). I wonder whether such a fmap can be used for writing down mfix down for monads which have a strict >>= operator. To be more precise, I wonder about the following implementation with Control.Arrow.loop (again).

The monad I use is Identity, yet it forces whatever is inside it whenever binding takes place with seq.

newtype Id' a = Id' { runId' :: a } deriving ...

instance Functor Id' where
    fmap = liftM -- instead of `f <$> (Id' x) = Id' (f x)`. 

instance Applicative Id' where
    pure  = return
    (<*>) = ap

instance Monad Id' where
    return         = Id'
    (Id' x)  >>= k = x `seq` k x

instance MonadFix Id' where
    mfix f = let a = f (runId' a) in a

myMfix :: MonadFix m => (a -> m a) -> m a
myMfix k = let f ~(_, d) = ((,) d) <$> k d
           in (flip runKleisli () . loop . Kleisli) f

My intuition is yes, it can be used. I reckon that we run into problems only in two cases:

  1. k we apply mfix/myMfix to is strict.
  2. We apply mfix/myMfix to return.

Both cases are fairly simple and we do not expect any converging result in the first place. I believe that other cases could be forced to WHNF without forcing the feedback.

Is my intuition correct? If not, can one show an example where it fails?

Zhiltsoff Igor
  • 1,812
  • 8
  • 24

1 Answers1

1

Your definition of Id' isn't a valid monad as it fails the left-unit law:

  return undefined >= const (return 3)
= Id' undefined >>= const (return 3)
= undefined `seq` const (return 3) undefined
= undefined

which does not equal

  const (return 3) undefined
= return 3
= Id' 3

So the discussion of whether your definition of MonadFix for Id' is valid is rather moot, let alone the validity of whether the definition via the Kleisli construction is faithful.

Having said that, if you want to see whether a definition is a faithful implementation of mfix, you need to write down and prove the three laws of strictness, purity, and left-shrinking. If you start with an actual monad (instead of your Id' which fails that test), then you need to establish these three laws to be considered a valid monadic feedback operator. Without doing that work, it's impossible to say how the definition might generalize.

My hunch is that your intuition of definition via the Kleisli construction might very well hold for well-behaved arrows, i.e., those whose loop operator do satisfy the right-tightening law. However, it is well known that an arrow over the Kleisli category of a monad with a left-strict bind operator does not have loop operators that satisfy right tightening. (See Corollary 3.1.7 and the discussion in Section 6.4.1.)

So, any monad with a left-strict bind operator (such as [], Maybe, IO, strict state, etc.) will fail the test. But arrows that arise from monads like environment, lazy state, and those built out of monoids (a.k.a. writer monads) might work out.

alias
  • 28,120
  • 2
  • 23
  • 40
  • Thank you for your answer. Yet, what is a "Kleisli monad" you mention in the last but one extract? Moreover, when you are saying that my intuition does not work for some monads, does that mean we might **NOT** be able to extract the information we've got with `mfix` from `loop` for an according `Kleisli m`? – Zhiltsoff Igor Aug 26 '20 at 14:55
  • I don't understand which monad you're asking about in the first comment. Regarding your second comment: I mean think of any Haskell monad with a left-strict bind operator. (Maybe, IO, List, Strict state..) Construct the Kleisli arrow for it. That arrow will *not* possess a suitable loop operator. That is, you cannot give a definition of loop for them that satisfies the arrow-loop laws, as right-tightening would fail. So, there's really no corresponding `mfix` to extract. – alias Aug 26 '20 at 16:18
  • Regarding the first one: you are writing: "However, it is well known that arrows that arise from **Kleisli monads** with a left-strict bind operator do not have". What are Kleisli monads? Regarding the second one: even though some instances do not satisfy laws, it does not mean we wouldn't want to use fix-point(-like) combinator for those *monads*. The thing is if we try to apply our combinator to `(1:)`, we get **WHNF** right off the bat. The question is about the cases when we do not have that and how that might affect the feedback. – Zhiltsoff Igor Aug 26 '20 at 16:44
  • I see the confusion. I should've said "An arrow over the Kleisli category of a monad with a left-strict bind operator.." Hopefully that's clear now. Edited the text accordingly. – alias Aug 26 '20 at 16:56
  • Regarding non-law-abiding definitions of `mfix`/`loop` etc: You can surely define whatever function you want that you find useful. I can see a line of research that weakens `mfix`/`loop` laws to allow for more definitions. If you can come up with a more permissive abstraction that is still useful, that would be worthy of exploration. But that area of research is largely uncharted so far as I know. (And honestly, I'm not sure if it is terribly interesting.) – alias Aug 26 '20 at 17:00