在 Haskell 中为 Monad-Transformers 实施 liftIO 风格的提升

Implementing a liftIO-Style lift for Monad-Transformers in Haskell

我目前正在学习在 Haskell 中使用 Monad-Transformer。我编写了一个简单的 使用 StateT and ExceptT:

的 Monad-Transformer
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 MyMonadliftMyMonad 像 函数,当 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