5

我正在尝试使用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。

4

1 回答 1

6

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

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')

这里需要额外的参数 inWrapper2来满足函数依赖。

现在我们可以导出所需的实例,如下所示:

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)
于 2018-10-26T16:13:42.707 回答