创建我自己的状态monad变换器模块隐藏底层状态monad

时间:2015-04-26 18:16:04

标签: haskell monad-transformers state-monad

我正在学习mtl,我希望学习创建新monad作为模块的正确方法(不是典型的应用程序用法)。

作为一个简单的例子,我写了一个ZipperT monad(complete code here):

{-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
module ZipperT (
  MonadZipper (..)
, ZipperT
, runZipperT
) where

import Control.Applicative
import Control.Monad.State

class Monad m => MonadZipper a m | m -> a where
    pushL :: a -> m ()
    pushR :: a -> m ()
    ...

data ZipperState s = ZipperState { left :: [s], right :: [s] }

newtype ZipperT s m a = ZipperT_ { runZipperT_ :: StateT (ZipperState s) m a }
                        deriving ( Functor, Applicative
                                 , Monad, MonadIO, MonadTrans
                                 , MonadState (ZipperState s))

instance (Monad m) => MonadZipper s (ZipperT s m) where
    pushL x = modify $ \(ZipperState left right) -> ZipperState (x:left) right
    pushR x = modify $ \(ZipperState left right) -> ZipperState left (x:right)
    ...

runZipperT :: (Monad m) => ZipperT s m a -> ([s], [s]) -> m (a, ([s], [s]))
runZipperT computation (left, right) = do
    (x, ZipperState left' right') <- runStateT (runZipperT_ computation) (ZipperState left right)
    return (x, (left', right'))

它起作用,我可以和其他monad组合

import Control.Monad.Identity
import Control.Monad.State
import ZipperT

length' :: [a] -> Int
length' xs = runIdentity (execStateT (runZipperT contar ([], xs)) 0)
    where contar = headR >>= \x -> case x of
                     Nothing -> return ()
                     Just  _ -> do
                                    right2left
                                    (lift . modify) (+1)
                                 -- ^^^^^^^
                                    contar

但我希望避免使用明确的lift

  • 创建这样的模块的正确方法是什么?
  • 我可以避免使用明确的lift吗? (我希望隐藏StateT
  • 的内部ZipperT结构

谢谢!

1 个答案:

答案 0 :(得分:3)

我认为如果您可以为变换器编写MonadState的实例,则可以使用modify而不使用lift

instance Monad m => MonadState (ZipperT s m a) where
   ...

我必须承认,我不确定状态modify的哪一部分应该影响。

我看过完整的代码。看来你已经定义了

MonadState (ZipperState s) (ZipperT s m)

这已经提供了一个modify,然而它会修改错误的基础状态。你真正想要的是暴露m中包含的状态,前提是它本身就是MonadState。从理论上讲,这可以用

来完成
instance MonadState s m => MonadState s (ZipperT s m) where
   ...

但是现在我们为同一个monad有两个MonadState个实例,导致冲突。

我想我以某种方式解决了这个问题。

这就是我的所作所为:

首先,我删除了原始deriving MonadState实例。我反而写了

getZ :: Monad m => ZipperT s m (ZipperState s)
getZ = ZipperT_ get

putZ :: Monad m => ZipperState s -> ZipperT s m ()
putZ = ZipperT_ . put

modifyZ :: Monad m => (ZipperState s -> ZipperState s) -> ZipperT s m ()
modifyZ = ZipperT_ . modify

并使用上述自定义函数替换get,put,modify库中以前出现的ZipperT

然后我添加了新实例:

-- This requires UndecidableInstances
instance MonadState s m => MonadState s (ZipperT a m) where
   get = lift get
   put = lift . put

现在,客户端代码无需升降机即可运行:

length' :: [a] -> Int
length' xs = runIdentity (execStateT (runZipperT contar ([], xs)) 0)
    where contar :: ZipperT a (StateT Int Identity) ()
          contar = headR >>= \x -> case x of
                     Nothing -> return ()
                     Just  _ -> do
                                    right2left
                                    modify (+ (1::Int))
                                 -- ^^^^^^^
                                    contar