编写Monad Transformer,它真的需要这么多硬编码实例

jam*_*idh 12 monads haskell monad-transformers

我是monad变换器的长期用户,第一次使用monad变换器编写器....而且我觉得我做了一些不必要的事情.

我们正在开发一个具有多个数据库表的项目,并且将该集合硬编码到不同的monad堆栈变得笨重,所以我们决定将其分解为不同的可插入monad变换器,允许我们在功能类型级别进行选择,如下所示

doSomething::(HasUserTable m, HasProductTable m)=>Int->m String
Run Code Online (Sandbox Code Playgroud)

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

我的第一次尝试是将ReaderT包裹起来,它将用于保存数据库句柄.很明显,这是行不通的,因为ReaderT(和StateT等)不能在不使用硬编码"升力"链的情况下堆叠,从而破坏了堆叠元件的可插拔模块性.

唯一的解决方案似乎是编写完全独立的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
Run Code Online (Sandbox Code Playgroud)

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

有更清洁的方法吗?

Ben*_*son 11

是的,这是monad变换器的问题之一:当你添加一个新的变换器时,你必须编写越来越多的样板实例.这是Ñ每个时间实例,总共为O(n ^ 2)的实例.例如,您可以mtl源代码中观察此缩放问题.Monad变压器不易扩展.

现在,我们每天使用的monad中有很大一部分可以表示为由变换器提供的某种组合mtl,这意味着其他人已经完成了编写所有这些无聊实例的工作.但是那些变形金刚肯定不会涵盖每一个单子,每当你需要自己编写时,你就会被咬伤.

这就是为什么正在不断努力设计影响打字的新方法.Haskell的一个很好的例子是Kiselyov等人的extensible-effects,它采用代数方法来实现基于免费monad的效果打字.这个库的设计在两篇文章中描述:Monad Transformers的替代品,花费一些时间来描述该mtl方法的问题,以及更多可扩展的效果,描述了库的更新和优化实现.

如果您想了解安全和可扩展的效果输入可以进行多远,请参阅Edwin Brady的Idris语言effects.存在相当多的资源解释effects:一个教程,原始的编程和推理与代数效应文章,和资源相关的代数效果描述一些新的功能effects.可能还有一些我在这个列表中忘记的资源.