组合状态和状态转换器动作
Composing State and State transformer actions
我有几个 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
来更好地反映真实情况
I don't want to promote the State
actions to StateT Maybe
either, because it poses an inaccurate model.
"promote" 是什么意思?我不知道你指的是哪一个:
- 重写
State
操作的定义,使其类型现在为 StateT Maybe
,即使它们根本不依赖于 Maybe
;
- 使用适配器函数将
State s a
转换为 StateT s Maybe a
。
我同意拒绝 (1),但对我来说这意味着:
- 选择 (2)。一个有用的工具是使用
mmorph
library (blog entry)。
- 重写
State s a
中的操作以使用 Monad m => StateT s m a
。
在第二种情况下,类型 compatible 与任何 Monad m
但不允许代码采用任何特定的基本 monad,所以你得到相同的纯度为 State s a
.
我想 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
。
我有几个 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
来更好地反映真实情况
I don't want to promote the
State
actions toStateT Maybe
either, because it poses an inaccurate model.
"promote" 是什么意思?我不知道你指的是哪一个:
- 重写
State
操作的定义,使其类型现在为StateT Maybe
,即使它们根本不依赖于Maybe
; - 使用适配器函数将
State s a
转换为StateT s Maybe a
。
我同意拒绝 (1),但对我来说这意味着:
- 选择 (2)。一个有用的工具是使用
mmorph
library (blog entry)。 - 重写
State s a
中的操作以使用Monad m => StateT s m a
。
在第二种情况下,类型 compatible 与任何 Monad m
但不允许代码采用任何特定的基本 monad,所以你得到相同的纯度为 State s a
.
我想 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
。