12

我正在尝试找到一种更优雅的方式来编写以下代码。

class C c where
    type E c :: * -> *

class C c => A c where
    g :: E c a -> E c a

class (C c, A c) => D c where
    f :: E c a -> E c a

instance A c => D c where
    f = g

这会产生错误。

Test.hs:58:9:
    Could not deduce (E c0 ~ E c)
    from the context (A c)
      bound by the instance declaration at Test.hs:57:10-19
    NB: `E' is a type function, and may not be injective
    Expected type: E c a
      Actual type: E c0 a
    Expected type: E c a -> E c a
      Actual type: E c0 a -> E c0 a
    In the expression: g
    In an equation for `f': f = g
Failed, modules loaded: none.

我目前的解决方案是添加一个虚拟变量,从中可以得出正在使用的特定 C。

class C c where
    type E c :: * -> *

class C c => A c where
    g_inner :: c -> E c a -> E c a
g = g_inner undefined

class (C c, A c) => D c where
    f_inner :: c -> E c a -> E c a
f = f_inner undefined

instance A c => D c where
    f_inner = g_inner

我知道这是关联类型不是单射的另一个实例,但我无法弄清楚。当然,E 可能不是单射的,但似乎某处g将适用于D类中引用的特定 (E c) 的信息已经丢失。

任何解释,更重要的是更好的解决方法将不胜感激。谢谢!

编辑

好的,我看到切换typedata使代码工作。

我试图弄清楚这可能是如何工作的。每个都c创建一个新的数据类型E c。在实例上下文中,我们必须匹配forall a. ((E) c) a -> ((E) c) a. forall a. ((E) c) a -> ((E) c) a表示,然后我们与自身F = E c匹配。forall a. F a -> F a

我很难看到类型同义词(关联类型)案例在哪里出现问题。当然,可以定义两个A都具有签名的实例(E c) a -> (E c) a。但是,为什么使用A c范围内的实例中的定义是错误的呢?

谢谢!!

4

1 回答 1

8

问题是,从 just 开始E c a -> E c a,编译器不知道C要选择哪个实例。

关联类型族只是类型同义词。所以上课

class C c => A c where
    g :: E c a -> E c a

从类型检查器的角度来看也可能是

class C c => A c where
    g :: m a -> m a

由于c没有提到类变量,因此无法确定应该使用哪个实例字典来选择函数。虽然这是因为类型族不是单射的,但我同意从错误消息中看不出这是问题所在。

使用 Daniel Wagner 建议的数据族可能是最优雅的解决方案。我有时能够颠倒我的类型系列,因此我没有得到E c一个特定的c,而是选择c基于E c。在这种情况下,这将给出:

class E (e :: * -> *) where
    type C e :: *

class E e => A e where
    g :: e a -> e a

class (A e) => D e where
    f :: e a -> e a

instance A e => D e where
    f = g

根据您正在做的其他事情,这可能对您有用。

撇开这个问题不谈,拥有一个单独的实例并没有多大意义。由于A可用作超类约束,因此您可以将其设置f = g为默认方法。这样做会少很多麻烦;你可能实际上并不想要一个instance D e where ....

于 2011-10-09T14:02:31.847 回答