简体   繁体   English

`~`(代字号)在实例上下文中意味着什么,为什么在某些情况下需要解决重叠?

[英]What does `~` (tilde) mean in an instance context, and why is it necessary to resolve overlap in some cases?

A complication. 一个复杂的问题。

Consider the following snippet: 请考虑以下代码段:

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

And this is how it works: 这就是它的工作原理:

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

Note that the overlapping instances are not resolved, despite the pragma being supplied to this end. 请注意,尽管为此提供了编译指示,但仍未解析重叠实例。

A solution. 一个办法。

It took some guesswork to arrive at the following adjusted definition: 得出以下调整后的定义需要一些猜测:

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

It works as I expected the previous to: 它的工作原理如我所预期的那样:

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

My questions. 我的问题。

I am having a hard time understanding what is happening here. 我很难理解这里发生的事情。 Is there an explanation? 有解释吗?

Particularly, I can put forward two separate questions: 特别是,我可以提出两个不同的问题:

  1. Why is the overlap not resolved in the first snippet? 为什么在第一个片段中没有解决重叠?
  2. Why is the overlap resolved in the second snippet? 为什么在第二个片段中解决了重叠?

But, if the issues are connected, perhaps a single, unified theory would serve explaining this case better. 但是,如果问题是相关的,也许一个统一的理论可以更好地解释这个案例。

PS concerning a close / duplicate vote I am aware that ~ signifies type equality, and I am consciously using it to obtain the behaviour I need (particularly, printD' 1 'a' not matching) . PS关于重复/重复投票我知道~表示类型相等,我有意识地使用它来获得我需要的行为(特别是, printD' 1 'a'不匹配) It hardly explains anything concerning specifically the case I presented, where the two ways of stating type equality (the ~ and the instance D aa ) lead to two subtly distinct behaviours. 它几乎没有解释任何与我提出的具体情况有关的情况,其中说明类型相等的两种方式~instance D aa导致两种微妙不同的行为。


note I tested the snippets above with ghc 8.4.3 and 8.6.0.20180810 注意我用ghc 8.4.38.6.0.20180810测试了上面的片段

First: only instance head matters during instance selection: what's on the left of => does not matter. 第一:在实例选择期间只有实例头很重要: =>左边的内容无关紧要。 So, instance D aa prevents selection unless they are equal; 因此, instance D aa阻止选择,除非它们相等; instance ... => D ua can always be selected. instance ... => D ua总是可以选择instance ... => D ua

Now, the overlap pragmas only come into play if one instance is already more "specific" than the other. 现在,重叠编译指示仅在一个实例已经比另一个实例更“特定”时才起作用。 "Specific", in this case, means "if there exists a substitution of type variables that can instantiate an instance head A to instance head B , then B is more specific than A ". 在这种情况下,“特定”表示“如果存在可以将实例头A实例化为实例头B的类型变量的替换,则BA更具体”。 In

