8

In Haskell, Given a monad m, there is mfix :: (a -> m a) -> m a that computes the fixed-point of a monadic computation.

Dually, given a comonad w, there is cofix :: w (w a -> a) -> a that computes the fixed-point of a comonadic computations.

Now suppose that I have a program that uses both a monad m and a comonad w that are related by a distributivity law distr :: w (m a) -> m (w a) of the comonad over the monad. Is it possible to combine mfix and cofix into a function of type w (w a -> m a) -> m a that would compute the fixpoint of monadic and comonadic computations?

Bob
  • 1,713
  • 10
  • 23
  • Do you have an example `w (w a -> m a)` argument to test our answer with? Also have you considered specializing `cofix` with `a = m b`? It has a slightly different type than what you asked but can still be quite useful. – Li-yao Xia Mar 15 '23 at 18:10
  • @Li-yaoXia You might consider the non-empty list comonad for `w` and the Maybe monad for `m` as in the Section 6 of [this paper](https://cs.ioc.ee/~tarmo/papers/essence.pdf#page=25). – Bob Mar 15 '23 at 18:26

1 Answers1

0

Yes, in fact, we can define a reasonable wmfix :: w (w a -> m a) -> m a in two different ways; one starting from cofix, and the other from mfix.

If we normalize both approaches, they become roughly the same idea: turn some f : w (w a -> m a) into a suitable b -> b, take the fixpoint, and dig out the m a from b. For the cofix-like approach we can take

wmfix1 f = cofix (fmap ((. distr) . (=<<)) f)

or directly

wmfix1 f = extract (fix g) where
    g :: w (m a) -> w (m a)
    g = extend (\x -> distr x >>= extract f)

If we start from mfix instead, we get

wmfix2 f = fmap extract (extract (fmap (mfix . (distr .)  . extend) f))

or

wmfix2 f = fmap extract (fix h) where
    h :: m (w a) -> m (w a)
    h = (distr . extend (extract f) =<<)

Unfortunately, it doesn't look like these are same in general, which requires a condition like

fmap extract (distr (extend f x)) = f x

This condition does seem to hold for the w = NonEmpty, m = Maybe case you mentioned.

S.Klumpers
  • 410
  • 3
  • 14
  • You use `fix` in both definitions. Can they be reformulated to use `mfix` and `cofix` instead of `fix`? – Bob Mar 17 '23 at 11:55
  • 1
    Yes, I omitted those because I found they looked less nice (and `mfix`/`cofix` are `fix in disguise anyway), but I'll add them for completeness – S.Klumpers Mar 17 '23 at 12:06
  • Allow me to challenge you a bit further. But Isn't there an answer that use both `mfix` and `cofix`? – Bob Mar 17 '23 at 12:17
  • 2
    Well, not to say that you couldn't, but you wouldn't expect to need it; if `fix` is a bare fixpoint, then `mfix` and `cofix` are fixpoints which "go under" some structure, so `wmfix` is morally "just" a fixpoint that goes under two structures at the same time, not a fixpoint of a fixpoint. We also expect that if `m` or `w` is `Identity` then `wmfix` should reduce to `cofix` or `mfix` which it does right now (provided the condition), but if you used both, you would be left with two `fix`es – S.Klumpers Mar 17 '23 at 12:37
  • 1
    Your definitions both have the right type. Now it remains to see if they have the right semantics (A green flag is that they reduce to cofix/mfix with the Identity comonad/monad). For `fix`, you have that `fix f = f (fix f)`. Anything similar for your `wmfix1` and `wmfix2`? – Bob Mar 17 '23 at 13:52
  • If you ask me, `fix f = f (fix f)` essentially derives the semantics from the definition. Likewise, while hackage lists laws for MonadFix, it looks like these are trivial when `mfix f = fix (>>= f)`. So, what would you understand as the semantics for `wmfix`, if not `wmfix f` = "`f` applied to `wmfix f`"? (In my answer, I give two interpretations (`g` and `h`) for "apply `f`"). – S.Klumpers Mar 20 '23 at 14:20
  • "`wmfix f` equals `f` applied to `wmfix f`" sounds good but why are there two interpretations for "applied to"? Why are they valid interpretations? And, when the condition does not hold, which one should I choose? – Bob Mar 24 '23 at 11:50