我有几个State
monad行动。一些动作基于当前状态和可选地生成结果的其他输入做出决定。这两种类型的动作相互调用。
我已使用State
和StateT Maybe
对这两种操作类型进行了建模。以下(人为的)示例显示了我目前的方法。
{-# LANGUAGE MultiWayIf #-}
import Control.Monad (guard)
import Control.Monad.Identity (runIdentity)
import Control.Monad.Trans.State
type Producer = Int -> State [Int] Int
type MaybeProducer = Int -> StateT [Int] Maybe Int
produce :: Producer
produce n
| n <= 0 = return 0
| otherwise = do accum <- get
let mRes = runStateT (maybeProduce n) accum
if | Just res <- mRes -> StateT $ const (return res)
| otherwise -> do res <- produce (n - 1)
return $ res + n
maybeProduce :: MaybeProducer
maybeProduce n = do guard $ odd n
modify (n:)
mapStateT (return . runIdentity) $
do res <- produce (n - 1)
return $ res + n
我已经考虑将检查与动作分开(从而将它们转换为简单的状态动作),因为检查本身非常复杂(80%的工作)并提供动作所需的绑定。我也不想将State
操作提升为StateT Maybe
,因为它会造成不准确的模型。
我缺少更好或更优质的方式吗?特别是我不喜欢mapStateT
/ runStateT
二人组,但这似乎是必要的。
PS:我知道该示例实际上是Writer
,但我使用State
来更好地反映真实案例
答案 0 :(得分:1)
我也不想将
StateT Maybe
行为推广到State
,因为它会造成一个不准确的模型。
你是什么意思&#34;推广&#34;?我无法分辨你的意思:
StateT Maybe
操作的定义,使其类型现为Maybe
,即使他们根本不依赖State s a
; StateT s Maybe a
转换为State s a
。我同意拒绝(1),但对我来说这意味着:
mmorph
library(blog entry)。 Monad m => StateT s m a
中的操作以使用m
。在第二种情况下,类型兼容与任何Monad State s a
但不允许代码假定任何特定的基础monad,因此您获得与{{1}相同的纯度}}
我给mmorph
一个机会。请注意:
State s a
= StateT s Identity a
; hoist generalize :: (MFunctor t, Monad m) => t Identity a -> t m a
; hoist generalize :: State s a -> StateT s Maybe a
。 编辑: State s a
和forall m. StateT s m a
类型之间存在同构现象,这些反函数给出了它们的价值:
{-# LANGUAGE RankNTypes #-}
import Control.Monad.Morph
import Control.Monad.Trans
import Control.Monad.Trans.State
import Control.Monad.Identity
fwd :: (MFunctor t, Monad m) => t Identity a -> t m a
fwd = hoist generalize
-- The `forall` in the signature forbids callers from demanding any
-- specific choice of type for `m`, which allows *us* to choose
-- `Identity` for `m` here.
bck :: MFunctor t => (forall m. t m a) -> t Identity a
bck = hoist generalize
因此,Monad m => StateT s m a
和mmorph
解决方案实际上是相同的。不过,我更喜欢在这里使用mmorph
。