组成州和州的变压器行动

时间:2016-04-13 18:30:57

标签: haskell monad-transformers

我有几个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来更好地反映真实案例

1 个答案:

答案 0 :(得分:1)

  

我也不想将StateT Maybe行为推广到State,因为它会造成一个不准确的模型。

你是什么意思&#34;推广&#34;?我无法分辨你的意思:

  1. 重写StateT Maybe操作的定义,使其类型现为Maybe,即使他们根本不依赖State s a;
  2. 使用适配器功能将StateT s Maybe a转换为State s a
  3. 我同意拒绝(1),但对我来说这意味着:

    • 转到(2)。一个有用的工具是使用mmorph libraryblog 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 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