Haskell DerivingVia在带有乐趣部门的多参数类型类上

时间:2018-10-26 13:13:01

标签: haskell deriving

我正在尝试使用DerivingVia来简化具有函数依赖项的多参数类型类的实例定义。

我有以下类型和类别:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DerivingVia #-}

newtype Wrapper t = Wrapper t  
newtype Wrapper2 t = Wrapper2 t

class MyEq a f | a -> f where
  eq :: a -> a -> f Bool

-- Instance for 'Wrapper2'
instance Eq t => MyEq (Wrapper2 t) Wrapper2 where
  eq (Wrapper2 t) (Wrapper2 t') = Wrapper2 (t == t')

我想使用MyEq (Wrapper Int) Wrapper导出deriving via

我的第一次尝试是使用:

deriving via Wrapper2 instance MyEq (Wrapper Int) Wrapper

如论文第6.2节https://www.kosmikus.org/DerivingVia/deriving-via-paper.pdf所述,它查找一个MyEq (Wrapper Int) Wrapper2实例,第二个参数已“更改”,但第一个参数仍为Wrapper Int

显然instance MyEq (Wrapper Int) Wrapper2不存在,因为我实现了instance MyEq (Wrapper2 Int) Wrapper2

我无法通过创建来“作弊”(请参见Wrapper作为第一类型参数):

-- Instance for 'Wrapper2'
instance Eq t => MyEq (Wrapper t) Wrapper2 where
  eq (Wrapper2 t) (Wrapper2 t') = Wrapper2 (t == t')

因为在这种情况下不遵守功能依赖性Wrapper t -> Wrapper2

我可以通过重写eq :: f a -> f a -> f Bool并删除功能依赖性来轻松解决此问题,但是我想避免更改此API。

1 个答案:

答案 0 :(得分:5)

首先,让我们重复一遍,您想要为您派生的实例就是这个实例:

instance MyEq (Wrapper Int) Wrapper where
  eq (Wrapper t) (Wrapper t') = Wrapper (t == t')

我看不到一种完全按照您想要的方式派生类的方法,因为当您观察自己时,这需要您更改两个类参数,但是我们目前只能从最后一个派生。

一种可能性是翻转类参数,以使“重要的”类参数(确定另一个的类参数)成为最后一个,然后调整通过其派生的包装器类型以包括一些有用的信息,例如:

class MyEq f a | a -> f where
  aeq :: a -> a -> f Bool

函数aeq保留相同的类型,但是MyEq的类参数被翻转。 现在Wrapper2得到了一个额外的参数,可以让我们在导出时指定f的期望值:

newtype Wrapper2 (f :: Type -> Type) t = Wrapper2 t

现在可以定义Wrapper2的实例而无需显式指定f

instance (Eq t, Coercible Bool (f Bool)) => MyEq f (Wrapper2 f t) where
  eq (Wrapper2 t) (Wrapper2 t') = coerce (t == t')

这里Wrapper2中的额外参数对于满足功能依赖性是必需的。

现在我们可以按以下方式导出所需的实例:

deriving via Wrapper2 Wrapper Int instance MyEq Wrapper (Wrapper Int)

这行得通,因为GHC现在正在寻找 instance MyEq Wrapper (Wrapper2 Wrapper Int),这与我们拥有的{ 提供。


您可以使用关联的类型实现相同的目标:

class MyEq a where
  type Result a :: Type -> Type
  eq :: a -> a -> Result a Bool

Wrapper2的定义与额外的参数相同。实例变为

instance (Eq t, Coercible Bool (f Bool)) => MyEq (Wrapper2 f t) where
  type Result (Wrapper2 f t) = f
  eq (Wrapper2) (Wrapper2 t') = coerce (t == t')

deriving via Wrapper2 Wrapper Int instance MyEq (Wrapper Int)