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

Create my own state monad transformer module hiding underlying 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'))

它的工作原理,我可以与其他 monads 组合

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

谢谢!

我认为如果你可以为你的转换器写一个 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

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

然后我添加了新实例:

-- 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