Как работает этот код монады State?

Этот код взят из этой статьи.

Я был в состоянии следовать за ним до этой части.

module Test where

type State = Int

data ST a = S (State -> (a, State))

apply        :: ST a -> State -> (a,State)
apply (S f) x = f x

fresh =  S (\n -> (n, n+1))

instance Monad ST where
    -- return :: a -> ST a
    return x   = S (\s -> (x,s))

    -- (>>=)  :: ST a -> (a -> ST b) -> ST b
    st >>= f   = S (\s -> let (x,s') = apply st s in apply (f x) s')

data Tree a = Leaf a | Node (Tree a) (Tree a) deriving (Show)


mlabel  :: Tree a -> ST (Tree (a,Int))
-- THIS IS THE PART I DON'T UNDERSTAND:
mlabel (Leaf x) = do n <- fresh
                     return (Leaf (x,n))
mlabel (Node l r) =  do l' <- mlabel l
                        r' <- mlabel r
                        return (Node l' r')

label t = fst (apply (mlabel t) 0)

tree = Node (Node (Leaf 'a') (Leaf 'b')) (Leaf 'c')

И label tree производит:

Node (Node (Leaf ('a',0)) (Leaf ('b',1))) (Leaf ('c',2))

Я вижу, что оператор >>= — это инструмент для «цепочки» функций, которые возвращают монады (или что-то в этом роде).

И хотя мне кажется, что я понимаю этот код, я не понимаю, как работает этот конкретный код.

В частности do n <- fresh. Мы не передали никаких аргументов свежему, верно? Что в таком случае производит n <- fresh? Абсолютно этого не понимаю. Может, это как-то связано с карри?


person user1685095    schedule 19.07.2015    source источник


Ответы (3)


Со встроенной монадической «конвейерной обработкой» ваш код становится

fresh state = (state, state + 1)

mlabel (Leaf x) state =                   --  do
  let (n, state') = fresh state           --    n <- fresh
  in  (Leaf (x,n), state')                --    return (Leaf (x,n))

mlabel (Node l r) state =                 -- do
  let (l', state') = mlabel l state       --    l' <- mlabel l
  in let (r', state'') = mlabel r state'  --    r' <- mlabel r
     in  (Node l' r', state'')            --    return (Node l' r') 

main = let (result, state') = mlabel tree 0  
       in  print result                         

{- Or with arrows,

mlabel (Leaf x)   = Leaf . (x ,)  &&&  (+ 1)
mlabel (Node l r) = mlabel l >>> second (mlabel r)
                              >>> (\(a,(b,c)) -> (Node a b,c))
main              = mlabel tree >>> fst >>> print  $ 0
-}

Или в императивном псевдокоде:

def state = unassigned

def fresh ():
    tmp = state 
    state := state + 1     -- `fresh` alters the global var `state`
    return tmp             -- each time it is called

def mlabel (Leaf x):       -- a language with pattern matching
    n = fresh ()           -- global `state` is altered!
    return (Leaf (x,n))  

def mlabel (Node l r):
    l' = mlabel l          -- affects the global
    r' = mlabel r          --    assignable variable
    return (Node l' r')    --    `state`

def main:
    state := 0             -- set to 0 before the calculation!
    result = mlabel tree
    print result

Вычисление result изменяет назначаемое state; он соответствует полю snd в кортеже (a, State) языка Haskell. А поле fst кортежа — это только что построенное дерево, несущее нумерацию вместе с данными в своих листьях.

Эти варианты функционально эквивалентны.

Возможно, вы слышали крылатую фразу о том, что монадическая привязка является "программируемой точкой с запятой". Здесь смысл его ясен: он определяет так сказать "протокол вызова функции", что мы используем первое возвращаемое значение как вычисляемый результат, а второе возвращаемое значение как обновленное состояние, которое мы передаем в следующий расчет , поэтому он увидит состояние updated.

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

Таким образом, все, что делает монада State, — это поддерживает для нас это скрытое состояние и передает его обновленным между последовательными вычислениями. Так что ничего важного в конце концов нет.

person Will Ness    schedule 20.07.2015
comment
Правильно ли я понимаю, что mlabel l ››= (\l' -› mlabel r ››= (\r' -› return (Node l' r') )) дает каррированные функции? И этот код мог быть написан так, как вы его написали? mlabel l, состояние ››= \l' ... и т.д.? - person user1685095; 23.07.2015
comment
Я понимаю, что монады делают по существу. Я думаю, что мне просто не очень удобно читать код, написанный с помощью каррированных функций. Функциональный код, который вы написали, в основном тот же код без каррирования, верно? - person user1685095; 23.07.2015
comment
mlabel l >>= f == S (\s -> let (x,s') = apply (mlabel l) s in apply (f x) s'). так что это функция одного аргумента s. Я не знаю, что вы имеете в виду под карри там. Это функция. В Haskell g x y = ... совпадает с g x = \y -> .... Оба являются определениями. Эти уравнения не создают новых объектов в памяти по мере их обнаружения — компилируется вся программа. И из-за лени мы можем анализировать/преобразовывать программу с помощью рассуждений об уравнениях. И компиляторам это разрешено. - person Will Ness; 24.07.2015
comment
нет mlabel l, state >>= \l'; скорее, функция под S применяется к состоянию apply (S f) s = f s, поэтому, начиная с mlabel l >>= f = S \s-> ..., в конечном итоге будет применяться \s-> ... к аргументу состояния. Функции Haskell каррируются, f x y = ... означает, что f x — это функция, которая возможно, позже применяется к y. Таким образом, функция, определенная mlabel l, будет применена к некоторому начальному состоянию в какой-то момент. В вашей программе это делает label t = .... - person Will Ness; 24.07.2015
comment
вы действительно должны попытаться записать все это, как предложил Эндрю С, терпеливо, шаг за шагом. Отправьте мне пинг, если у вас возникнут проблемы с этим, и я добавлю это в ответ. - person Will Ness; 24.07.2015
comment
f x = x + 1 — это нормальная функция, верно? f = \x + 1 является каррированной функцией? - person user1685095; 24.07.2015
comment
нет, оба одинаковые. (вы имели в виду, f = \x -> x + 1 я полагаю). - person Will Ness; 24.07.2015
comment
Applicative необходим, а не просто удобен, из-за traverse, определенных replicateM подобных функций и, возможно, некоторых других вещей. Что делает Monad необходимым? MMorph? - person dfeuer; 08.10.2016
comment
join [replicate 5 x | x <- xs] = [ y | x <- xs, y <- replicate 5 x] = xs <* [(),(),(),(),()] по существу аппликативный, [ y | x <- xs, y <- replicate x x] = ? по существу монадический. По сути, монадическое вычисление — это вычисление, которое нельзя построить заранее. Если мы рассматриваем Functors как вычисления/программы, Applicative подобен программе, которую можно скомпилировать заранее и затем запустить; для монадических программ компилятор (интерпретатор) должен присутствовать во время их выполнения. Так что, если мы не можем обойтись без привязки, это Monadic. Что-то такое? - person Will Ness; 08.10.2016

Конкретно делайте ‹- свежее. Мы не передали никаких аргументов свежему, верно?

Точно. Мы пишем для аргумента, который будет передаваться fresh, когда мы, например, делаем что-то вроде apply (mlabel someTree) 5. Хорошее упражнение, которое поможет вам более четко увидеть, что происходит, это сначала написать mlabel с явным (>>=) вместо do-нотации, а затем заменить (>>=) и return тем, что говорит экземпляр Monad.

person duplode    schedule 19.07.2015
comment
Я думаю, воля — абсолютно ключевое слово для понимания монад. - person Will Ness; 20.07.2015

Главное, что нужно понять, это то, что нотация do преобразуется в функции Monad, поэтому

do n <- fresh
   return (Leaf (x,n))

сокращение от

fresh >>= (\n -> 
           return (Leaf (x,n))  )

и

do l' <- mlabel l
   r' <- mlabel r
   return (Node l' r')

сокращение от

mlabel l >>= (\l' -> 
              mlabel r >>= (\r' ->
                            return (Node l' r') ))

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

person AndrewC    schedule 19.07.2015
comment
Я мог бы разобрать, что здесь делают fresh, mlabel и return, но я думаю, что вы узнаете больше о монаде ST, разобравшись с этим самостоятельно. (т.е. я не хочу преждевременно объяснять.) - person AndrewC; 20.07.2015
comment
'mlabel l' дает нам преобразователь состояния, который принимает состояние в качестве входных данных и производит (Leaf (x, oldState), NewState), верно? Если да, то как после этого мы можем использовать bind для 'mlabel r'? Не должна ли функция, которую мы связываем после ''mlabel l', иметь тип Leaf (Char, State) -> ST b? - person user1685095; 20.07.2015
comment
@ user1685095, потому что ST является экземпляром Monad: в (>>=) :: Monad m => m a -> (a -> m b) -> m b m можно заменить на ST. Так что ST a может идти слева от >=. И это тоже тип mlabel r. - person Will Ness; 20.07.2015
comment
@user1685095 user1685095 Leaf (Char, State) -> ST b не является типом, потому что Leaf является конструктором данных. mlabel (l :: Tree a) :: ST (Tree (a,Int)) ~~ State -> ( Tree (a,Int), State). l' имеет тип Tree (a,Int), но l имеет тип Tree a, как и r. Обратите внимание, что вторая привязка вложена внутрь целевой функции первой привязки. Значение в l' откладывается, и выполняется вторая привязка со второй целевой функцией внутри области действия, где l' все еще доступно, поэтому return может использовать как r', так и l'. - person Will Ness; 20.07.2015