我是 monad 转换器的长期用户,是第一次 monad 转换器的作者……而且我觉得我做了一些不必要的事情。

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

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

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

我的第一次尝试是环绕 ReaderT,它将用于保存 DB 句柄。很明显这是行不通的,因为 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 转换器添加更多的传递规则(即,我们添加的每个新表都需要传递所有其他表 monad 转换器,因此我们需要 n^2 个实例声明!)

有没有更干净的方法来做到这一点?

最佳答案

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

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

这就是为什么要不断努力设计新方法来实现打字效果。 Haskell 中的一个很好的例子是 Kiselyov 等人的 extensible-effects library ,它采用代数方法来影响打字,基于自由单子(monad)。该库的设计在两篇文章中进行了描述: An Alternative to Monad Transformers 花费了一些时间来描述 mtl 方法的问题,以及 More Extensible Effects ,描述了该库的更新和优化实现。

如果您想了解安全性和可扩展的效果类型可以采取多远,请参阅 Edwin Brady 的 effects library 以了解 Idris 语言。有很多资源解释 effects : tutorial ,原始的 Programming and Reasoning with Algebraic Effects 文章,以及 Resource-dependent Algebraic Effects 描述 13 的一些新功能 13 。在此列表中,我可能忘记了更多资源。

关于haskell - 写一个Monad Transformer,真的需要这么多硬编码实例吗,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35527483/

10-15 17:46