请考虑以下代码段:
class D u a where printD :: u -> a -> String
instance D a a where printD _ _ = "Same type instance."
instance {-# overlapping #-} D u (f x) where printD _ _ = "Instance with a type constructor."
这是它的工作方式:
λ printD 1 'a'
...
...No instance for (D Integer Char)...
...
λ printD 1 1
"Same type instance."
λ printD [1] [1]
...
...Overlapping instances for D [Integer] [Integer]
...
λ printD [1] ['a']
"Instance with a type constructor."
请注意,尽管为此提供了编译指示,但仍无法解析重叠的实例 结束。
需要一些猜测才能得出以下调整后的定义:
class D' u a where printD' :: u -> a -> String
instance (u ~ a) => D' u a where printD' _ _ = "Same type instance."
instance {-# overlapping #-} D' u (f x) where printD' _ _ = "Instance with a type constructor."
它的工作与我之前预期的一样:
λ printD' 1 'a'
...
...No instance for (Num Char)...
...
λ printD' 1 1
"Same type instance."
λ printD' [1] [1]
"Instance with a type constructor."
λ printD' [1] ['a']
"Instance with a type constructor."
我很难理解这里发生的事情。有解释吗?
特别是,我可以提出两个单独的问题:
但是,如果这些问题联系在一起,也许可以使用一个统一的理论来更好地解释这种情况。
P.S。关于结束/重复投票,我知道~
表示类型相等,并且我有意识地使用它来获得我需要的行为(特别是printD' 1 'a'
不匹配)< / em>。它几乎没有解释任何与我具体提出的案例有关的事情,在该案例中,两种声明类型相等性的方法(~
和instance D a a
)导致两种微妙的行为。 >
注释:我使用ghc
8.4.3
和8.6.0.20180810
答案 0 :(得分:4)
首先:在选择实例期间,只有实例头很重要:=>
左侧的内容无关紧要。因此,instance D a a
会阻止选择,除非它们相等。随时可以选择instance ... => D u a
。
现在,仅当一个实例比另一个实例更具“特定性”时,重叠编译指示才起作用。在这种情况下,“特定”的意思是“如果存在可以实例化实例头A
到实例头B
的类型变量替代,则B
比{{ 1}}”。在
A
都不比另一个更具体,因为没有将instance D a a
instance {-# OVERLAPPING #-} D u (f x)
变成a := ?
的替代D a a
,也没有使{{1 }}插入D u (f x)
。 u := ?; f := ?; x := x
编译指示不执行任何操作(至少与该问题有关)。因此,在解决约束D u (f x)
时,编译器会发现这两个实例都是候选者,两者都不比另一个具体,并给出错误。
在
D a a
第二个实例比第一个实例更具体,因为可以用{-# OVERLAPPING #-}
实例化第一个实例以到达第二个实例。现在,实用程序可以减轻重量。解析D [Integer] [Integer]
时,两个实例都匹配,第一个与instance (u ~ a) => D u a
instance {-# OVERLAPPING #-} D u (f x)
匹配,第二个与u := u; a := f x
匹配。但是,第二个既更具体,又更D [Integer] [Integer]
,因此第一个作为候选被丢弃,并且使用第二个实例。 (旁注:我认为第一个实例应为u := [Integer]; a := [Integer]
,第二个实例应不具有编译指示。这样,所有将来的实例都隐式地覆盖了所有实例,而不是具有来注释每个人。)
使用该技巧,将以正确的优先级进行选择,然后无论如何都要强制两个参数之间相等。显然,这种组合可以实现您想要的。
可视化发生情况的一种方法是维恩图。从第一次尝试开始,u := [Integer]; f := []; x := Integer
和OVERLAPPING
形成两个集合,每个集合可以匹配的类型对的集合。这些集合确实重叠,但是只有OVERLAPPABLE
个匹配项对有很多对,而只有instance D a a
个匹配项对有很多对。两者都不可以说是更具体的,因此instance D u (f x)
编译指示失败。在第二次尝试中,D a a
实际上覆盖了类型对的整个 universe ,而D u (f x)
是它的子集(读为:内部)。现在,OVERLAPPING
杂注起作用了。这种思考方式还向我们展示了完成这项工作的另一种方式,即创建一个覆盖了第一次尝试的交集的新集合。
D u a
但是我会选择一个带有两个实例的实例,除非您确实出于某种原因需要使用这个实例。
但是,请注意,重叠的实例被认为有些脆弱。正如您所注意到的,要了解选择了哪个实例及其原因通常很棘手。人们需要考虑范围内的所有实例,它们的优先级,并本质上运行一个非平凡的选择算法来了解正在发生的事情。当跨多个模块(包括孤立对象)定义实例时,事情会变得更加复杂,因为选择规则可能会根据本地导入而有所不同。这甚至可能导致不连贯。最好尽可能避免使用它们。
另请参阅GHC manual。