我正试图找到一种更优雅的方式来编写以下代码。
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)已丢失。
任何解释,更重要的是更好的解决方法 非常感激。谢谢!
好的,我看到将type
切换为data
会使代码生效。
我正试图说出这可能会起作用。每个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
中的定义呢?
谢谢!
答案 0 :(得分:8)
问题在于,仅从E c a -> E c a
开始,编译器不知道选择哪个实例C
。
关联类型系列只是类型同义词。所以上课
class C c => A c where
g :: E c a -> E c a
从typechecker的角度来看,也可能是
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 ...
。