Невозможно понять, как State Monad получает свое состояние в этом коде - PullRequest
0 голосов
/ 09 января 2019

Я понимаю, что Государственная Монада полезна для распространения новых значений в последовательном исполнении. Но в следующем коде у меня возникают проблемы с пониманием того, как и где addResult получает обновленное состояние каждый раз, когда оценивается.

fizzBuzz :: Integer -> String
fizzBuzz n
   | n `mod` 15 == 0 = "FizzBuzz"
   | n `mod` 5 == 0 = "Buzz"
   | n `mod` 3 == 0 = "Fizz"
   | otherwise = show n

fizzbuzzList :: [Integer] -> [String]
fizzbuzzList list = execState (mapM_ addResult list) []

addResult :: Integer -> State [String] ()
addResult n = do
    xs <- get
    let result = fizzBuzz n
    put (result : xs)

main :: IO ()
main = mapM_ putStrLn $ fizzbuzzList [1..100]

Этот код оценивается, чтобы произвести

1, 2, Физз ...

Я просто не мог понять, как новое значение, созданное addResult, добавляется в ранее созданный список. Не могли бы вы помочь мне понять, как mapM_ addResult list это здесь?

Ответы [ 2 ]

0 голосов
/ 09 января 2019

Как вы правильно заметили, монада State используется для передачи некоторого внешнего значения 'state' через ряд вычислений. Однако вы спрашиваете, как это состояние «сохраняется» при множественных вызовах вашей функции addResult :: Integer -> State [String] () для каждого значения списка list. Уловка - определение mapM_. Начнем с рассмотрения более простой функции mapM:

mapM f [] = return []
mapM f (x:xs) = do
    fx <- f x
    fxs <- mapM f xs
    return (fx : fxs)

Если мы мысленно «расширим» это рекурсивное определение, скажем, с помощью примера списка [x1,x2,x3,x4,...,xn], мы увидим, что значение mapM f [x1,...,xn] будет другим монадическим вычислением:

do
    fx1 <- f x1
    fx2 <- f x2
    -- etc.
    fxn <- f xn
    return [fx1,fx2,...,fxn]

Итак, mapM в основном «склеивает» кучу монадических вычислений в один большой, объединяя их в порядке. Что объясняет, как вы строите список вместо того, чтобы создавать множество меньших: get в начале addResult получает состояние с последнего запуска , а не с начала, потому что вы работаете все вычисления вместе, вот так:

do
    fl0 <- addResult (list !! 0)
    fl1 <- addResult (list !! 1)
    -- etc. like before

(Если вы внимательно прочитаете, вы заметите, что я говорил о mapM, но вы на самом деле использовали mapM_. Они точно такие же, за исключением того, что последний возвращает ().)

0 голосов
/ 09 января 2019

State можно считать определяемым как

data State s a = s -> (a, s)

, что означает, что

Integer -> State [String] ()

эквивалентно

Integer -> [String] -> ((), [String])

Итак, addResult принимает целое число и возвращает функцию , которая принимает состояние и возвращает кортеж, содержащий новое состояние.

mapM_, грубо говоря, объединяет группу этих функций вместе. Использование регулярного отображения создаст список State функций, каждая из которых ожидает состояние и возвращает новое состояние. mapM_ делает дальнейший шаг привязки каждого State к предыдущему. Конечный результат - это не список значений State, а одно значение State, которое формирует конвейер.

execState затем предоставляет начальное состояние на одном конце конвейера и возвращает конечное состояние с другого конца.

...