Haskell DerivingVia on multi param type 类 with fun deps
Haskell DerivingVia on multi param type classes with fun deps
我正在尝试使用 DerivingVia
来削减具有函数依赖性的多参数类型 class 实例定义的样板文件。
我有这些类型 class:
{-# 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')
我想使用 deriving via
导出 MyEq (Wrapper Int) Wrapper
。
我的第一次尝试是使用:
deriving via Wrapper2 instance MyEq (Wrapper Int) Wrapper
如论文第 6.2 节 https://www.kosmikus.org/DerivingVia/deriving-via-paper.pdf 中所述,这会查找 MyEq (Wrapper Int) Wrapper2
实例,第二个参数是 "changed" 但第一个参数仍然是 Wrapper Int
.
显然 instance MyEq (Wrapper Int) Wrapper2
不存在,因为我实现了 instance MyEq (Wrapper2 Int) Wrapper2
。
我不能 "cheat" 通过创建(参见 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.
所以首先,让我们重复一下,你想要为你派生的实例是这个:
instance MyEq (Wrapper Int) Wrapper where
eq (Wrapper t) (Wrapper t') = Wrapper (t == t')
我看不出有一种方法可以完全按照您想要的方式导出 class,因为正如您自己观察的那样,这需要您更改两个 class 参数,但我们目前只能导出通过最后。
一种可能是翻转 class 参数,使 "important" class 参数(决定另一个的参数)成为最后一个,然后调整包装器类型你推导 via 包括一些有用的信息,像这样:
class MyEq f a | a -> f where
aeq :: a -> a -> f Bool
函数 aeq
保留相同的类型,但 MyEq
的 class 个参数被翻转。
现在 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)
我正在尝试使用 DerivingVia
来削减具有函数依赖性的多参数类型 class 实例定义的样板文件。
我有这些类型 class:
{-# 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')
我想使用 deriving via
导出 MyEq (Wrapper Int) Wrapper
。
我的第一次尝试是使用:
deriving via Wrapper2 instance MyEq (Wrapper Int) Wrapper
如论文第 6.2 节 https://www.kosmikus.org/DerivingVia/deriving-via-paper.pdf 中所述,这会查找 MyEq (Wrapper Int) Wrapper2
实例,第二个参数是 "changed" 但第一个参数仍然是 Wrapper Int
.
显然 instance MyEq (Wrapper Int) Wrapper2
不存在,因为我实现了 instance MyEq (Wrapper2 Int) Wrapper2
。
我不能 "cheat" 通过创建(参见 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.
所以首先,让我们重复一下,你想要为你派生的实例是这个:
instance MyEq (Wrapper Int) Wrapper where
eq (Wrapper t) (Wrapper t') = Wrapper (t == t')
我看不出有一种方法可以完全按照您想要的方式导出 class,因为正如您自己观察的那样,这需要您更改两个 class 参数,但我们目前只能导出通过最后。
一种可能是翻转 class 参数,使 "important" class 参数(决定另一个的参数)成为最后一个,然后调整包装器类型你推导 via 包括一些有用的信息,像这样:
class MyEq f a | a -> f where
aeq :: a -> a -> f Bool
函数 aeq
保留相同的类型,但 MyEq
的 class 个参数被翻转。
现在 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)