【发布时间】:2016-02-20 18:45:39
【问题描述】:
我是一个长期使用 monad 转换器的用户,第一次写 monad 转换器……我觉得我做了一些不必要的事情。
我们正在开发一个包含多个 DB 表的项目,并且将集合硬编码到不同的 monad 堆栈变得笨拙,因此我们决定将其分解为不同的可插拔 monad 转换器,以便我们在函数类型级别进行选择, 像这样
doSomething::(HasUserTable m, HasProductTable m)=>Int->m String
(HasXTable 是类,XTableT 是具体的 monad 转换器)。这些单独的 monad 转换器可以以完全模块化的方式插入或移除,并且可以存储 DB 句柄、需要 ResourceT 等......
我的第一次尝试是围绕 ReaderT,它将用于保存 DB 句柄。很明显,这是行不通的,因为 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
更糟糕的是,我们需要为我们添加的每个新的 monad 转换器添加更多的传递规则(即,我们添加的每个新表都需要传递所有其他表 monad 转换器,所以我们需要 n^2 个实例声明!)
有没有更简洁的方法来做到这一点?
【问题讨论】:
-
这看起来很容易让人联想到可扩展的效果。 hackage.haskell.org/package/free-vl 有一个实现和对解释它的论文的引用。
-
“所以我们需要 n^2 个实例声明”这是 mtl 风格的 monad 转换器的一个众所周知的问题。如果你将你的类型写成
ReaderT String m r,你可以使用广义的新类型派生来派生那些与读者相同的实例(这看起来像这里的大多数)。您可以将大多数实例替换为MonadTrans t, HasUserTable m => HasUserTable (t m),但这种方式会扼杀类型推断,并且需要多个扩展。 -
@user2407038 使用通用
MonadTrans t, HasUserTable m=>HasUserTable (t m)的问题在于它也适用于 UserTableT,与我需要编写的正确实例相冲突。我怀疑这就是为什么存在 n^2 问题的原因(否则他们会为所有的单子变换器这样做)。我认为您对 n^2 问题的评论可能是我的问题的答案,虽然不是一个快乐的问题....对于单子转换器,甚至是 Haskell,您无法做得更好。如果您有参考讨论这个问题,我会接受它作为答案。
标签: haskell monads monad-transformers