在 GHC 中为术语分配文字
Assigning literals to terms in GHC
出于好奇,为什么是下面的程序
1 = 0
"hello" = "world"
有效且可由 GHC 编译?这仅仅是一个错误还是一个功能?谢谢!
因为这些文字是模式,所以您正在使用模式绑定。
let
Haskell 中的绑定 惰性 ,因此不会执行实际的模式匹配。
但是如果强行匹配,确实会失败:
> :{
let
x@1 = 0
in
1
:}
1 -- no assignment! NB
it :: Num a => a
> :{
let
x@1 = 0
in
x
:}
*** Exception: <interactive>:87:1-7: Irrefutable pattern failed for pattern x@1
因为1
确实是不是0
.
所以这不是错误,也不是 GHC 实现 的一个特性,而是语言 Haskell 本身的一个特性。
它是允许的,因为它是语言规则的自然结果,并且问题不足以在语言规范中制定一个特殊案例来防止它。
自然后果
有两种标准的定义:函数定义和数据定义。在函数定义中,您可以将模式 作为函数 的参数写入等号左侧。在数据定义中,你可以在等号左边写一个模式来匹配等号右边的数据。所以,这些都是允许的:
x = 3
x@y = 3
[x, y, z] = [3, 4, 5]
[x, _, z] = [3, 4, 5]
[x, 4, z] = [3, 4, 5]
x:_ = "xsuffix"
x:"suffix" = "xsuffix"
数字文字和字符串文字是模式。 (前者脱糖成一个调用 (==)
的守卫。)所以这些也是允许的:
3 = 3
x@3 = 3
[3, 4, 5] = [3, 4, 5]
"xsuffix" = "xsuffix"
-- and yes, these too
3 = 4
"xsuffix" = "lolno"
没问题
与该语言的所有其他部分一样,数据定义是惰性的:它们表示的模式匹配计算只有在需要时才会执行(通过检查匹配绑定的变量之一)。由于 "hello" = "world"
和 1 = 0
不绑定任何变量,因此它们表示的模式匹配计算——这会抛出异常——永远不会执行。因此,避免允许它们并不是特别重要。
...除非是
但是等等......我们说这是一个有效的模式:
x@3 = 3
和这个相似的发散并绑定一个变量:
x@3 = 4
为什么那个是允许的?这更难回答!我认为尝试考虑一些可以防止这种情况的语言规则是非常合理的。一般来说,一个完备的规则当然是不可判定的,因为等式右边可以进行任意计算。但是您还可以做出其他选择;例如:
- 不允许在数据定义中使用可反驳的模式。如果模式无法匹配,则模式是 refutable。例如,
x
是无可辩驳的,x@y
是无可辩驳的,_
是无可辩驳的,但是x:y
是可反驳的,True
是可反驳的,()
是可反驳的(因为当 RHS 处于底部时它会发散)。这是迄今为止最简单的,并且会排除 x@3 = 4
和 "hello" = "world"
两者。不幸的是,它也会排除非常有用的东西,比如 [x, y, z] = [3, 4, 5]
.
- 实施终止检查器,并要求可反驳模式的 RHS 终止。如果你有一个分析可以决定一些计算终止——例如,通过发现其中的所有递归都是结构性的或其他东西,有一个完整的终止检查算法的家庭手工业——那么你可以让编译器检查它。如果确实终止,编译器实际上可以 运行 在编译期间进行计算,并仔细检查给定的模式是否与该值匹配。这样做的缺点是终止检查算法非常复杂,因此这给编译器编写者带来了很大的负担,而且有些人很难预测,这使得针对它的编程让用户感到沮丧。
- 要求程序员证明匹配不会失败。您可以为程序员引入一种机制来编写关于他们程序的证明,并要求他们证明匹配不会失败。这朝着依赖类型的方向发展;这种举措的两个主要成本通常是程序效率的降低,以及用这种语言编写程序需要更多的努力。
语言设计者做出了很多选择(不仅仅是在模式匹配语义方面),这些选择在让程序员和编译器编写者的生活更轻松方面犯了错误,但允许更多的程序抛出异常或永远不会完成。这是一个这样的地方——数据定义中允许可反驳的模式,即使这会导致崩溃,因为该政策的实施是有用的、简单的和可预测的。
出于好奇,为什么是下面的程序
1 = 0
"hello" = "world"
有效且可由 GHC 编译?这仅仅是一个错误还是一个功能?谢谢!
因为这些文字是模式,所以您正在使用模式绑定。
let
Haskell 中的绑定 惰性 ,因此不会执行实际的模式匹配。
但是如果强行匹配,确实会失败:
> :{
let
x@1 = 0
in
1
:}
1 -- no assignment! NB
it :: Num a => a
> :{
let
x@1 = 0
in
x
:}
*** Exception: <interactive>:87:1-7: Irrefutable pattern failed for pattern x@1
因为1
确实是不是0
.
所以这不是错误,也不是 GHC 实现 的一个特性,而是语言 Haskell 本身的一个特性。
它是允许的,因为它是语言规则的自然结果,并且问题不足以在语言规范中制定一个特殊案例来防止它。
自然后果
有两种标准的定义:函数定义和数据定义。在函数定义中,您可以将模式 作为函数 的参数写入等号左侧。在数据定义中,你可以在等号左边写一个模式来匹配等号右边的数据。所以,这些都是允许的:
x = 3
x@y = 3
[x, y, z] = [3, 4, 5]
[x, _, z] = [3, 4, 5]
[x, 4, z] = [3, 4, 5]
x:_ = "xsuffix"
x:"suffix" = "xsuffix"
数字文字和字符串文字是模式。 (前者脱糖成一个调用 (==)
的守卫。)所以这些也是允许的:
3 = 3
x@3 = 3
[3, 4, 5] = [3, 4, 5]
"xsuffix" = "xsuffix"
-- and yes, these too
3 = 4
"xsuffix" = "lolno"
没问题
与该语言的所有其他部分一样,数据定义是惰性的:它们表示的模式匹配计算只有在需要时才会执行(通过检查匹配绑定的变量之一)。由于 "hello" = "world"
和 1 = 0
不绑定任何变量,因此它们表示的模式匹配计算——这会抛出异常——永远不会执行。因此,避免允许它们并不是特别重要。
...除非是
但是等等......我们说这是一个有效的模式:
x@3 = 3
和这个相似的发散并绑定一个变量:
x@3 = 4
为什么那个是允许的?这更难回答!我认为尝试考虑一些可以防止这种情况的语言规则是非常合理的。一般来说,一个完备的规则当然是不可判定的,因为等式右边可以进行任意计算。但是您还可以做出其他选择;例如:
- 不允许在数据定义中使用可反驳的模式。如果模式无法匹配,则模式是 refutable。例如,
x
是无可辩驳的,x@y
是无可辩驳的,_
是无可辩驳的,但是x:y
是可反驳的,True
是可反驳的,()
是可反驳的(因为当 RHS 处于底部时它会发散)。这是迄今为止最简单的,并且会排除x@3 = 4
和"hello" = "world"
两者。不幸的是,它也会排除非常有用的东西,比如[x, y, z] = [3, 4, 5]
. - 实施终止检查器,并要求可反驳模式的 RHS 终止。如果你有一个分析可以决定一些计算终止——例如,通过发现其中的所有递归都是结构性的或其他东西,有一个完整的终止检查算法的家庭手工业——那么你可以让编译器检查它。如果确实终止,编译器实际上可以 运行 在编译期间进行计算,并仔细检查给定的模式是否与该值匹配。这样做的缺点是终止检查算法非常复杂,因此这给编译器编写者带来了很大的负担,而且有些人很难预测,这使得针对它的编程让用户感到沮丧。
- 要求程序员证明匹配不会失败。您可以为程序员引入一种机制来编写关于他们程序的证明,并要求他们证明匹配不会失败。这朝着依赖类型的方向发展;这种举措的两个主要成本通常是程序效率的降低,以及用这种语言编写程序需要更多的努力。
语言设计者做出了很多选择(不仅仅是在模式匹配语义方面),这些选择在让程序员和编译器编写者的生活更轻松方面犯了错误,但允许更多的程序抛出异常或永远不会完成。这是一个这样的地方——数据定义中允许可反驳的模式,即使这会导致崩溃,因为该政策的实施是有用的、简单的和可预测的。