在 Haskell 中为 Monad-Transformers 实施 liftIO 风格的提升
Implementing a liftIO-Style lift for Monad-Transformers in Haskell
我目前正在学习在 Haskell 中使用 Monad-Transformer。我编写了一个简单的
使用 StateT
and ExceptT
:
type DatabaseT m = StateT DatabaseState (ExceptT DatabaseError m)
已经有一些函数与 DatabaseT
一起使用,例如:
connectDB :: MonadIO m => DBMode -> DatabaseT m ()
isConnected :: Monad m => DatabaseT m Bool
为了更容易提升这些功能我想定义一个类型-class
类似于 MonadIO
:
class Monad m => MonadDB m where
liftDB :: Monad m' => DatabaseT m' a -> m a
现在有问题的部分是使 DatabaseT m
成为一个实例 if MonadDB
。
看implementation of MonadIO
这个函数应该是恒等函数:
instance Monad m => MonadDB (DatabaseT m) where
liftDB = id
但是这段代码无法编译:
Couldn't match type ‘m'’ with ‘m’
‘m'’ is a rigid type variable bound by
the type signature for:
liftDB :: forall (m' :: * -> *) a.
Monad m' =>
DatabaseT m' a -> DatabaseT m a
‘m’ is a rigid type variable bound by
the instance declaration
Expected type: DatabaseT m' a -> DatabaseT m a
Actual type: DatabaseT m a -> DatabaseT m a
In the expression: id
In an equation for ‘liftDB’: liftDB = id
In the instance declaration for ‘MonadDB (DatabaseT m)’
Relevant bindings include
liftDB :: DatabaseT m' a -> DatabaseT m a
它似乎有一个 Monad-Transformer 类型-class 一个 Monad MyMonad
,liftMyMonad
像
函数,当 MyMonad
是 Monad-Transformer 本身时不起作用。
这是使 DatabaseT m
操作更通用的有效方法吗?
它有根本性的缺陷吗?
如果是这样,我如何从 DatabaseT
之上的任何地方提升 DatabaseT m
动作
堆栈?
您要求的最终状态可能不是您想要的最终状态。也就是说,这对大多数需求来说不是很好:
class Monad m => MonadDB m where
liftDB :: Monad m' => DatabaseT m' a -> m a
典型的解决方案是根本不写这样的实例,而是写:
class Monad m => MonadDB m where
connectDB :: DBMode -> m ()
isConnected :: m Bool
这允许您:
- 在 DatabaseT 是组件的单子上下文中使用函数
- 使用 MonadMock 等解决方案进行测试
- 不破坏抽象 - 完全避免在消费者代码中使用
lift
。