我有一个班级
class Monad m => MyClass m where
type MyType1 m
type MyType2 m
...
a :: m (MyType1 m)
b :: m (MyType2 m)
c :: m (MyType3 m)
...
我也有很多实例合理地实现这些功能(a
...)
instance MyClass A where
type MyType1 A = Int
...
a = ...
...
instance MyClass (B a) where
type MyType1 (B a) = Char
...
a = ...
...
...
但除了通过变形金刚解除实施之外,我还有很多实例没有做任何有用的事情:
instance MyClass m => MyClass (MyTransA m)
type MyType1 (MyTransA m) = MyType1 m
...
a = lift a
...
instance MyClass m => MyClass (MyTransB m)
type MyType1 (MyTransB m) = MyType1 m
...
a = lift a
...
...
这就是要写的很多样板文件,所以我想用简单的
替换这些重复的无趣的实例class MonadTrans t => AutoLiftMyClass t
instance (AutoLiftMyClass a, MyClass m) => MyClass (a m)
type MyType1 (a m) = MyType1 m
...
a = lift a
...
这将允许我只写
instance AutoLiftMyClass MyTransA
instance AutoLiftMyClass MyTransB
...
免费提升,避免列举所有被提升的a
,b
,......每MyTransA
,MyTransB
,... < / p>
问题在于,无论出于何种原因(我真的不知道为什么),GHC只考虑实例声明的RHS,因此AutoLiftMyClass
碰撞MyType1
的类型族实例。 ..具有所有合理的实例A
,B
,...(那些不会声明AutoLiftMyClass
实例的
我在wiki上看过关于封闭式家庭的一些帖子和文章,但对我来说没有多大意义。有什么方法可以让这个想法发挥作用吗?
答案 0 :(得分:2)
您可以使用DefaultSignatures
,这应该可以解决这个问题:
class Monad m => MyClass m where
type MyType m :: *
type MyType m = MyTypeDef m
val :: m (MyType m)
default val :: (MyClassDef m) => m (MyTypeDef m)
val = defVal
MyClass
,MyType
的变体只是上述内容的副本,主要是:
class MyClassDef m where
type MyTypeDef m :: *
defVal :: m (MyTypeDef m)
instance
(MonadTrans t, Monad n, MyClass n
) => MyClassDef (t (n :: * -> *)) where
type MyTypeDef (t n) = MyType n
defVal = lift val
请注意,实际上只需要在t n
构造函数上进行干净的模式匹配。重叠不是问题,因为这只会在默认签名中使用。
那么你的实例就是:
instance (MyClass m) => MyClass (ReaderT r m)
instance (MyClass m, Monoid r) => MyClass (WriterT r m)
instance (MyClass m) => MyClass (StateT r m)
当然可能需要为默认实现提供多个选项,但这并不比上面那么难 - 你只需在类中添加另一种类型:
class MyClassDef (ix :: Symbol) m where
type MyTypeDef ix m :: *
defVal :: m (MyTypeDef ix m)
instance
(MonadTrans t, Monad n, MyClass n
) => MyClassDef "Monad Transformer" (t (n :: * -> *)) where
type MyTypeDef "Monad Transformer" (t n) = MyType n
defVal = lift val
请注意ix
中的defVal
不明确,但我会使用TypeApplications
来解决它。您可以使用Proxy
完成相同的操作。
在编写实例时确定附加参数,并假设您不使用重叠实例(如果您想要良好的类型推断,则不应该使用,特别是如果您希望类型推断与其他mtl样式一起使用库)您可以将其添加为关联类型:
class Monad m => MyClass m where
type UseDef m :: Symbol
type MyType m :: *
type MyType m = MyTypeDef (UseDef m) m
val :: m (MyType m)
default val :: (MyClassDef (UseDef m) m) => m (MyTypeDef (UseDef m) m)
val = defVal @(UseDef m)
如果您忘记实施UseDef
,则会收到如下错误:
* Could not deduce (MyClassDef (UseDef (StateT r m)) (StateT r m))
arising from a use of `Main.$dmval'
但如果您愿意,可以为缺少的默认值提供自己的自定义错误:
instance (TypeError (Text ("No default selected"))) => MyClassDef "" m
class Monad m => MyClass m where
type UseDef m :: Symbol
type UseDef m = ""
并且如果实现所有方法和类型,则不会出现错误,因为UseDef
未在任何地方使用 - 仅在实例化的默认签名中,如果给出了实现,则甚至不存在。
你的实例需要增加一系列样板的费用,但它并不多(特别是复制粘贴):
instance (MyClass m) => MyClass (ReaderT r m) where
type UseDef (ReaderT r m) = "Monad Transformer"
instance (MyClass m, Monoid r) => MyClass (WriterT r m) where
type UseDef (WriterT r m) = "Monad Transformer"
instance (MyClass m) => MyClass (StateT r m) where
type UseDef (StateT r m) = "Monad Transformer"
请注意,您必须为每个实例提供所需的上下文。
请注意,如果您只关心避免重叠实例,那么所有这些都是必要的。如果不这样做,那么使用简单的解决方案,然后编写
instance {-# OVERLAPS #-} (AutoLiftMyClass a, MyClass m) => MyClass (a m)
或开启OverlappingInstances
。