写一个 Monad Transformer,真的需要那么多硬编码的实例吗

Writing a Monad Transformer, does it really need so many hardcoded instances

我是 monad transformer 的长期用户,也是 monad transformer 的第一次编写者....我觉得我做了一些不必要的事情。

我们正在开发一个有多个 DB table 的项目,并且将集合硬编码到不同的 monad 堆栈中变得很笨重,因此我们决定将其分解为不同的可插入 monad 转换器,以便我们选择然后在函数类型级别选择,像这样

doSomething::(HasUserTable m, HasProductTable m)=>Int->m String

(HasXTable 是 class,XTableT 是具体的 monad 转换器)。这些单独的 monad 转换器可以以完全模块化的方式插入或删除,并将存储 DB 句柄,需要 ResourceT 等....

我的第一次尝试是环绕 ReaderT,它将用于保存数据库句柄。很明显,这是行不通的,因为 ReaderT(和 StateT 等)如果不使用硬编码 "lift" 链就无法堆叠,从而破坏了堆栈元素的可插入模块化。

唯一的解决方案似乎是编写完全独立的 ReaderT monad 副本,每个副本都允许在较低级别访问其他副本。这行得通,但是解决方案充满了样板代码,像这样

class HasUserTable m where
    getUser::String->m User

newtype UserTableT m r = UserTableT{runUserTableT::String->m r}

--Standard monad instance stuff, biolerplate copy of ReaderT
instance Functor m=>Functor (UserTableT m) where....
instance Applicative m=>Applicative (UserTableT m) where....
instance Monad m=>Monad (UserTableT m) where....
instance Monad m=>HasUserTable (UserTableT m) where....

--Gotta hardcode passthrough rules to every other monad transformer
--in the world, mostly using "lift"....
instance MonadTrans BlockCacheT where....
instance (HasUserTable m, Monad m)=>HasUserTable (StateT a m)....
instance (HasUserTable m, Monad m)=>HasUserTable (ResourceT m)....
.... etc for all other monad transformers

--Similarly, need to hardcode passthrough rules for all other monads
--through the newly created one
instance MonadResource m=>MonadResource (UserTableT m) where....
instance MonadState a m=>MonadState a (UserTableT m) where....
instance (MonadBaseControl IO m) => MonadBaseControl IO (UserTableT m)....
.... etc for all other monad transformers

更糟糕的是,我们需要为我们添加的每个新的 monad 转换器添加更多的直通规则(即 - 我们添加的每个新 table 都需要直通所有其他 table monad 转换器,所以我们需要 n^2 个实例声明!)

有更简洁的方法吗?

是的,这是 monad 转换器的问题之一:当你添加一个新的转换器时,你必须编写越来越多的样板实例。每次都是 n 个实例,总共 O(n^2) 个实例。例如,您可以观察到这个缩放问题 in the mtl source code。 Monad 转换器不易扩展。

现在,我们日常使用的 monad 中有很大一部分可以表示为 mtl 提供的转换器的某种组合,这意味着其他人已经完成了编写所有的工作那些无聊的事例。但是那些转换器肯定不会涵盖 每个 monad,而且每当您需要编写自己的 monad 时都会被咬到。

这就是为什么人们一直在努力设计新的打字效果方法。 Haskell 中的一个很好的例子是 Kiselyov 等人的 extensible-effects library, which takes an algebraic approach to effect typing, based on free monads. The design of this library is described in two articles: An Alternative to Monad Transformers, which spends some time describing problems with the mtl approach, and More Extensible Effects,描述了库的更新和优化实现。

如果您想了解安全和可扩展的效果类型可以走多远,请参阅 Edwin Brady 的 effects library for the Idris language. There exist quite a lot of resources explaining effects: a tutorial, the original Programming and Reasoning with Algebraic Effects article, and Resource-dependent Algebraic Effects 描述 effects 的一些新功能。可能还有一些我忘记在此列表中的资源。