user5536315
user5536315

Reputation:

What is the previous value of a monadic computation in the function context?

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!

Upvotes: 0

Views: 149

Answers (1)

duplode
duplode

Reputation: 34398

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

Upvotes: 1

Related Questions