组合状态和状态转换器动作

Composing State and State transformer actions

我有几个 State monad 动作。一些动作根据当前状态和其他可选生成结果的输入做出决定。两种类型的动作相互调用。

我用 StateStateT 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" 是什么意思?我不知道你指的是哪一个:

  1. 重写 State 操作的定义,使其类型现在为 StateT Maybe,即使它们根本不依赖于 Maybe
  2. 使用适配器函数将 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 aforall 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 ammorph 解决方案实际上是相同的。不过,我更喜欢在这里使用 mmorph