Understanding do notation for simple Reader monad: a <- (*2), b <- (+10), return (a+b)

孤者浪人 提交于 2020-02-24 07:56:05

问题


instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w  

import Control.Monad.Instances  

addStuff :: Int -> Int  
addStuff = do  
    a <- (*2)  
    b <- (+10)  
    return (a+b)  

I'm trying to understand this monad by unwiding the do notation, because I think the do notation hides what happens.

If I understood correctly, this is what happens:

(*2) >>= (\a -> (+10) >>= (\b -> return (a+b))) 

Now, if we take the rule for >>=, we must understand (*2) as h and (\a -> (+10) >>= (\b -> return (a+b))) as f. Applying h to w is easy, let's just say it is 2w (I don't know if 2w is valid in haskell but just for reasoning lets keep it this way. Now we have to apply f to h w or 2w. Well, f simply returns (+10) >>= (\b -> return (a+b)) for an specific a, which is 2w in our case, so f (hw) is (+10) >>= (\b -> return (2w+b)). We must first get what happens to (+10) >>= (\b -> return (2w + b)) before finally applying it to w.

Now we reidentify (+10) >>= (\b -> return (2w + b)) with our rule, so h is +10 and f is (\b -> return (2w + b)). Let's first do h w. We get w + 10. Now we need to apply f to h w. We get (return (2w + w + 10)).

So (return (2w + w + 10)) is what we need to apply to w in the first >>= that we were tyring to uwind. But I'm totally lost and I don't know what happened.

Am I thinking in the rigth way? This is so confusing. Is there a better way to think of it?


回答1:


You're forgetting that operator >>= doesn't return just f (h w) w, but rather \w -> f (h w) w. That is, it returns a function, not a number.

By substituting it incorrectly you lost the outermost parameter w, so it's no wonder it remains free in your final expression.

To do this correctly, you have to substitute function bodies for their calls completely, without dropping stuff.

If you substitute the outermost >>=, you will get:

(*2) >>= (\a -> ...) 
==
\w -> (\a -> ...) (w*2) w

Then, if you substitute the innermost >>=, you get:

\a -> (+10) >>= (\b -> return (a+b))
==
\a -> \w1 -> (\b -> return (a+b)) (w1 + 10) w1

Note that I use w1 instead of w. This is to avoid name collisions later on when I combine the substitutions, because these two ws come from two different lambda abstractions, so they're different variables.

Finally, substitute the return:

return (a+b)
==
\_ -> a+b

Now insert this last substitution into the previous one:

\a -> (+10) >>= (\b -> return (a+b))
==
\a -> \w1 -> (\b -> return (a+b)) (w1 + 10) w1
==
\a -> \w1 -> (\b -> \_ -> a+b) (w1 + 10) w1

And finally insert this into the very first substitution:

(*2) >>= (\a -> ...) 
==
\w -> (\a -> ...) (w*2) w
==
\w -> (\a -> \w1 -> (\b -> \_ -> a+b) (w1 + 10) w1) (w*2) w

And now that all substitutions are compete, we can reduce. Start with applying the innermost lambda \b -> ...:

\w -> (\a -> \w1 -> (\_ -> a+w1+10) w1) (w*2) w

Now apply the new innermost lambda \_ -> ...:

\w -> (\a -> \w1 -> a+w1+10) (w*2) w

Now apply \a -> ...:

\w -> (\w1 -> w*2+w1+10) w

And finally apply the only remaining lambda \w1 -> ...:

\w -> w*2+w+10

And voila! The whole function reduces to \w -> (w*2) + (w+10), completely as expected.




回答2:


First, we write out the implicit argument in your definition explicitly,

addStuff :: Int -> Int  
addStuff = do  
    a <- (*2)  
    b <- (+10)  
    return (a+b)
  =
addStuff :: Int -> Int  
addStuff x = ( do  
    a <- (*2)  
    b <- (+10)  
    return (a+b) ) x
  =
  ....

Then, with

    return x  =  const x  
    (f =<< h) w  =  f (h w) w      -- (f =<< h)  =  (h >>= f)

it should be easier to follow and substitute the definitions, line for line:

  ....
  =
    ( (*2) >>= (\a ->                     -- (h >>= f)  =
       (+10) >>= (\b -> 
         const (a+b) ) ) ) x
  =
    ( (\a ->                              --   =   (f =<< h)
       (+10) >>= (\b ->
         const (a+b) ) ) =<< (*2) ) x     -- (f =<< h) w  =
  =
      (\a ->
       (+10) >>= (\b ->
         const (a+b) ) )  ( (*2) x) x     --   =  f (h w) w 
  =
    ( let a = (*2) x in                   -- parameter binding
       (+10) >>= (\b ->                   
         const (a+b) ) )            x
  =
      let a = (*2) x in                   -- float the let 
      ((\b ->
         const (a+b) ) =<< (+10) )  x     -- swap the >>=
  =
      let a = (*2) x in
       (\b ->                             -- (f =<< h) w  =
         const (a+b) )  ( (+10) x)  x     --   =  f (h w) w
  =
      let a = (*2) x in
       (let b = (+10) x in                -- application
          const (a+b) )             x
  =
      let a = (*2)  x in                  -- do a <- (*2)
      let b = (+10) x in                  --    b <- (+10)
      const (a+b)   x                     --    return (a+b)

The essence of reader monad is application to same argument shared between all calls.




回答3:


Intuitively, each function call on the right-hand side of the <- is given an additional argument, which you can think of as the argument to addStuff itself.

Take

addStuff :: Int -> Int  
addStuff = do  
    a <- (*2)  
    b <- (+10)  
    return (a+b)  

and turn it into

addStuff :: Int -> Int  
addStuff x = let a = (*2) x
                 b = (+10) x
             in (a+b)

It looks a little less "strange" if you use the MonadReader instance for (->) r, which provides ask as a way to get direct access to the implicit value.

import Control.Monad.Reader

addStuff :: Int -> Int
addStuff = do
  x <- ask   -- ask is literally just id in this case
  let a = x * 2
  let b = x + 10
  return (a + b)


来源:https://stackoverflow.com/questions/60069630/understanding-do-notation-for-simple-reader-monad-a-2-b-10-return

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!