添加约束会导致其他约束超出范围吗?
Can adding a constraint cause other constraints to go out of scope?
考虑以下代码,它进行类型检查:
module Scratch where
import GHC.Exts
ensure :: forall c x. c => x -> x
ensure = id
type Eq2 t = (forall x y. (Eq x, Eq y) => Eq (t x y) :: Constraint)
foo :: forall t a.
( Eq2 t
, Eq a
) => ()
foo = ensure @(Eq (a `t` a)) ()
foo
在这里没有做任何有用的事情,但让我们假设它正在做一些需要 Eq (t a a)
实例的重要业务。编译器能够采用 (Eq2 t, Eq a)
约束并详细说明 Eq (t a a)
字典,因此约束被解除并且一切正常。
现在假设我们希望 foo
做一些额外的工作,这取决于以下相当复杂的实例 class:
-- some class
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)) =>
SomeClass t
where
type SomeConstraint t :: * -> Constraint
foo' :: forall t a.
( Eq2 t
, Eq a
, SomeClass t -- <- the extra constraint
) => ()
foo' = ensure @(Eq (a `t` a)) ()
请注意,在 foo'
的主体中,我们仍然只要求我们在 foo
中所做的:Eq (t a a)
约束。此外,我们还没有删除或修改编译器用来在 foo
中详细说明 Eq (t a a)
实例的约束;除了新约束之外,我们仍然需要 (Eq2 t, Eq a)
。所以我希望 foo'
也能进行类型检查。
不幸的是,看起来实际发生的事情是编译器忘记了如何详细说明Eq (t a a)
。这是我们在 foo'
的正文中得到的错误:
• Could not deduce (Eq (t a a)) arising from a use of ‘ensure’
from the context: (Eq2 t, Eq a, SomeClass t)
bound by the type signature for:
foo' :: forall (t :: * -> * -> *) a.
(Eq2 t, Eq a, SomeClass t) =>
()
鉴于编译器可以“从上下文 (Eq2 t, Eq a)
”“推断出 Eq (t a a)
”,我不明白为什么更丰富的上下文 (Eq2 t, Eq a, SomeClass t)
会导致 Eq (t a a)
变得不可用。
这是一个错误,还是我做错了什么?在这两种情况下,是否有一些解决方法?
编辑:这个变通办法被证明是一个不可行的变通办法,因为它不能在 GHC 8.8.3 中编译。奇怪的是,analogous real world program 在 GHC 8.6.5 中编译得很好,甚至通过了一系列测试,尽管编译成功是一个错误的结果。
我找到了一种解决方法,涉及“分解”我所依赖的附加 class 中的量化。因此,如果我进行以下更改:
class
-- (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)) =>
(forall ob. SomeConstraint t ~ ob => forall x y. (ob x, ob y) => ob (t x y)) =>
SomeClass t
where
type SomeConstraint t :: * -> Constraint
它进行 foo'
类型检查。
我仍然不太明白为什么这会使事情起作用,以及它是否以某种方式与我在 foo'
中量化变量的方式相关(在这种情况下,它并不是一个真正可行的解决方案)。
此外,在我看来,foo'
上的附加 SomeClass
约束(无论 SomeClass
是如何定义的)会以某种方式导致编译器忘记,这是一个错误如何从可用的 Eq2 t
和 Eq a
构建一个 Eq (t a a)
。这个直觉是不是不对?
欢迎补充说明这两点的答案。
我认为您 运行 违反了重叠公理的 "Reject if in doubt" 规则。当您将 SomeClass t
约束引入范围时,您还引入了新的量化约束 forall ob x y. (ob x, ob y) => ob (t x y)
。到了释放Eq (t a a)
的时候,GHC不知道是用foo
签名中的量化约束Eq2 t
还是SomeClass
[=中的量化约束33=],因为任何一个都适用。 (与往常一样,GHC 在评估多态实例是否适用时不考虑 SomeConstraint t ~ ob
。)没有机制来检查后者是否可以“专门化”到前者。
如果从 foo
中删除 Eq2 t
约束:
foo :: forall u t a.
( SomeClass t
, Eq a
) => ()
foo = ensure @(Eq (a `t` a)) ()
然后你会得到一个错误“Couldn't match type SomeConstraint t
with Eq
”,暗示这正是 GHC 试图解决这个约束的方式。 (如果您从 class
中删除 SomeConstraint t ~ ob
,它甚至会进行类型检查!)
这不能解决您的问题,但我认为它可以解释发生了什么。
这不是真正的错误; it's expected。在foo
的定义中,上下文有
forall x y. (Eq x, Eq y) => Eq (t x y)
(即Eq2 t
)
Eq a
SomeClass t
forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)
(来自“关闭超类关系结束”SomeClass t
)
我们想要Eq (t a a)
。那么,从上下文来看,有两个公理的头部匹配:(1) 与 x ~ a, y ~ a
和 (2) 与 ob ~ Eq, x ~ a, y ~ a
。有疑问; GHC 拒绝。 (请注意,由于 SomeConstraint t ~ ob
仅在 (4) 的假设中,因此它被完全忽略;选择实例仅关注实例头。)
显而易见的方法是从 SomeClass
的超类中删除 (4)。如何?从实际实例“head”中拆分量化:
class ob (t x y) => SomeClassSuper ob t x y where
instance ob (t x y) => SomeClassSuper ob t x y where
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y) => SomeClass t where
type SomeConstraint t :: * -> Constraint
这就是您的 forall ob. _ => forall x y. _ => _
技巧基本上所做的,除了这不依赖于错误(不允许使用您的语法)。现在,(4) 变为 forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y
。因为这实际上不是 Class args...
形式的约束,它没有超类,所以 GHC 不会向上搜索并找到破坏一切的全能 forall ob x y. ob (t x y)
头。现在唯一能够放电Eq (t a a)
的实例是(1),所以我们使用它。
GHC 确实 在绝对需要时搜索新 (4) 的“超类”;用户指南实际上使此功能成为对上述基本规则的扩展,这些规则来自原始文件。也就是说,forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)
仍然是 available,但它被认为是 after 上下文中的所有“真实”超类(因为它不是实际上不是任何东西的超类)。
import Data.Kind
ensure :: forall c x. c => ()
ensure = ()
type Eq2 t = (forall x y. (Eq x, Eq y) => Eq (t x y) :: Constraint)
-- fine
foo :: forall t a. (Eq2 t, Eq a) => ()
foo = ensure @(Eq (t a a))
class ob (t x y) => SomeClassSuper ob t x y where
instance ob (t x y) => SomeClassSuper ob t x y where
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y) => SomeClass t where
type SomeConstraint t :: * -> Constraint
-- also fine
bar :: forall t a. (Eq2 t, Eq a, SomeClass t) => ()
bar = ensure @(Eq (t a a))
-- also also fine
qux :: forall t a. (Eq2 t, Eq a, SomeConstraint t a, SomeClass t) => ()
qux = ensure @(SomeConstraint t (t a a))
您可能会争辩说,根据开放世界政策,GHC 应该 面对“不连贯”(例如 (1) 与原始版本之间的重叠)回溯(4)),因为量化约束可以制造“不连贯”,而实际上并没有不连贯,我们希望您的代码“正常工作”。这是一个完全合理的需求,但 GHC 目前比较保守,出于性能、简单性和可预测性的原因,只是放弃而不是回溯。
考虑以下代码,它进行类型检查:
module Scratch where
import GHC.Exts
ensure :: forall c x. c => x -> x
ensure = id
type Eq2 t = (forall x y. (Eq x, Eq y) => Eq (t x y) :: Constraint)
foo :: forall t a.
( Eq2 t
, Eq a
) => ()
foo = ensure @(Eq (a `t` a)) ()
foo
在这里没有做任何有用的事情,但让我们假设它正在做一些需要 Eq (t a a)
实例的重要业务。编译器能够采用 (Eq2 t, Eq a)
约束并详细说明 Eq (t a a)
字典,因此约束被解除并且一切正常。
现在假设我们希望 foo
做一些额外的工作,这取决于以下相当复杂的实例 class:
-- some class
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)) =>
SomeClass t
where
type SomeConstraint t :: * -> Constraint
foo' :: forall t a.
( Eq2 t
, Eq a
, SomeClass t -- <- the extra constraint
) => ()
foo' = ensure @(Eq (a `t` a)) ()
请注意,在 foo'
的主体中,我们仍然只要求我们在 foo
中所做的:Eq (t a a)
约束。此外,我们还没有删除或修改编译器用来在 foo
中详细说明 Eq (t a a)
实例的约束;除了新约束之外,我们仍然需要 (Eq2 t, Eq a)
。所以我希望 foo'
也能进行类型检查。
不幸的是,看起来实际发生的事情是编译器忘记了如何详细说明Eq (t a a)
。这是我们在 foo'
的正文中得到的错误:
• Could not deduce (Eq (t a a)) arising from a use of ‘ensure’
from the context: (Eq2 t, Eq a, SomeClass t)
bound by the type signature for:
foo' :: forall (t :: * -> * -> *) a.
(Eq2 t, Eq a, SomeClass t) =>
()
鉴于编译器可以“从上下文 (Eq2 t, Eq a)
”“推断出 Eq (t a a)
”,我不明白为什么更丰富的上下文 (Eq2 t, Eq a, SomeClass t)
会导致 Eq (t a a)
变得不可用。
这是一个错误,还是我做错了什么?在这两种情况下,是否有一些解决方法?
编辑:这个变通办法被证明是一个不可行的变通办法,因为它不能在 GHC 8.8.3 中编译。奇怪的是,analogous real world program 在 GHC 8.6.5 中编译得很好,甚至通过了一系列测试,尽管编译成功是一个错误的结果。
我找到了一种解决方法,涉及“分解”我所依赖的附加 class 中的量化。因此,如果我进行以下更改:
class
-- (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)) =>
(forall ob. SomeConstraint t ~ ob => forall x y. (ob x, ob y) => ob (t x y)) =>
SomeClass t
where
type SomeConstraint t :: * -> Constraint
它进行 foo'
类型检查。
我仍然不太明白为什么这会使事情起作用,以及它是否以某种方式与我在 foo'
中量化变量的方式相关(在这种情况下,它并不是一个真正可行的解决方案)。
此外,在我看来,foo'
上的附加 SomeClass
约束(无论 SomeClass
是如何定义的)会以某种方式导致编译器忘记,这是一个错误如何从可用的 Eq2 t
和 Eq a
构建一个 Eq (t a a)
。这个直觉是不是不对?
欢迎补充说明这两点的答案。
我认为您 运行 违反了重叠公理的 "Reject if in doubt" 规则。当您将 SomeClass t
约束引入范围时,您还引入了新的量化约束 forall ob x y. (ob x, ob y) => ob (t x y)
。到了释放Eq (t a a)
的时候,GHC不知道是用foo
签名中的量化约束Eq2 t
还是SomeClass
[=中的量化约束33=],因为任何一个都适用。 (与往常一样,GHC 在评估多态实例是否适用时不考虑 SomeConstraint t ~ ob
。)没有机制来检查后者是否可以“专门化”到前者。
如果从 foo
中删除 Eq2 t
约束:
foo :: forall u t a.
( SomeClass t
, Eq a
) => ()
foo = ensure @(Eq (a `t` a)) ()
然后你会得到一个错误“Couldn't match type SomeConstraint t
with Eq
”,暗示这正是 GHC 试图解决这个约束的方式。 (如果您从 class
中删除 SomeConstraint t ~ ob
,它甚至会进行类型检查!)
这不能解决您的问题,但我认为它可以解释发生了什么。
这不是真正的错误; it's expected。在foo
的定义中,上下文有
forall x y. (Eq x, Eq y) => Eq (t x y)
(即Eq2 t
)Eq a
SomeClass t
forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)
(来自“关闭超类关系结束”SomeClass t
)
我们想要Eq (t a a)
。那么,从上下文来看,有两个公理的头部匹配:(1) 与 x ~ a, y ~ a
和 (2) 与 ob ~ Eq, x ~ a, y ~ a
。有疑问; GHC 拒绝。 (请注意,由于 SomeConstraint t ~ ob
仅在 (4) 的假设中,因此它被完全忽略;选择实例仅关注实例头。)
显而易见的方法是从 SomeClass
的超类中删除 (4)。如何?从实际实例“head”中拆分量化:
class ob (t x y) => SomeClassSuper ob t x y where
instance ob (t x y) => SomeClassSuper ob t x y where
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y) => SomeClass t where
type SomeConstraint t :: * -> Constraint
这就是您的 forall ob. _ => forall x y. _ => _
技巧基本上所做的,除了这不依赖于错误(不允许使用您的语法)。现在,(4) 变为 forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y
。因为这实际上不是 Class args...
形式的约束,它没有超类,所以 GHC 不会向上搜索并找到破坏一切的全能 forall ob x y. ob (t x y)
头。现在唯一能够放电Eq (t a a)
的实例是(1),所以我们使用它。
GHC 确实 在绝对需要时搜索新 (4) 的“超类”;用户指南实际上使此功能成为对上述基本规则的扩展,这些规则来自原始文件。也就是说,forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => ob (t x y)
仍然是 available,但它被认为是 after 上下文中的所有“真实”超类(因为它不是实际上不是任何东西的超类)。
import Data.Kind
ensure :: forall c x. c => ()
ensure = ()
type Eq2 t = (forall x y. (Eq x, Eq y) => Eq (t x y) :: Constraint)
-- fine
foo :: forall t a. (Eq2 t, Eq a) => ()
foo = ensure @(Eq (t a a))
class ob (t x y) => SomeClassSuper ob t x y where
instance ob (t x y) => SomeClassSuper ob t x y where
class (forall ob x y. (SomeConstraint t ~ ob, ob x, ob y) => SomeClassSuper ob t x y) => SomeClass t where
type SomeConstraint t :: * -> Constraint
-- also fine
bar :: forall t a. (Eq2 t, Eq a, SomeClass t) => ()
bar = ensure @(Eq (t a a))
-- also also fine
qux :: forall t a. (Eq2 t, Eq a, SomeConstraint t a, SomeClass t) => ()
qux = ensure @(SomeConstraint t (t a a))
您可能会争辩说,根据开放世界政策,GHC 应该 面对“不连贯”(例如 (1) 与原始版本之间的重叠)回溯(4)),因为量化约束可以制造“不连贯”,而实际上并没有不连贯,我们希望您的代码“正常工作”。这是一个完全合理的需求,但 GHC 目前比较保守,出于性能、简单性和可预测性的原因,只是放弃而不是回溯。