【问题标题】:What does `~` (tilde) mean in an instance context, and why is it necessary to resolve overlap in some cases?`~`(波浪号)在实例上下文中是什么意思,为什么在某些情况下需要解决重叠问题?
【发布时间】:2018-09-01 08:14:31
【问题描述】:

并发症。

考虑以下 sn-p:

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

请注意,尽管提供了 pragma ,但重叠的实例并未解决 结束。

 

一个解决方案。

经过一番猜测才得出以下调整后的定义:

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. 为什么在第一个 sn-p 中没有解决重叠?
  2. 为什么在第二个 sn-p 中解决了重叠?

但是,如果这些问题是相互关联的,也许一个单一的、统一的理论可以更好地解释这个案例。

 

附注关于关闭/重复投票 我知道~ 表示类型相等,我有意识地使用它来获得我需要的行为(特别是printD' 1 'a' 不匹配)。它几乎没有解释任何关于我提出的案例的具体内容,其中声明类型相等的两种方式~instance D a a 导致两种微妙不同的行为。

 


note 我用ghc8.4.38.6.0.20180810测试了上面的sn-ps

【问题讨论】:

标签: haskell typeclass


【解决方案1】:

首先:在实例选择过程中只有实例头部很重要:=> 左边的内容无关紧要。因此,instance D a a 阻止选择,除非它们相等; instance ... => D u a 总是可以选择的。

现在,重叠编译指示只有在一个实例已经比另一个实例更“具体”时才会发挥作用。在这种情况下,“特定”意味着“如果存在可以将实例头 A 实例化为实例头 B 的类型变量的替换,则 BA 更具体”。在

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

两者都不比另一个更具体,因为没有将D a a 替换为D u (f x) 的替换a := ?,也没有将D u (f x) 替换为D a a 的任何替换u := ?; f := ?; x := x{-# OVERLAPPING #-} pragma 什么都不做(至少与问题有关)。因此,当解决约束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,而第二个实例应该没有pragma。这样,所有未来的实例都会隐含地重叠catch-all实例,而不必注释每一个。)

通过这个技巧,选择以正确的优先级完成,然后无论如何强制两个参数之间的相等。显然,这种组合达到了您想要的效果。

一种可视化正在发生的事情的方法是维恩图。从第一次尝试开始,instance D a ainstance D u (f x) 形成了两个集合,即每个可以匹配的类型对的集合。这些集合确实重叠,但有许多类型对只匹配D a a,并且许多对只匹配D u (f x)。两者都不能说更具体,因此 OVERLAPPING 杂注失败。在第二次尝试中,D u a 实际上涵盖了整个universe 类型对,而D u (f x) 是它的一个子集(阅读:内部)。现在,OVERLAPPING 编译指示有效。以这种方式思考也向我们展示了另一种实现这项工作的方法,即创建一个完全覆盖第一次尝试的交集的新集合。

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

但我会选择有两个实例的那个,除非你真的出于某种原因需要使用这个。

但是请注意,重叠的实例被认为有点脆弱。正如您所注意到的,通常很难理解选择了哪个实例以及为什么。人们需要考虑范围内的所有实例、它们的优先级,并且基本上在脑海中运行一种非平凡的选择算法以了解正在发生的事情。当跨多个模块(包括孤儿)定义实例时,事情变得更加复杂,因为选择规则可能会根据本地导入而有所不同。这甚至会导致不一致。最好尽可能避免它们。

另请参阅GHC manual

【讨论】:

  • 关于在实例选择后应用~ 的好点!但请注意,将(f x) 更改为[x] 不会改变这些示例中的任何内容。所以,D a aD a [x] 一样具体,即使后者提到了一个特定的类型构造函数?因此,实例头的特殊性不会随着类型构造函数的特殊性而变化 * -> *?这是非常令人惊讶的,这个细节让我很难接受这个解释。此外,overlapping pragma 所起的作用非常模糊。尝试删除它 - 一切都坏了!
  • @IgnatInsarov 我认为规则大致是:给定D a a,我可以以某种方式替换a,以便实例变为D a' [x'] 的形式吗?如果是这样,后者更具体。在您的示例中,这是无法做到的。此外,给定D a [x],我们不能替换a,x,使其变为D a' a' 的形式。所以两者都不是更具体的:它们就像D a CharD Char a 一样无与伦比。相反,D a a 的具体性低于 D [x] [x]D Char Char
  • 哦,我明白了,所以实例头只是部分排序的。这是有道理的。
猜你喜欢
  • 2019-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 2020-08-08
  • 2011-09-22
  • 2017-01-28
  • 2015-06-24
相关资源
最近更新 更多