Как обозначение do в Monad означает Haskell - PullRequest
0 голосов
/ 02 марта 2020

Рассмотрим следующий сегмент кода

import Control.Monad.State

type Stack = [Int]

pop :: State Stack Int 
pop = state $ \(x:xs) -> (x, xs)

push :: Int -> State Stack () 
push a = state $ \xs -> ((), a:xs)

stackManip :: State Stack Int
stackManip = do 
    push 3 
    a <- pop 
    pop

Как мы знаем, do notation в Monad совпадает с оператором >>=. Мы можем переписать этот сегмент следующим образом:

push 3 >>= (\_ ->
pop >>= (\a ->
pop))

окончательное значение этого выражения не связано с push 3 и первым pop, независимо от того, что вводится, он просто возвращает pop, так что сначала не будет помещать значение 3 в стек и извлекать его, с чего бы это произошло?


Спасибо за ваш ответ. Я добавил отсутствующий код (реализация для Stack, push и pop) выше, и я думаю, что выяснил, как он работает. Ключом к пониманию этого кода является понимание реализации State s монады:

instance Monad (State s) where 
    return x = State $ \s -> (x, s) 
    (State h) >>= f = State $ \s -> let (a, newState) = h s 
                                        (State g) = f a
                                    in g newState

Вся реализация >>= do передает состояние функции h вычисляя его новое состояние и передавая новое состояние в функцию g , подразумеваемую в f , получая более новое состояние. Таким образом, фактически состояние изменилось неявно.

Ответы [ 2 ]

1 голос
/ 02 марта 2020

Монады в Haskell иногда называют «программируемыми точками с запятой». Это не фраза, которую я нахожу особенно полезной в целом, но она отражает то, как выражения, написанные с пометкой Haskell do, имеют что-то вроде императивных программ. И, в частности, способ объединения «операторов» в блоке do зависит от конкретной используемой монады. Следовательно, «программируемые точки с запятой» - способ объединения последовательных «операторов» (которые во многих императивных языках разделяются точками с запятой) может быть изменен («запрограммирован») с использованием другой монады.

И так как do на самом деле нотация - это просто syntacti c sugar для построения выражения от других с использованием оператора >>=, это реализация >>= для каждой монады, которая определяет, каково ее «специальное поведение».

Для Например, экземпляр Monad для Maybe позволяет в качестве грубого описания работать со значениями Maybe, как будто они на самом деле являются значениями базового типа, обеспечивая при этом отсутствие значения (т. е. Nothing) происходит в любой точке, результатом является короткое замыкание вычислений, и Nothing будет общим результатом.

Для монады списка каждая строка фактически «выполняется» несколько раз (или ни одной) - один раз для каждый элемент в списке.

А для значений монады State s это, по сути, "функции манипулирования состоянием" t ype s -> (a, s) - они принимают начальное состояние, и из этого вычисляют новое состояние, а также выходное значение некоторого типа a. Реализация >>= - «точка с запятой» - делает здесь * просто гарантирует, что, когда за одной функцией f :: s -> (a, s) следует другая g :: s -> (b, s), результирующая функция применяет f к исходному состоянию и затем применяет g до состояния, вычисленного из f. По сути, это просто композиция функций, немного измененная, чтобы также позволить нам получить доступ к «выходному значению», тип которого не обязательно связан с типом состояния. И это позволяет перечислять различные функции манипулирования состоянием одну за другой в блоке do и знать, что состояние на каждой стадии точно такое, которое вычисляется предыдущими строками, сложенными вместе. Это, в свою очередь, допускает очень естественный стиль программирования, в котором вы даете последовательные «команды» для манипулирования состоянием, но в то же время без фактического выполнения деструктивных обновлений или иного ухода из мира чистых функций и неизменных данных.

* строго говоря это не >>=, а >>, операция, которая получается из >>=, но игнорирует выходное значение. Вы, возможно, заметили, что в примере, который я дал a, значение, выводимое f, полностью игнорируется, но >>= позволяет проверять это значение и определять, какие вычисления делать дальше. В нотации do это означает запись a <- f и последующее использование a. Это на самом деле ключевая вещь, которая отличает монад от их менее могущественных, но все же жизненно важных кузенов (особенно аппликативных функторов).

1 голос
/ 02 марта 2020

Из-за монады L aws ваш код эквивалентен

stackManip :: State Stack Int
stackManip = do 
    push 3 
    a <- pop    -- a == 3
    r <- pop
    return r

Таким образом, вы набираете sh 3, выталкиваете его, игнорируете выдвинутые 3, извлекаете другое значение и возвращаете его.

Haskell - это просто еще один язык программирования. Вы программист в контроле. Пропускает ли компилятор несущественные инструкции, зависит от него, и в любом случае он ненаблюдаем (за исключением проверки кода, сгенерированного компилятором, или измерения температуры процессора при выполнении кода, но это может быть довольно сложно сделать на сервере). ферма за Полярным кругом).

...