所以我在第一个严肃的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
个别操作?
答案 0 :(得分:6)
这通常是monad变换器的一个众所周知的问题。研究人员设计了各种处理方式,其中没有一种显然是“最好的”。一些已知的解决方案包括:
mtl
方法,它会自动提升monad变换器类型的monad类型(并且仅其内置monad变换器)。如果这些是您的函数正在使用的monad的唯一功能,则允许您只编写f :: (MonadState A m, MonadError () m) => m C
。由于其极端的不可移植性和其他一些原因,mtl
通常被认为是伪弃用的。有关详细信息,请参阅this page和this question。newtype
中并手动编写其支持的各种monad类型类的实例。对于Functor
,Applicative
,Monad
以及堆栈中顶级转换器实现的任何其他类型类,可以使用GeneralizedNewtypeDeriving
让编译器编写实例为你自动;对于其他类型类,您必须为每个方法插入适当数量的lift
调用。这种方法的优点是,它更通用,更易于理解,同时在呼叫站点为您提供与mtl
相同的灵活性。这种方法的一个大问题是它鼓励对所有操作使用单个“mega-monad”而不是仅指定所需的操作,因为将任何新的monad转换器添加到堆栈需要编写一个全新的实例列表。A
的任意状态”和“一些任意异常抛出能力”。相反,monad堆栈提供的不同功能在您的程序的心理模型中具有一些语义含义。以前方法的一种变体是为基本Functor
,Applicative
和Monad
之外的效果创建自定义类型类,并为newtype
上的自定义类型类写入实例而不是monad。与此处列出的其他方法相比,这具有一个主要优势:您可以在不同位置拥有一个堆栈,其中包含相同monad变换器的多个副本。到目前为止,这是我在自己的程序中使用最多的策略。