instance D a a
instance {-# OVERLAPPING #-} D u (f x)

neither is more specific than the other, as there is no substitution a := ? 既没有比另一个更具体,因为没有替代a := ? that makes D aa into D u (fx) , nor is there any substitution u := ?; f := ?; x := x 这使得D aa成为D u (fx) ,也没有任何替代u := ?; f := ?; x := x u := ?; f := ?; x := x u := ?; f := ?; x := x that makes D u (fx) into D aa . u := ?; f := ?; x := x使D u (fx)成为D aa The {-# OVERLAPPING #-} pragma does nothing (at least, pertaining to the problem). {-# OVERLAPPING #-} pragma什么都不做(至少与问题有关)。 So, when resolving the constraint D [Integer] [Integer] , the compiler finds both instances to be candidates, neither more specific than the other, and gives an error. 因此,在解析约束D [Integer] [Integer] ,编译器会发现两个实例都是候选实例,既不比另一实例更具体,又会出错。

In

instance (u ~ a) => D u a
instance {-# OVERLAPPING #-} D u (f x)

the second instance is more specific than the first one, because the first one can be instantiated with u := u; a := fx 第二个实例比第一个实例更具体,因为第一个实例可以用u := u; a := fx实例化u := u; a := fx u := u; a := fx to get to the second one. u := u; a := fx到达第二个。 The pragma now pulls its weight. 这个pragma现在拉了重量。 When resolving D [Integer] [Integer] , both instances match, the first one with u := [Integer]; a := [Integer] 解析D [Integer] [Integer] ,两个实例都匹配,第一个实例匹配u := [Integer]; a := [Integer] u := [Integer]; a := [Integer] , and the second with u := [Integer]; f := []; x := Integer u := [Integer]; a := [Integer] ,第二个用u := [Integer]; f := []; x := Integer u := [Integer]; f := []; x := Integer u := [Integer]; f := []; x := Integer . u := [Integer]; f := []; x := Integer However, the second is both more specific and OVERLAPPING , so the first one is discarded as a candidate and the second instance is used. 但是,第二个更具体和OVERLAPPING ,因此第一个被作为候选者丢弃并且使用第二个实例。 (Side note: I think the first instance should be OVERLAPPABLE , and the second instance should have no pragma. This way, all future instances implicitly overlap the catch-all instance, instead of having to annotate each one.) (旁注:我认为第一个实例应该是OVERLAPPABLE ,第二个实例应该没有pragma。这样,所有未来的实例都隐式地重叠了catch-all实例,而不是必须对每个实例进行注释。)

With that trick, selection is done with the right priority, and then equality between the two arguments is forced anyway. 通过该技巧,选择以正确的优先级完成,然后强制两个参数之间的相等。 This combination achieves what you want, apparently. 显然,这种组合可以达到你想要的效果。

One way to visualize what is happening is a Venn diagram. 可视化正在发生的事情的一种方法是维恩图。 From the first attempt, instance D aa and instance D u (fx) form two sets, the sets of the pairs of types that each one can match. 从第一次尝试开始, instance D aainstance D u (fx)形成两组,每组可以匹配的类型对。 These sets do overlap, but there are many pairs of types only D aa matches, and many pairs only D u (fx) matches. 这些集合确实重叠,但是有许多类型只有D aa匹配,而许多对只​​有D u (fx)匹配。 Neither can be said to be more specific, so the OVERLAPPING pragma fails. 两者都不能说是更具体,所以OVERLAPPING pragma失败了。 In the second attempt, D ua actually covers the entire universe of pairs of types, and D u (fx) is a subset (read: inside) of it. 在第二次尝试, D ua实际上涵盖对类型的整个宇宙 ,和D u (fx)是一个子集:它(读内侧)。 Now, the OVERLAPPING pragma works. 现在, OVERLAPPING pragma工作正常。 Thinking in this way also shows us another way to make this work, by creating a new set that covers exactly the intersection of the first try. 以这种方式思考也向我们展示了另一种方法,通过创建一个新的集合来完成第一次尝试的交集。

instance D a a
instance D u (f x)
instance {-# OVERLAPPING #-} (f x) (f x)

But I'd go with the one with two instances unless you really need to use this one for some reason. 但是我会选择带有两个实例的实例,除非你出于某种原因确实需要使用这个实例。

Note, however, that overlapping instances are considered a bit fragile. 但请注意,重叠实例被认为有点脆弱。 As you noticed, it is often tricky to understand which instance is picked and why. 正如您所注意到的,了解选择哪个实例以及原因通常很棘手。 One needs to consider all the instances in scope, their priorities, and essentially run a nontrivial selection algorithm in one's mind to understand what's going on. 人们需要考虑范围内的所有实例,它们的优先级,并且基本上在一个人的脑海中运行一个非平凡的选择算法来理解正在发生的事情。 When the instances are defined across multiple modules (including orphans) things become even more complex, because selection rules might differ according to the local imports. 当跨多个模块(包括孤儿)定义实例时,事情变得更加复杂,因为选择规则可能根据本地导入而不同。 This can even lead to incoherence. 这甚至可能导致不一致。 It is best to avoid them when possible. 最好尽可能避免使用它们。

See also the GHC manual . 另见GHC手册

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM