Вопрос о писательской монаде как учил в ЛЯХ.(Как происходило добавление в журнал?) - PullRequest
2 голосов
/ 28 марта 2019

Я изучаю Haskell из учебника «Учим Haskell для великого добра», и я дошел до роли писательских монад . Вот пример, который я не могу понять.

import Control.Monad.Writer

logNumber :: Int -> Writer [String] Int  
logNumber x = writer (x, ["Got number: " ++ show x])  

multWithLog :: Writer [String] Int  
multWithLog = do  
   a <- logNumber 3  
   b <- logNumber 5  
   return (a*b) -- shouldn't return (3*5) result in (15,[]) ?

ghci> runWriter $ multWithLog
(15,["Got number: 3","Got number: 5"]) -- how did that happen? 

Я пытаюсь понять, как изменилось моноидальное значение w в монаде Writer w a, возвращаемое блоком do. Учебное пособие не вдавалось в подробности того, как проходил mappend.

Объявление типа для Writer и объявление экземпляра для Writer в качестве монады задаются в руководстве как

newtype Writer w a = Writer { runWriter :: (a, w) }  

instance (Monoid w) => Monad (Writer w) where  
    return x = Writer (x, mempty)
    (Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')  

если return x приводит к Writer (x, mempty) в соответствии с объявлением экземпляра и mempty для моноида [a] равно [], не должно return (a*b), что составляет return (3*5), оценка (15,[]) ?

ghci> return (15) :: Writer [String] Int
WriterT (Identity (15,[]))

Я дал указанную выше команду ghci, и она возвращает значение типа WriterT, кортеж содержит пустой список, как и ожидалось.

multWithLog :: Writer [String] Int
multWithLog = logNumber 3 >>= (\a -> 
              logNumber 5 >>= (\b -> 
              return (a*b)))

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

У меня сложилось впечатление, что >>= только извлек Int 3 из результата logNumber 3 и передал его (\a -> logNumber 5 ...etc.), который затем выполнял функцию logNumber с другим значением (5) и тд. Как эти операции привели к изменению [String] части монады Writer?

Ответы [ 2 ]

2 голосов
/ 28 марта 2019

Из кода, который вы разместили

(Writer (x,v)) >>= f =
   let (Writer (y, v')) = f x in Writer (y, v `mappend` v')

мы можем видеть, что действительно f вызывается только с аргументом x. Так что в logNumber 3 >>= \a -> ... переменная a действительно связана с 3.

Однако, >>= делает что-то после вызова f, а именно объединяет v с v'. В вашем примере v - это [String] от logNumber 3, что ["Got number: 3"]. Вместо v' приходит форма, оценивающая \a -> ... с a=3 и равная ["Got number: 5"].

mappend для списков - ++, что объединяет списки вместе. Следовательно мы получаем окончательный результат.

Позвольте мне быть немного неряшливым и пренебречь Writer упаковщиками. Мы получаем

return (a*b)
= (a*b, [])

logNumber 5 >>= \b -> return (a*b) 
= logNumber 5 >>= \b -> (a*b, [])
= (5, ["Got number: 5"]) >>= \b -> (a*b, [])
= (a*5, ["Got number: 5"] `mappend` [])
= (a*5, ["Got number: 5"])

logNumber 3 >>= \a -> logNumber 5 >>= \b -> return (a*b)
= logNumber 3 >>= \a -> (a*5, ["Got number: 5"])
= (3, ["Got number: 3"]) >>= \a -> (a*5, ["Got number: 5"])
= (3*5, ["Got number: 3"] `mappend` ["Got number: 5"])
= (15, ["Got number: 3", "Got number: 5"])

Интуитивно, мы можем притвориться, что значение в вашей монаде писателя является эффективным вычислением, которое возвращает значение (например, 3) и в качестве побочного эффекта добавляет несколько сообщений в список строк. Журнал всех таких сообщений невидим внутри монады (мы можем только добавить в журнал) и будет доступен только в самом конце, когда мы будем использовать runWriter для выхода из монадического контекста.

0 голосов
/ 31 марта 2019

Это должно объяснить это:

> runWriter (return 15) :: (Int, [String])
(15,[])                       -- == runWriter $ writer (15, mempty)

> runWriter (logNumber 3)
(3,["Got number: 3"])         -- == runWriter $ writer (3, ["Got number: 3"])

> runWriter (logNumber 5)
(5,["Got number: 5"])         -- == runWriter $ writer (5, ["Got number: 5"])

> runWriter (logNumber 3 >> logNumber 5)
(5,["Got number: 3","Got number: 5"])   -- == ["Got number: 3"] ++ ["Got number: 5"]

> runWriter (logNumber 3 >>         logNumber 5 >>         return 15        )
(15,["Got number: 3","Got number: 5"])  -- == ["Got number: 3"] ++ ["Got number: 5"] ++ []

> runWriter (logNumber 3 >>= (\_ -> logNumber 5 >>= (\_ -> return 15    ) ) )
(15,["Got number: 3","Got number: 5"])

> runWriter (logNumber 3 >>= (\i -> logNumber 5 >>= (\j -> return (i*j) ) ) )
(15,["Got number: 3","Got number: 5"])

И монадическое выражение последней строки эквивалентно блоку multWithLog do.

Обратите внимание на вложенность лямбда-функций: лямбда-функция

                                                    (\j -> return (i*j) )

находится внутри лямбда-функция

                             (\i -> logNumber 5 >>= (\j -> return (i*j) ) )

Именно поэтому i в этом return (i*j) относится к i внешнему аргументу лямбда-функции *1022*, полученному им из самого внешнего выражения монадического действия, logNumber 3.

Как? Поскольку в соответствии с определением >>=, как вы указали в своем вопросе, у нас есть

   runWriter ( Writer (x,v) >>= f )
=
   runWriter ( let (Writer (y, u)) = f x in Writer (y, v `mappend` u) )
= 
   let (Writer (y, u)) = f x in runWriter ( Writer (y, v `mappend` u) )
= 
   let (Writer (y, u)) = f x in (y, v `mappend` u) 

1032 * т.е. *

   runWriter ( logNumber 5 >>= (\j -> return j) )
=                              -------- f -----
   runWriter ( writer (5, ["Got number: 5"]) >>= (\j -> writer (j, mempty)) )
=                  --  x  ------- v -------      -------- f ---------------
   let Writer (y, u) = ( (\j -> writer (j, mempty)) 5 ) 
                         -------- f --------------- x
                            in (y, ["Got number: 5"] `mappend` u) 
=                                  ------- v ------- 
   let (y, u) = (5, mempty) in (y, ["Got number: 5"] `mappend` u) 
=
                               (5, ["Got number: 5"] `mappend` mempty) 

«Моноидальные значения» от каждого Writer действия делают не «измененными». Каждое действие вносит свое «моноидальное значение» в общее «моноидное значение» комбинированного вычисления Writer -типа блока do, построенного из его суб-вычислений Writer -типа с помощью mappending моноидальные значения, вносимые каждым подвычислением ( семантика из Writer), найденные в поле snd кортежей, представляющих действия ( реализация из Writer).

Опять же, это общее значение объединяется путем объединения частей моноидального значения каждого кортежа, а именно их полей snd. Моноидальная комбинация - mappend, что делается для нас за кадром с помощью вычислений типа Writer.

А для списков mappend [a] [b] == [a] ++ [b] == [a,b], а mappend [a,b] [] == [a,b] ++ [] == [a,b].

Ваши вопросы, тогда:

  • не должно return (a*b) составлять (15,[])?

    так и должно быть, и это так, как мы видели в начале ответа.

  • Writer против WriterT оболочки

    это не имеет значения. Оба изоморфны, потому что Identity не является опцией. WriterT является частью реализация монады Writer; тот, что приведен в книге, проще и понятнее.

  • Как эти операции привели к изменению [String] части монады Writer?

    не изменено, но объединено mappend определенного моноида, используемого конкретным писателем; как часть монадной комбинации , то есть определение монадической привязки, >>=, определение; будучи как Monads, обобщают протокол вызова функций, а Writer Monad собирает значения Monoid за кулисами, чтобы их можно было добавлять в тени, в дополнение к пользовательским функциям, выполняющим свою работу в открытом режиме:

     do { a <- logNumber 3  
        ; b <- logNumber 5  
        ; return (a*b)
        }
    =    ----- user area ------       ---- hidden area ---
     do {  a       <- writer (3       , ["Got number: 3"]       )
        ;    b     <- writer (5       , ["Got number: 5"]       )  
        ;             writer (
          (a*b)                       , []                      )
        }
    =
     Writer
        ( (3*5)                       , mconcat [..., ..., ...] )
    

Объятия do - примечание, это твой друг. Это помогает нам мыслить абстрактно .

...