解决实例与类型系列的重叠

时间:2016-09-26 13:53:19

标签: haskell typeclass overlap type-families

我有一个班级

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
...

免费提升,避免列举所有被提升的ab,......每MyTransAMyTransB,... < / p>

问题在于,无论出于何种原因(我真的不知道为什么),GHC只考虑实例声明的RHS,因此AutoLiftMyClass碰撞MyType1的类型族实例。 ..具有所有合理的实例AB,...(那些不会声明AutoLiftMyClass实例的

我在wiki上看过关于封闭式家庭的一些帖子和文章,但对我来说没有多大意义。有什么方法可以让这个想法发挥作用吗?

1 个答案:

答案 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 

MyClassMyType的变体只是上述内容的副本,主要是:

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