0

I am struggeling with the function instance of monads (who does not?). Monads may depend on the value of the previous monadic computation. What does this exactly mean for the function monad?

When we look at the bare implementations of applicative/monad instances

ap   f g x = f x (g x)
bind f g x = f (g x) x

it seems as if the result of g x constitutes this previous value. However, I recently stumbled upon the following exmaple:

mainA = (\x y z -> (x,y,z)) <$> (2/) <*> (+1) <*> (\x -> x * x)
mainM = (\x -> (\y -> (\z w -> if w == 0 then (0,y,z) else (x,y,z)) =<< (\x -> x * x)) =<< (+1)) =<< (2/)
mainA 0 -- (Infinity,1.0,0.0)
mainM 0 -- (0.0,1.0,0.0)

Since a monadic action has to return a monad we have this additional lambda w -> ... within mainM's definition. This gives us access to the initial value that is passed to the overall computation. Given bind f g x = f (g x) x, does this mean the previous value for the function instance is x not the result of g x?

Sorry for the confusion!

  • 1
    Isn't the "previous value" stored in `z`? The "callback" receives the previous value *first* and the "ambient context" *second*. Does thinking of `bind` as `bind f g = \x -> f (g x) x` (clearly putting the "function result" on the right side) help things? – danidiaz Feb 08 '20 at 12:32
  • Intuitively yes, `z` is the prev val. However, I can access `z` in the applicative computation as well, right? So it cannot be `z`. It seems as if the previous value in the function context means the environment `w`, which is read only. I am not sure if this makes sense at all. –  Feb 08 '20 at 12:47
  • I guess I've made a little progress (or am even further on the wrong track). `bind f g x = f (g x) x` is just a function and thus a value. `mainM` on the other hand treats `(=<<)` like a computation with a read-only environment. So it makes probably no sense to compare the two. Help in this matter still appreciated. –  Feb 08 '20 at 13:07
  • @bob This `bind` and function monad `(=<<)` are the same thing, so it does make sense to compare them. They both are functions, and values. You can substitute one for the other just fine. – duplode Feb 08 '20 at 13:18
  • 1
    @bob "*I can access `z` in the applicative computation as well*" - actually you just access it via the functor instance (`<$>` = `fmap`). But that's not what distinguishes applicatives from monads: the point of the monad is that you can *choose* between different monadic computations depending on the previous value, not just that you can access it. Of course, your example is applicative and doesn't really *use* that feature, you even complain that "*a monadic action has to return a monad*". – Bergi Feb 08 '20 at 13:35
  • @Bergi _choose the next monadic computation_ - that was my misunderstanding. Post it as an answer and I'll accept it. –  Feb 08 '20 at 14:10
  • By "monadic action", do you mean a Kleisli arrow (function with type `a -> m b`)? The value `m b` itself is typically referred to as an action (at least, when talking about `IO`). – chepner Feb 08 '20 at 14:25

1 Answers1

1

In this one-liner spelling, mainM's definition is kinda hard to follow, so I'll begin by adding some indentation:

mainM =
  (\x ->
    (\y -> 
      (\z w -> if w == 0 then (0,y,z) else (x,y,z))
       =<< (\x -> x * x))
    =<< (+1))
  =<< (2/)

Since a monadic action has to return a monad we have this additional lambda w -> ... within mainM's definition. This gives us access to the initial value that is passed to the overall computation.

That's pretty much correct. It might be slightly easier to see what's going on if we don't write that function as a function of two arguments (a function of two arguments is a function of one arguments that returns a function):

mainM =
  (\x ->
    (\y -> 
      (\z ->
        \w -> if w == 0 then (0,y,z) else (x,y,z))
       =<< (\x -> x * x))
    =<< (+1))
  =<< (2/)

So the results of applying (2/), (+1) and (\x -> x * x) to the initial parameter/environment are bound to x, y and z, respectively, and the three of them are used to obtain the final result. If we felt like using w consistently for the environment, the definition would look like this:

mainM =
  (\x ->
    (\y -> 
      (\z ->
        \w -> if w == 0 then (0,y,z) else (x,y,z))
       =<< \w -> w * w)
    =<< \w -> w + 1)
  =<< \w -> 2 / w

Or, using (>>=) instead of (=<<):

mainM =
  (\w -> 2 / w) >>= \x ->
  (\w -> w + 1) >>= \y ->
  (\w -> w * w) >>= \z ->
  \w -> if w == 0 then (0,y,z) else (x,y,z)

Or, using do-notation:

mainM = do
  x <- \w -> 2 / w
  y <- \w -> w + 1
  z <- \w -> w * w
  \w -> if w == 0 then (0,y,z) else (x,y,z)

In any case, note that mainM isn't actually using the results of the previous computations (that is, x, y and z) to decide which computation to perform next (that is, which function of the environment to use). We can actually rewrite it using only applicative:

mainA' = (\w x y z -> if w == 0 then (0,y,z) else (x,y,z))
  <*> (2/) <*> (+1) <*> (\x -> x * x)

Using the results of a previous computation to decide on the next one would look more like this:

testM = (\x -> if x == 0 then \_ -> 0 else \w -> w / x) =<< subtract 1
GHCi> testM 0.99
-98.99999999999991
GHCi> testM 1
0.0
GHCi> testM 1.01
100.99999999999991

Still, the function/reader monad isn't a good illustration of the differences between Applicative and Monad, because (<*>) and (=<<) happen to be equivalent for it. With testM, for instance, we can simply pull the environment argument outside of the if-expression...

testM' = (\x w -> if x == 0 then 0 else w / x) =<< subtract 1

... thus getting something which is straightforward to rewrite using Applicative:

testA = (\w x -> if x == 0 then 0 else w / x) <*> subtract 1
duplode
  • 33,731
  • 7
  • 79
  • 150
  • By using the functor in `mainA` I didn't need the outer lambda explicitly. From that point on I was on the wrong track. This made me believe that monad/applicative differ in the value they have access to. This was silly, since a monad takes a kleisli arrow that returns a monad action. This very arrow gives us the ability to choose the next action depending on a previous value. Thank you for the clarification. Hopefully this is helpful for others too. –  Feb 08 '20 at 18:01