4
votes

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.

1
What do you mean by “encoded manually” here? - Antal Spector-Zabusky
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
@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. - Iven Marquardt
@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. - Iven Marquardt
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 aps 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

1 Answers

11
votes

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.