“〜”(波浪号)在实例上下文中是什么意思,为什么在某些情况下有必要解决重叠问题?

时间:2018-09-01 08:14:31

标签: haskell typeclass

一种并发症。

请考虑以下代码段:

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."

我的问题。

我很难理解这里发生的事情。有解释吗?

特别是,我可以提出两个单独的问题:

  1. 为什么在第一个片段中没有解决重叠?
  2. 为什么在第二个片段中解决了重叠问题?

但是,如果这些问题联系在一起,也许可以使用一个统一的理论来更好地解释这种情况。

P.S。关于结束/重复投票,我知道~表示类型相等,并且我有意识地使用它来获得我需要的行为(特别是printD' 1 'a'不匹配)< / em>。它几乎没有解释任何与我具体提出的案例有关的事情,在该案例中,两种声明类型相等性的方法(~instance D a a)导致两种微妙的行为。 >


注释:我使用ghc 8.4.38.6.0.20180810

测试了以上代码段

1 个答案:

答案 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 := IntegerOVERLAPPING形成两个集合,每个集合可以匹配的类型对的集合。这些集合确实重叠,但是只有OVERLAPPABLE个匹配项对有很多对,而只有instance D a a个匹配项对有很多对。两者都不可以说是更具体的,因此instance D u (f x)编译指示失败。在第二次尝试中,D a a实际上覆盖了类型对的整个 universe ,而D u (f x)是它的子集(读为:内部)。现在,OVERLAPPING杂注起作用了。这种思考方式还向我们展示了完成这项工作的另一种方式,即创建一个覆盖了第一次尝试的交集的新集合。

D u a

但是我会选择一个带有两个实例的实例,除非您确实出于某种原因需要使用这个实例。

但是,请注意,重叠的实例被认为有些脆弱。正如您所注意到的,要了解选择了哪个实例及其原因通常很棘手。人们需要考虑范围内的所有实例,它们的优先级,并本质上运行一个非平凡的选择算法来了解正在发生的事情。当跨多个模块(包括孤立对象)定义实例时,事情会变得更加复杂,因为选择规则可能会根据本地导入而有所不同。这甚至可能导致不连贯。最好尽可能避免使用它们。

另请参阅GHC manual