将计算从 State monad 提升到 RWS monad
Lifting a computation from the State monad into the RWS monad
我围绕 RWS
(Reader
+Writer
+State
) monad 的使用构建了一个计算:
newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
deriving ({- lots of typeclasses -})
通过组装形式为
的基本计算逐步构建计算
foo :: a -> Problem b
然而,有时子计算不需要 RWS
monad 的全部功能。例如,考虑
bar :: c -> State MyState d
我想在 Problem
monad 的上下文中使用 bar
作为更大计算的一部分。我可以看到三种方法,none 对我来说似乎非常优雅。
手动解包 State
计算并将其重新包装在 RWS monad 中:
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
修改 bar
的类型签名,将其提升到 Problem
monad 中。这有一个缺点,即新类型签名没有明确承诺 bar
独立于 MyEnv
并且不向 MyLog
.
记录任何内容
用显式 ReaderT MyEnv WriterT MyLog State MyState
monad 堆栈替换 RWS
monad。这使我能够简洁地 lift.lift
bar
子计算到完整的 monad 中;但是,这个技巧将不起作用,例如对于 c -> Reader MyEnv d
.
形式的子计算
是否有更简洁的方法来组合 foo
和 bar
?我有一种预感,对 type-class 实例的一些巧妙定义可能会起到作用,但我看不出具体如何进行。
我假设您正在使用 mtl
(如果您没有使用,请考虑这样做 - 这些库大部分是兼容的,但以下内容除外)。您可以派生 MonadReader MyEnv
、MonadWriter MyLog
和 MonadState MyState
的实例。然后,您可以使用这些在具有此类约束的 any monad 堆栈上概括您的函数。
{-# 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 ()
现在,为什么这通常有用?它归结为你的 monad "stack" 的灵活性。如果明天你决定你实际上不需要 RWS
而只需要 State
,你可以相应地重构 Problem
和 bar
(首先只需要状态)将继续工作,没有任何变化。
一般来说,我尽量避免在foo
或bar
等辅助函数中使用WriterT
、StateT
、RWST
等。选择使用类型类 MonadWriter
、MonadState
、MonadReader
等让您的代码尽可能通用和独立于实现。然后,您只需使用 WriterT
, StateT
、RWST
一旦进入你的代码:当你实际上 "run" 你的 monad 时。
关于 transformers
的附注
如果您正在使用 transformers
, none of this works. That isn't necessarily a bad thing: mtl
, by virtue of always being able to "find" components (like state or writer) has some problems。
我围绕 RWS
(Reader
+Writer
+State
) monad 的使用构建了一个计算:
newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
deriving ({- lots of typeclasses -})
通过组装形式为
的基本计算逐步构建计算foo :: a -> Problem b
然而,有时子计算不需要 RWS
monad 的全部功能。例如,考虑
bar :: c -> State MyState d
我想在 Problem
monad 的上下文中使用 bar
作为更大计算的一部分。我可以看到三种方法,none 对我来说似乎非常优雅。
手动解包
State
计算并将其重新包装在 RWS monad 中: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
修改
bar
的类型签名,将其提升到Problem
monad 中。这有一个缺点,即新类型签名没有明确承诺bar
独立于MyEnv
并且不向MyLog
. 记录任何内容
用显式
ReaderT MyEnv WriterT MyLog State MyState
monad 堆栈替换RWS
monad。这使我能够简洁地lift.lift
bar
子计算到完整的 monad 中;但是,这个技巧将不起作用,例如对于c -> Reader MyEnv d
. 形式的子计算
是否有更简洁的方法来组合 foo
和 bar
?我有一种预感,对 type-class 实例的一些巧妙定义可能会起到作用,但我看不出具体如何进行。
我假设您正在使用 mtl
(如果您没有使用,请考虑这样做 - 这些库大部分是兼容的,但以下内容除外)。您可以派生 MonadReader MyEnv
、MonadWriter MyLog
和 MonadState MyState
的实例。然后,您可以使用这些在具有此类约束的 any monad 堆栈上概括您的函数。
{-# 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 ()
现在,为什么这通常有用?它归结为你的 monad "stack" 的灵活性。如果明天你决定你实际上不需要 RWS
而只需要 State
,你可以相应地重构 Problem
和 bar
(首先只需要状态)将继续工作,没有任何变化。
一般来说,我尽量避免在foo
或bar
等辅助函数中使用WriterT
、StateT
、RWST
等。选择使用类型类 MonadWriter
、MonadState
、MonadReader
等让您的代码尽可能通用和独立于实现。然后,您只需使用 WriterT
, StateT
、RWST
一旦进入你的代码:当你实际上 "run" 你的 monad 时。
关于 transformers
的附注
如果您正在使用 transformers
, none of this works. That isn't necessarily a bad thing: mtl
, by virtue of always being able to "find" components (like state or writer) has some problems。