2016-10-01 6 views
2

Я новичок в Haskell, и я столкнулся с ситуацией, когда хотел бы использовать государственную монаду. (Или, по крайней мере, я думаю, что это то, что я хотел бы использовать.) Для государственной монады существует миллион учебных пособий, но все они, похоже, полагают, что моя главная цель - понять ее на глубоком концептуальном уровне и следовательно, они останавливаются перед частью, где они говорят, как на самом деле разрабатывать программное обеспечение с ней. Поэтому я ищу помощь с упрощенным практическим примером.Как я могу написать этот простой код, используя государственную монаду?

Ниже представлена ​​очень простая версия того, как выглядит мой текущий код. Как вы можете видеть, я пронизываю состояние через свои функции, и мой вопрос - просто переписать код с помощью нотации do, чтобы мне не пришлось это делать.

data Machine = Register Int 

addToState :: Machine -> Int -> Machine 
addToState (Register s) a = Register $ s+a 

subtractFromState :: Machine -> Int -> Machine 
subtractFromState (Register s) a = Register (s-a) 

getValue :: Machine -> Int 
getValue (Register s) = s 

initialState = Register 0 

runProgram = getValue (subtractFromState (addToState initialState 6) 4) 

Код моделирует простую абстрактную машину, которая имеет один регистр, а также инструкции для добавления в реестр, вычесть из него, и получить его значение. «Программа» в конце инициализирует регистр до 0, добавляет к нему 6, вычитает 4 и возвращает результат, который, конечно, равен 2.

Я понимаю цель государственной монады (или, по крайней мере, думаю, что я делаю), и я надеюсь, что это позволит мне переписать это так, что я в конечном итоге с чем-то вроде

runProgram :: ??????? 
runProgram = do 
    put 0 
    addToState 6 
    subtractFromState 4 
    value <- getValue 
    return value 

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

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

Обновление: после отличного ответа Ли. Теперь я знаю, как это сделать, но я зациклился на том, как писать код в той же элегантной форме, когда у меня есть несколько взаимодействующих машин. Я спросил об этом в a new question.

ответ

7

Прежде всего, необходимо преобразовать существующие функции для возврата State Machine a значения:

import Control.Monad.State.Lazy 

data Machine = Register Int 

addToState :: Int -> State Machine() 
addToState i = do 
     (Register x) <- get 
     put $ Register (x + i) 

subtractFromState :: Int -> State Machine() 
subtractFromState i = do 
     (Register x) <- get 
     put $ Register (x - i) 

getValue :: State Machine Int 
getValue = do 
     (Register i) <- get 
     pure i 

, то вы можете объединить их в динамическое вычисление:

program :: State Machine Int 
program = do 
    addToState 6 
    subtractFromState 4 
    getValue 

наконец вам нужно может запустить это вычисление с evalState для получения окончательного результата и отказа от состояния:

runProgram :: Int 
runProgram = evalState program (Register 0) 
+0

Большое вам спасибо, это неоценимо. Мне пришлось внести ряд изменений, чтобы заставить его работать (о чем я вообще не жалуюсь, было очень поучительно это сделать). Я не уверен, отредактировать их в своем ответе или оставить его как есть - дайте мне знать, если у вас есть предпочтения. – Nathaniel

+0

@Nathaniel - Вы можете внести необходимые изменения. – Lee

+0

Если вы заинтересованы в дальнейшем, у меня есть [следовать по вопросу] (http://stackoverflow.com/questions/39813675/simulating-interacting-stateful-objects-in-haskell) о том, как программировать в этом стиле, когда У меня есть несколько взаимодействующих объектов с сохранением состояния. – Nathaniel