如何通过monad转换器参数化monad?
我想编写一种可以通过其 monad 转换器参数化的类型。我尝试了几件事,最终得到了一些类似的东西:
newtype Foo t a = Foo { unFoo :: t (State St) a }
然后,我想要一堆实例:
deriving instance (MonadTrans t, Monad (t (State St))) => Monad (Foo t)
最后,我使用适当的 monad 转换器实例化类型。我的代码类型检查,但我收到所有派生实例的警告:“没有明确的实现>>=
”。如果我尝试运行它,当它遇到类型类函数时会出现未定义的错误。
有没有办法自动派生我需要的所有实例?我试图派生出更多的类,而不仅仅是Monad
.
我也对一种更符合人体工程学的方式来完成同样的事情感兴趣——我必须不断手动包装和解开Foo
构造函数,特别是当我尝试对其lift
进行计算时,因为我无法弄清楚如何实现一个MonadTrans
实例为了它。
回答
Monad
不是股票可推导的类,例如Eq
或Show
。Functor
顺便说一句,两者都不是,而是您实际上可以通过DeriveFunctor
. 但没有这样的运气Monad
。
但是等等,你的推导实际上以某种方式起作用,对吗?没有错误?
那可能是因为你有 DeriveAnyClass
启用了,这是一个方便的小扩展,但不是一个特别聪明的扩展。它不能为你做繁重的工作,它所做的只是产生一个空的实例声明。根本没有方法实现。
等等,那有什么用?因为默认方法!许多类为任何类型提供默认方法实现,或者至少有一些约束。我最常遇到的是FromJSON
and ToJSON
,它具有所有内容的默认实现,前提是您的类型具有Generic
.
而且DeriveAnyClass
是语法,这一切都只是轻微缩短。一个方便的东西,所以你可以写data Foo = ... deriving Bar
,依赖于所有Bar
的默认方法。
但是Monad
没有默认实现>>=
,这就是您收到警告的原因。而且,当然,一旦您尝试调用该方法就会崩溃。
你真正想要的是GeneralizedNewtypeDeriving
。此扩展还允许您自动派生任何类,但仅限于newtype
s,它的工作原理是将每个方法委托给包装类型的实现。非常便利。
这正是您所需要的:所有方法实现 forFoo
都将委托给那些 for t
。
在你的代码中,我从你的问题的描述假定,你要么是没有GeneralizedNewtypeDeriving
在所有启用的,要不然你同时DerivedAnyClass
和GeneralizedNewtypeDeriving
启用,在这种情况下DeriveAnyClass
优先(惊喜!)
为了解决这个问题,您可以禁用DeriveAnyClass
,或者更好的是,使用DerivingStrategies
。启用该扩展后,您可以明确告诉编译器对每个派生使用哪种策略 -DeriveAnyClass
或者GeneralizedNewtypeDeriving
:
{-# LANGUAGE DerivingStrategies #-}
deriving newtype instance (MonadTrans t, Monad (t (State St))) => Monad (Foo t)
^^^^^^^
|
this is the important bit
PS 如果启用了两个扩展,您还应该收到警告。