创建实例而不需要新类型虚拟对象

Cli*_*ton 1 haskell typeclass

可以说我有以下内容:

class C a where
  f :: a -> Blah  

f_impl_for_d :: D a => a -> Blah
f_impl_for_d = _ 

newtype Alice f a = Alice (f a)
Run Code Online (Sandbox Code Playgroud)

也许一些现有的实例是这样的:

instance C a => C [a] where ...
instance C a => C (Identity a) where ... 
instance C a => C (Maybe a) where ...
Run Code Online (Sandbox Code Playgroud)

我想要的是这样的实例:

instance (D a, C a => C (f a)) => C (Alice f a)
Run Code Online (Sandbox Code Playgroud)

就像,只要D a有效,并且有一个C (f a)带有约束的实例C a,那么我应该能够导出C (Alice f a)

例如,如果我有D a,那么我应该有C (Alice [] a)C (Alice Maybe a)等等。

现在我可以这样做:

instance D a => C a where
  f = f_impl_for_d

instance C (f a) => C (Alice f a) where
  f (Alice x) = f x
Run Code Online (Sandbox Code Playgroud)

但那个最上面的实例是可笑地重叠的。

还有其他办法吗?我解决的唯一方法是创建一个新类型,如下所示:

newtype T x = T x

instance D x => C (T x) where
  f (T x) = f_impl_for_d x

instance (Functor f, C (f (T x))) => C (Alice f x) where 
  f (Alice x) = f (T <$> x)
Run Code Online (Sandbox Code Playgroud)

但这似乎有些令人费解。还有更好的办法吗?我尝试着用量化的约束来解决问题,但并没有走得太远。

Li-*_*Xia 5

C a => C (f a)不完全是你所需要的。你可能会认为它是一个函数C a -> C (f a),但它的含义在技术上比这更精确:C a约束只能使用实例来解决;该函数不能接受任意参数。

您可以定义一个(某种)子类来提供这样的功能:

class (forall a. C a => C (f a)) => C1 f where
  f1 :: (a -> Blah) -> (f a -> Blah)
Run Code Online (Sandbox Code Playgroud)

然后必须显式实现,例如Maybe,使用当前的实现C a => C (Maybe a),并且该C实例可以替换为默认的f1 f

instance C1 Maybe where ...
instance C a => C (Maybe a) where
  f = f1 f
Run Code Online (Sandbox Code Playgroud)

那么你就可以拥有

instance (D a, C1 f) => C (Alice f a) where
  f (Alice x) = f1 f_impl_for_d x
Run Code Online (Sandbox Code Playgroud)

这类似于base 中的Eq1,Ord1​​ ,Show1类。最初的目的是对量化的约束进行编码(forall a. Eq a => Eq (f a))(在它们成为事物之前),但从技术上讲,这些类的表达能力稍强一些,因为约束Eq a(标识全局唯一值)被替换为类型(可能有许多值) 。