展平monad堆栈

时间:2015-09-13 15:14:54

标签: haskell monads monad-transformers lifting

所以我在第一个严肃的haskell项目中都有这种代码:

f :: (MonadTrans t) => ExceptT () (t (StateT A B)) C
f = do mapExceptT lift $ do
    lift $ do
        ...
        lift $ do
            ...
            r <- ...
            ...
            return r
    >>= \r -> ...

我如何尝试实现目标肯定会出现问题(可能有更简单的方法)但目前我有兴趣学习如何以更好的方式处理一堆monad变换器,如果有的话是一个。这是我弄清楚如何在r的上下文中获取B并将其提升到堆栈中更高的monad的唯一方法。提升整个块而不是初始语句是我自己可以得到的。

我经常最终得到的是lift的链条,如果深层monad是liftIO,我可以通过IO来避免这些链条。我不知道其他monad的通用方法。

当他最终处理这样的堆栈时,是否存在可以遵循的模式,并且必须在某个级别提取一个值,在不同级别提取不同的值,将这些组合并影响这两个级别中的任何一个或者可能另一个?

可以以某种方式操纵堆栈,既不会提升整个块(导致let和绑定变量限制为内部块),也不必lift . lift . ... lift个别操作?

1 个答案:

答案 0 :(得分:6)

这通常是monad变换器的一个众所周知的问题。研究人员设计了各种处理方式,其中没有一种显然是“最好的”。一些已知的解决方案包括:

  • mtl方法,它会自动提升monad变换器类型的monad类型(并且其内置monad变换器)。如果这些是您的函数正在使用的monad的唯一功能,则允许您只编写f :: (MonadState A m, MonadError () m) => m C。由于其极端的不可移植性和其他一些原因,mtl通常被认为是伪弃用的。有关详细信息,请参阅this pagethis question
  • 如果您正在反复使用特定 monad堆栈,则可以将其包装在newtype中并手动编写其支持的各种monad类型类的实例。对于FunctorApplicativeMonad以及堆栈中顶级转换器实现的任何其他类型类,可以使用GeneralizedNewtypeDeriving让编译器编写实例为你自动;对于其他类型类,您必须为每个方法插入适当数量的lift调用。这种方法的优点是,它更通用,更易于理解,同时在呼叫站点为您提供与mtl相同的灵活性。这种方法的一个大问题是它鼓励对所有操作使用单个“mega-monad”而不是仅指定所需的操作,因为将任何新的monad转换器添加到堆栈需要编写一个全新的实例列表。
  • 在大多数情况下,你真的不希望monad具有“某种类型A的任意状态”和“一些任意异常抛出能力”。相反,monad堆栈提供的不同功能在您的程序的心理模型中具有一些语义含义。以前方法的一种变体是为基本FunctorApplicativeMonad之外的效果创建自定义类型类,并为newtype上的自定义类型类写入实例而不是monad。与此处列出的其他方法相比,这具有一个主要优势:您可以在不同位置拥有一个堆栈,其中包含相同monad变换器的多个副本。到目前为止,这是我在自己的程序中使用最多的策略。
  • 完全不同的方法是效果系统。通常,效果系统必须内置到语言的类型系统中,但可以encode an effect system in Haskell's type system。请参阅effect-monads包。