Монада RWS: Reader + Writer + State в одном интерфейсе
Фундаментальные трансформеры монад ReaderT
(read-only контекст: например, настройки программы из конфигурационного файла), WriterT
(write-only контекст: например, лог работы программы) и StateT
(read/write контекст: например, счётчики и прочее внутреннее состояние логики программы) стандартных библиотек mtl
и transformers
очень употребительны. Их можно композиционно параметризовать любой “объемлющей” монадой для моделирования требуемого вычислительного контекста (например, WriterT w IO
для программы, которая общается по сети и протоколирует (логирует) свою работу моноидом r
). Трансформеры легко параметризуются друг-другом; мы работает с вложенными в “стопку” монадами путём лифтинга
комбинаторов к нужным монадам с помощью функций семейства lift
.
Кстати, типы Writer
, Reader
и State
(без T на конце) из популярных книг по Haskell для новичков - суть параметризованные монадой Data.Functor.Identity
трансформеры.
Что может быть лучше этих трёх монад? Правильно, - одна монада, которая сочетает в себе сразу все три!
Тип RWST
и RWS
(по аналогии с другими XxxT
) и соответствующие комбинаторы содержатся в модуле Control.Monad.RWS
библиотеки трансформеров.
type RWS r w s = RWST r w s Identity
Тип RWST
“из коробки” реализует все три интерфейса вышеуказанных стандартных монад:
(Monad m, Monoid w) => MonadReader r (RWST r w s m)
(Monad m, Monoid w) => MonadState s (RWST r w s m)
(Monoid w, Monad m) => MonadWriter w (RWST r w s m)
А значит мы можем просто забить на лифт между трансформерами и смешивать известные комбинаторы ask
(Reader
), tell
(Writer
), modify
(State
) и т.п. как нам заблагорассудится:
m :: Monoid w => RWST r w s IO String
m = do
x <- ask
tell mempty
modify (\s -> s)
str <- liftIO getLine
return str
Запуск (т.е. “разворачиваение”) монады RWST
происходит аналогично другим трансформерам функциями runRWST
, evalRWST
и execRWST
. Начальное read-only окружение r
и стэйт s
задаются при запуске.