В качестве примера на мой comment выше, вы можете написать код, используя State
монады как
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
import Data.Text (Text)
import qualified Data.Text as Text
import Control.Monad.State
data MyState = MyState
{ _count :: Int
, _messages :: [Text]
} deriving (Eq, Show)
makeLenses ''MyState
type App = State MyState
incrCnt :: App()
incrCnt = modify (\my -> my & count +~ 1)
logMsg :: Text -> App()
logMsg msg = modify (\my -> my & messages %~ (++ [msg]))
logAndIncr :: Text -> App()
logAndIncr msg = do
incrCnt
logMsg msg
app :: App()
app = do
logAndIncr "First step"
logAndIncr "Second step"
logAndIncr "Third step"
logAndIncr "Fourth step"
logAndIncr "Fifth step"
Обратите внимание, что с помощью дополнительных операторов из Control.Lens
также позволяет писать incrCnt
и logMsg
в
incrCnt = count += 1
logMsg msg = messages %= (++ [msg])
который является еще одним преимуществом использования State
в сочетании с lens
библиотекой, но для сравнения я не использовать их в этом примере.Для того, чтобы записать эквивалентный код выше только с аргументом Попутно будет выглядеть как
incrCnt :: MyState -> MyState
incrCnt my = my & count +~ 1
logMsg :: MyState -> Text -> MyState
logMsg my msg = my & messages %~ (++ [msg])
logAndIncr :: MyState -> Text -> MyState
logAndIncr my msg =
let incremented = incrCnt my
logged = logMsg incremented msg
in logged
На данный момент это не так уж плохо, но когда мы перейдем к следующему шагу, я думаю, вы будете видеть дублирование коды действительно приходит в:
app :: MyState -> MyState
app initial =
let first_step = logAndIncr initial "First step"
second_step = logAndIncr first_step "Second step"
third_step = logAndIncr second_step "Third step"
fourth_step = logAndIncr third_step "Fourth step"
fifth_step = logAndIncr fourth_step "Fifth step"
in fifth_step
Еще одно преимущество оборачивая вверх в Monad
случае является то, что вы можете использовать полную мощность Control.Monad
и Control.Applicative
с ним:
app = mapM_ logAndIncr [
"First step",
"Second step",
"Third step",
"Fourth step",
"Fifth step"
]
Это позволяет обеспечить большую гибкость при обработке значений, вычисленных во время выполнения, по сравнению со статическими значениями.
Разница между передачей состояния вручную и использованием монады State
заключается в том, что монада State
является абстракцией над ручным процессом. Также бывает, что он подходит для нескольких других широко используемых более общих абстракций, таких как Monad
, Applicative
, Functor
и еще нескольких других. Если вы также используете трансформатор StateT
, вы можете скомпоновать эти операции с другими монадами, такими как IO
. Можете ли вы сделать все это без State
и StateT
? Конечно, вы можете, и никто не мешает вам это сделать, но дело в том, что State
абстрагирует этот шаблон и дает вам доступ к огромному набору инструментов из более общих инструментов. Кроме того, небольшая модификация типов выше, делает одни и те же функции работают в различных контекстах:
incrCnt :: MonadState MyState m => m()
logMsg :: MonadState MyState m => Text -> m()
logAndIncr :: MonadState MyState m => Text -> m()
Они будут теперь работать с App
, или с StateT MyState IO
, или любой другой стек монады с MonadState
реализации. Это делает его значительно более многоразовым, чем простой перенос аргументов, что возможно только через абстракцию, которая составляет StateT
.
Скорее всего, вам придется переопределить многие функции, уже предлагаемые монадой 'State'. Подумайте о последнем как о шаблоне проектирования. Вы также можете легко совместить 'State' с другими монадами. – Jubobs
Но если я не использую State, мне не нужно сочетать его с другими монадами. Я бы предпочел некоторые примеры кода. – ais
Ну, в примере, который вы даете, использование 'State', вероятно, слишком велико. У вас есть конкретный, реальный пример? – Jubobs