2017-02-22 6 views
4

В Haskell государство монада передается для извлечения и хранения состояния. И в двух следующих примерах, оба передают государственную монаду с использованием >>, а тесная проверка (с помощью функции inline и сокращения) подтверждает, что состояние действительно передано на следующий шаг.путаница по поводу прохождения государственной монады в Haskell

Однако это кажется не очень интуитивным. Значит ли это, когда я хочу передать государственную монаду, мне просто нужно >> (или >>= и лямбда-выражение \s -> a, где s не является бесплатным в a)? Может ли кто-нибудь дать интуитивное объяснение этому факту, не беспокоясь о сокращении функции?

-- the first example 
tick :: State Int Int 
tick = get >>= \n -> 
    put (n+1) >> 
    return n 

-- the second example 
type GameValue = Int 
type GameState = (Bool, Int) 

playGame' :: String -> State GameState GameValue 
playGame' []  = get >>= \(on, score) -> return score 
playGame' (x: xs) = get >>= \(on, score) -> 
    case x of 
     'a' | on -> put (on, score+1) 
     'b' | on -> put (on, score-1) 
     'c'  -> put (not on, score) 
     _  -> put (on, score) 
    >> playGame xs 

Большое спасибо!

ответ

5

Это действительно сводится к пониманию того, что состояние изоморфно s -> (a, s). Таким образом, любое значение, «завернутое» в монадическое действие, является результатом применения преобразования к некоторому состоянию s (вычисление с учетом состояния, производящее a).

Передача состояния между двумя сохраняющих состояние вычислений

f :: a -> State s b 
g :: b -> State s c 

соответствует сочинял их >=>

f >=> g 

или с помощью >>=

\a -> f a >>= g 

результат здесь

a -> State s c 

это действие, которое с сохранением состояния превращает некоторые основные состояния s в некотором роде, разрешается доступ к некоторым a и производит некоторую c. Таким образом, полное преобразование может зависеть от a, и значение c может зависеть от некоторого состояния s. Это именно то, что вы хотели бы выразить вычисления с учетом состояния. Чистая вещь (и единственная цель выражения этого механизма как монады) состоит в том, что вам не нужно беспокоиться о том, чтобы проехать мимо государства. Но чтобы понять, как это делается, пожалуйста, обратитесь к определению >>= по адресу hackage), просто игнорируйте на мгновение, что это трансформатор, а не конечная монада).

m >>= k = StateT $ \ s -> do 
    ~(a, s') <- runStateT m s 
    runStateT (k a) s' 

вы можете игнорировать обертывание и разворачивание использованием StateT и runStateT, здесь m в форме s -> (a, s), k имеет форму a -> (s -> (b, s)), и вы хотите, чтобы произвести динамическую трансформацию s -> (b, s). Таким образом, результат будет функцией от s, чтобы произвести b вы можете использовать k, но вам нужно a во-первых, как вы производите a? вы можете принять m и применить его к состоянию s, вы получите измененное состояние s' от первого монадического действия m, и вы передадите это состояние в (k a) (который имеет тип s -> (b, s)). Именно здесь государство s прошло через m, чтобы стать s' и передано в k, чтобы стать окончательным s''.

Для вас, как пользователя этого механизма, это остается скрытым, и это - аккуратная вещь о монадах. Если вы хотите, чтобы состояние эволюционировало по некоторым вычислениям, вы строите свои вычисления с небольших шагов, которые вы выражаете как State -actions, и вы даете do -notation или bind (>>=), чтобы выполнить цепочку/прохождение.

Единственная разница между >>= и >> заключается в том, что вы заботитесь о нем или не заботитесь о негосударственном результате.

a >> b 

фактически является эквивалентной

a >>= \_ -> b 

так, что когда-либо значение приобретает выход под действием a, вы бросаете его (сохраняя только модифицированное состояние) и продолжают (передавать состояние вместе) с другое действие b.


Что касается вас примеров

tick :: State Int Int 
tick = get >>= \n -> 
    put (n+1) >> 
    return n 

вы можете переписать его в do -notation в

tick = do 
    n <- get 
    put (n + 1) 
    return n 

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

  1. Первый getток состояние и выставить его (get :: s -> (s, s) в упрощенной обстановке), то <- говорит, что вы заботитесь о стоимости, и вы не хотите его выбросить, основное состояние также прошло в фоновом режиме без изменения (так работает get).

  2. Тогда put :: s -> (s -> ((), s)), что эквивалентно после удаления ненужных скобок в put :: s -> s -> ((), s), принимает значение, чтобы заменить текущее состояние с (первым аргументом), и производит динамическое действие, результат которого является неинтересным значением () который вы уронили (потому что вы не используете <- или потому, что используете >> вместо >>=). Из-за put базовое состояние изменилось на n + 1 и как таковое оно передается.

  3. return ничего не делает для основного состояния, только возвращает свой аргумент.

Чтобы подвести итог, tick начинается с некоторым начальным значением s он обновляет его s+1 внутренне и выходы s на стороне.

Другой пример работает точно так же, >> используется только там, чтобы выбросить () от put. Но состояние все время проходит.

+0

спасибо! после прочтения я понимаю, что я просто не понял, что это ценность, которая отбрасывается, но состояние все равно проходит. –

+0

рад помочь, если ответ на ваше удовлетворение, пожалуйста, подумайте о том, чтобы принять его, чтобы закрыть вопрос (это касается всех вопросов о stackoverflow), помогает отслеживать неразрешенные проблемы и помогает другим найти ответы, прежде чем задавать свои вопросы. – jakubdaniel

+0

Спасибо за этот отзыв, тоже LOL –

Смежные вопросы