在Haskell中,()
类型有两个值,即()
和bottom。如果你有一个表达式e :: ()
,实际上检查它是没有意义的,因为它要么是e = ()
,要么通过检查它,你正在崩溃一个程序,否则它不会崩溃。
因此,我认为对()
类型的值的操作可能不会检查该值,也不会区分()
和bottom。
然而,这完全是不真实的:
▎λ ghci
GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help
ghci> u = (undefined :: ())
ghci> show u
"*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err
undefined, called at <interactive>:1:6 in interactive:Ghci1
ghci> () == u
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err
undefined, called at <interactive>:1:6 in interactive:Ghci1
ghci> f () = "ok"
ghci> f u
"*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err
undefined, called at <interactive>:1:6 in interactive:Ghci1
原因何在?以下是一些猜想:
1.出于某种我想不出的原因,在()
上不懒惰是有用的。
- Haskell语义是以这样一种方式编写的,即解构 * 任何 * ADT,即使是微不足道的ADT,都会检查它们。
()
是一个非常特殊的情况,在像Haskell这样的大型语言中,为了确保这一点额外的安全性,根本不值得关注
1.还有一种可能的2+3的组合解释,即Haskell * 可能 * 具有语义,指示表达式case e of
检查e
*,除非 * 它是()
类型,但这会污染语言规范,带来相对较低的好处
3条答案
按热度按时间emeijp431#
我将解决这一部分:
由于某些我想不出的原因,在
()
上不懒惰是有用的。让我们看一下
Control.Parallel.Strategies
(版本1,一个较旧的版本)。这是一个用于并行计算的模块。为了简单起见,让我们集中讨论它的一个函数:parMap strat f xs
的结果与map f xs
相同,只是列表是并行计算的。strat
参数是什么?手段
使用
strat
只能做两件事:()
或底部。parMap
并行执行后一个操作。这允许调用方指定一个strat
参数,该参数根据需要 * 计算b
* 类型的列表值。例如是有效的调用,并且将导致
parMap
仅计算新的对列表,以分别公开对构造器、第一组件和第二组件。因此,在这种情况下,强制
strat
的()
结果允许用户控制在parMap
期间执行多少求值,即,强制该结果多少(并行),并且因此结果的哪些部分应该被留下不被评估。(相比之下,map f xs
会使结果完全不求值--它完全是惰性的。parMap
不能这样做,否则它就不再是并行的。)小题外话:注意,GADT
有一个构造函数,如
()
。在这里,强制使用这样的值,如下所示:这里的第一个参数必须是一个bottom。通过强制它,我们会使函数出错并产生一个异常。如果我们不强制它,我们会得到一个非常糟糕的未定义行为,就像C和C++中的那些行为一样,完全破坏了类型安全。Haskell会正确地拒绝任何绕过它的尝试:
会在编译阶段触发型别错误。
bqf10yzr2#
我不确定,但我怀疑这不是你说的那些话,而是为了让你的语言具有可预测性和一致性。
从本质上讲,你观察到了两件事,我认为它们是分开的。第一件事是,用
case
语句检查x
是否确实是()
,强制对x
求值;第二个是(Show
和Eq
的)示例被写入以使用case
语句。case <e0> of <pat> -> <e1>
,那么e0
的计算就足够检查pat
中的构造函数是否真的在给定的位置。让我们假设e0
的计算足够深入,足以检查pat
是否真的匹配!对于()
类型,这意味着模式()
导致完全计算--因为您已经指定了预期的完整值--而模式x
或_
可以不经过进一步的计算而匹配。case
,它与每个可用的构造函数匹配,并为字段提供简单的变量模式,然后依次对每个字段做一些事情(可能是递归调用)。也就是说,稍微简化一下,show
的实现如下所示:将此方案专门化为单构造函数、零字段类型
()
是很自然的:(此外,该报告指出,
(==)
在两个参数中始终是严格的;但该属性也会自然地从编写通用Eq
示例派生算法的明显方式中产生。)因此,最不令人惊讶的方法是类示例在其参数上进行模式匹配。hs1rzwqc3#
第二条绝对正确。
()
类型只是一个空的data
类型,具有特殊的类型/数据构造函数语法:因此,Haskell 2010年的报告虽然只提供了一个非正式语义,但在第 *3.17.2节模式匹配的非正式语义 * 中非常清楚地表明,表达式:
将根据第5条规则进行评估:
将模式
con pat1 … patn
与某个值进行匹配(其中con
是由data
定义的构造函数)取决于以下值:con v1 … vn
的形式,则子模式相对于数据值的分量从左到右匹配;如果全部匹配成功,则整体匹配成功;第一个失败或发散分别导致整个匹配失败或发散。con′ v1 … vm
,其中con
是con′
的不同建构函式,则比对会失败。这里,
undefined
的值是⊥,所以第三点适用,匹配发散。如果匹配发散,程序发散,如果程序发散,它必须以错误终止,或者--最坏的情况下--永远循环。它不能继续下去,好像什么都没有发生。诚然,最后一部分没有明确说明,但它是表达式的发散求值的语义的唯一合理解释。