4

The difference between monad and applicative is that the former can choose the next computation depending on a previous result:

(\x -> if x == 1 then (\_ -> []) else (\y -> (\z -> \w -> [x,y,z]) =<< sqr) =<< (+1)) =<< (+1) $ 0
--                      ^

(\w x -> if x == 1 then (\_ _ -> []) else (\y z -> [x,y,z])) <*> (+1) <*> (+1) <*> sqr $ 0
--                        ^^^

The monadic computation can short circuit the computation whereas with the applicative one we have to use the entire computational structure and run all effects no matter what input we provide.

Let's compare this to liftM:

liftM3 (\x -> if x == 1 then (\_ _ -> []) else (\y z -> [x,y,z])) (+1) (+1) sqr $ 0
--                             ^^^

This seems to be applicative style in disguise. Even if I replace the lift operator with a monadic applicator, the whole structure seems to lose its monadic property:

appM3 w f x g y h z =
  f(\x' -> g(\y' -> h(\z' -> w x' y' z') z) y) x

appM3 (\x -> if x == 1 then (\_ _ _ -> []) else (\y z _ -> [x, y, z])) (=<<) (+1) (=<<) (+1) (=<<) sqr $ 0
--                            ^^^^^

Does this mean that a proper monadic computation must always be encoded manually? I know do notation but the underlying mechanism seems similar to macro expansion (please correct me if this is nonsense), so it doesn't really refute my assumption.

  • What do you mean by “encoded manually” here? – Antal Spector-Zabusky Feb 13 '20 at 02:35
  • I find this question sort of puzzling. What do your first two example code blocks illustrate? Why do you say one short-circuits and the other doesn't? Both seem to short-circuit to me; e.g. `sqr = undefined` works for both. How did you come up with `appM3`? What does "monadic applicator" mean, and why does `appM3` qualify? (It doesn't even mention any monad operations!) What makes a monadic computation proper or improper? What does "encode" mean with respect to computations? Supposing `do`-notation were indeed essentially macro expansion, how would that be related to the rest of the question? – Daniel Wagner Feb 13 '20 at 05:45
  • @DanielWagner Well, I am still baffled by monads. It is difficult to ask the right questions then. Learning something people usually don't want to know the correct answers but why their assumptions/notions are wrong. This is because they don't understand the relevance of the correct answers yet. I plead guilty. –  Feb 13 '20 at 06:48
  • @DanielWagner `liftM3` short circuits because it ignores the result of `sqr`, that is lazyness kicks in. Still the whole computational structutre seems to be used. I think my observation holds. –  Feb 13 '20 at 06:50
  • @DanielWagner I work in a language without do notation and want to abstract from nested `>>=`/`=<<` calls. That is why I am experimenting with applicators. I call `appM3` an monadic applicator because it captures the structure of monadic computations. A left associative applicative applicator would look like `appA3 w f x g y h z = h(g(f w x) y) z`. –  Feb 13 '20 at 06:54
  • @DanielWagner A proper monadic computation is indeed a fuzzy expression. I mean the part that separates a monad computation from an applicative one. So an improper monadic computation is one that uses bind but semantically only needs applicative. –  Feb 13 '20 at 06:59
  • @AntalSpector-Zabusky I mean you cannot abstract from nested `=<<`/`>>=`calls without losing the property that distinguishes monad from applicative. Sure, there is do notation, which is rather a compiler technique though. –  Feb 13 '20 at 18:55
  • do notation is just a notation for (easier writing of) the underlying chain of nested binds. "essentially monadic" is the phrase that I've used sometimes to express what you mean here, I think. it doesn't make them improper. :) and yes, ``liftMN f m1 m2 ... mn == return f `ap` m1 `ap` m2 `ap` ... `ap` mn``, where `ap == (<*>)` and `return == pure`. as the answer says, `ap` existed before there even was a `<*>`. – Will Ness Feb 13 '20 at 21:21
  • @WillNess Thanks Will. I've understood another layer of the monadic onion :D and I actually managed to create a monadic applicator based on local continuations in JS that somewhat alleviates the nesting problem. –  Feb 13 '20 at 21:45
  • 1
    nesting is the *solution*, to have the essentially monadic computations expressed as monadic values. i.e., essentially monadic computation is one that creates "next" monadic computation depending on the value computed by the "previous" monadic computation. this can only be expressed with nesting, since in the linear chain of ``ap``s each computation is independently defined (*before* the combined computation "runs"). but in a `>>=` combination, the pure function on its right works on the value computed by comp. on its left *after* that comp. on the left "ran". – Will Ness Feb 13 '20 at 21:50
  • @WillNess In a lang with a less elegant and terse syntax like JS monadic computations quickly get confusing. Therefore I'm looking for an abstraction to untangle such code. [See this comparison of explicit and abstracted monadic chains in JS](https://repl.it/repls/UltimateFlatCpu). It is not an ideal solution though. –  Feb 14 '20 at 09:48
  • I'll look at it when I get the chance, thanks (although I'm JS-illiterate :) ). In Haskell actually the `do` notation provides such untangling abstract syntax. – Will Ness Feb 14 '20 at 14:59

1 Answers1

10

You're right. liftM is just fmap, and liftM2 is just liftA2. Their existence is a historical artifact still fossilized in the standard libraries, rather than something that actually requires the power of the monadic bind operation.

Previous versions of the standard libraries did not make Functor and Applicative superclasses of Monad. (If you go back far enough, Applicative didn't even exist.) So for the sake of utility, functions that did the same things were created for using with Monad. They survived the transition in the Applicative-Monad proposal because they remain useful for one purpose - if you are implementing all the classes by hand, you can use them to implement the type's Functor and Applicative instances in terms of the Monad code.

Carl
  • 26,500
  • 4
  • 65
  • 86