Понимание обозначений для простой монады Reader: a <- (* 2), b <- (+10), return (a + b) - PullRequest
3 голосов
/ 05 февраля 2020
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)  

Я пытаюсь понять эту монаду, раскручивая нотацию do, потому что я думаю, что нотация do скрывает то, что происходит.

Если я правильно понял, вот что происходит:

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

Теперь, если мы примем правило для >>=, мы должны понимать (*2) как h и (\a -> (+10) >>= (\b -> return (a+b))) как f. Применение h к w легко, давайте просто скажем, что это 2w (я не знаю, допустимо ли 2w в haskell, но просто для рассуждений, давайте оставим это так. Теперь мы должны применить f до h w или 2w. Ну, f просто возвращает (+10) >>= (\b -> return (a+b)) для спецификаций c a, что в нашем случае 2w, поэтому f (hw) равно (+10) >>= (\b -> return (2w+b)). Сначала мы должны получить то, что происходит с (+10) >>= (\b -> return (2w + b)), прежде чем, наконец, применить его к w.

Теперь мы переопределим (+10) >>= (\b -> return (2w + b)) с нашим правилом, поэтому h равно +10 и f равно (\b -> return (2w + b)). Давайте сначала сделаем h w. Мы получим w + 10. Теперь нам нужно применить f к h w. Мы получим (return (2w + w + 10)).

Так что (return (2w + w + 10)) - это то, что нам нужно применить до w в первые >>=, которые мы пытались развеять. Но я полностью растерялся и не знаю, что случилось.

Я так думаю? Это так сбивает с толку . Есть ли лучший способ думать об этом?

Ответы [ 3 ]

5 голосов
/ 05 февраля 2020

Вы забываете, что оператор >>= возвращает не просто f (h w) w, а \w -> f (h w) w. То есть он возвращает функцию, а не число.

При неправильной подстановке вы потеряли самый внешний параметр w, поэтому неудивительно, что он остается свободным в вашем конечном выражении.

сделать это правильно, вы должны полностью заменить тела функций для их вызовов, не отбрасывая вещи.

Если вы подставите самое внешнее >>=, вы получите:

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

Тогда, если Вы заменяете самый внутренний >>=, вы получаете:

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

Обратите внимание, что я использую w1 вместо w. Это сделано для того, чтобы избежать конфликтов имен позже, когда я объединю подстановки, потому что эти два w взяты из двух разных лямбда-абстракций, поэтому они являются разными переменными.

Наконец, подставим return:

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

Теперь вставьте эту последнюю замену в предыдущую:

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

И, наконец, вставьте это в самую первую замену:

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

И теперь, когда все замены конкурируют, мы можем уменьшить. Начните с применения самой внутренней лямбды \b -> ...:

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

Теперь примените новую внутреннюю лямбду \_ -> ...:

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

Теперь примените \a -> ...:

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

И наконец примените единственную оставшуюся лямбду \w1 -> ...:

\w -> w*2+w+10

И вуаля! Вся функция уменьшается до \w -> (w*2) + (w+10), как и ожидалось.

2 голосов
/ 05 февраля 2020

Во-первых, мы явно выписываем неявный аргумент в вашем определении,

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
  =
  ....

Затем, с

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

должно быть проще следовать и подставлять определения, строка для строка:

  ....
  =
    ( (*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)

Суть монады читателя заключается в применении к одному и тому же аргументу общего для всех вызовов.

1 голос
/ 05 февраля 2020

Интуитивно, каждый вызов функции в правой части <- получает дополнительный аргумент, который можно рассматривать как аргумент самого addStuff.

Принимать

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

и превратить его в

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

Это выглядит немного менее "странно", если вы используете экземпляр MonadReader для (->) r, который предоставляет ask в качестве способа получения прямого доступ к неявному значению.

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)
...