Поднятие вычисления из монады State в монаду RWS

Я структурирую вычисления вокруг использования монады RWS (Reader+Writer+State):

newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
                    deriving ({- lots of typeclasses -})

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

foo :: a -> Problem b

Однако иногда подвычислениям не требуется полная мощность монады RWS. Например, рассмотрим

bar :: c -> State MyState d

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

  1. Вручную распаковать вычисление State и перепаковать его в монаду RWS:

    baz :: a -> RWS MyEnv MyLog MyState c
    baz x = do temp <- foo x
               initialState <- get
               let (finalResult, finalState) = runState (bar temp) initialState
               put finalState
               return finalResult
    
  2. Измените сигнатуру типа bar, подняв ее в монаду Problem. Недостатком этого является то, что сигнатура нового типа явно не обещает, что bar не зависит от MyEnv, и ничего не записывает в MyLog.

  3. Замените монаду RWS явным стеком монад ReaderT MyEnv WriterT MyLog State MyState. Это позволяет мне сжато lift.lift вычислить bar в полную монаду; однако этот трюк не сработает, например. для подвычисления формы c -> Reader MyEnv d.

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


person Arek' Fu    schedule 28.08.2016    source источник
comment
Вы используете RWS из пакета трансформеров или из mtl?   -  person ErikR    schedule 29.08.2016
comment
Я использую mtl. На самом деле я не знал о transformers.   -  person Arek' Fu    schedule 29.08.2016


Ответы (1)


Я предполагаю, что вы используете mtl (если нет, подумайте об этом — библиотеки в основном совместимы, за исключением следующего). Вы можете получить экземпляры MonadReader MyEnv, MonadWriter MyLog и MonadState MyState. Затем вы можете использовать их, чтобы обобщить свои функции для любого стека монад, который имеет такого рода ограничения.

{-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleContexts #-}

import Control.Monad.RWS
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.State

newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
                    deriving (Functor, Applicative, Monad,
                              MonadReader MyEnv, MonadWriter MyLog, MonadState MyState)

Из вашего примера, может быть, bar нужно только знать, что есть какое-то состояние MyState, поэтому вы можете дать ему подпись

bar :: MonadState MyState m => c -> m d
bar = ...

Затем, даже для foo, которому могут понадобиться все возможности RWS, вы могли бы написать

foo :: (MonadState MyState m, MonadReader MyEnv m, MonadWriter MyLog m) => a -> m b
foo = ...

Затем вы можете смешивать и сочетать их по своему вкусу:

baz :: Problem ()
baz = foo 2 >> bar "hi" >> return ()

Теперь, почему это полезно в целом? Это сводится к гибкости вашего "стека" монад. Если завтра вы решите, что вам на самом деле не нужен RWS, а только State, вы можете соответствующим образом реорганизовать Problem, а bar (которому всегда требовалось только состояние) продолжит работать без каких-либо изменений.

В общем, я стараюсь избегать использования WriterT, StateT, RWST и т. д. внутри вспомогательных функций, таких как foo или bar. Постарайтесь, чтобы ваш код был как можно более общим и независимым от реализации, используя классы типов MonadWriter, MonadState, MonadReader и т. д. Тогда вам нужно использовать WriterT, StateT, RWST только один раз внутри вашего кода: когда вы фактически "запускаете" свою монаду .

Дополнительное примечание о transformers

Если вы используете transformers, ничего из этого не работает. Это не обязательно плохо: mtl в силу того, что всегда можно «найти» компоненты (например, состояние или запись) имеет некоторые проблемы.

person Alec    schedule 28.08.2016
comment
Отличный ответ, спасибо. Ссылка в конце очень актуальна. - person Arek' Fu; 29.08.2016
comment
Последняя ссылка была очень интересной. У меня сложилось (возможно, наивное) впечатление, что стеки Monad только упрощают повторное использование кода в разных приложениях. - person Undreren; 29.08.2016
comment
@Undreren Вас также могут заинтересовать продолжения этой статьи: 1, 2, 3. Тем не менее, я никогда не сталкивался с ограничениями mtl в реальной ситуации. - person Alec; 29.08.2016