jozefg答案的简短扩展......
Primops 正是那些由运行时提供的操作,因为它们不能在语言中定义(或者出于效率的原因不应该定义)。的真正目的GHC.Prim
不是定义任何东西,而只是导出一些操作,以便 Haddock 可以记录它们的存在。
在 GHC 的代码库中此时使用该构造let x = x in x
,因为该值undefined
尚未,嗯,“定义”。(这要等到 Prelude。)但是请注意,循环let
结构就像 一样undefined
,在语法上都是正确的,并且可以具有任何类型。也就是说,它是一个具有 ⊥ 语义的无限循环,就像它一样undefined
。
……还有一个旁白
另请注意,通常 Haskell 表达式的let x = z in y
意思是“将变量更改x
为表达式中出现的z
任何位置”。如果您熟悉 lambda 演算,您应该认识到这是将 lambda 抽象应用于术语的简化规则。那么 Haskell 表达式只不过是纯 lambda 演算之上的一些语法吗?让我们来看看。x
y
\x -> y
z
let x = x in x
首先,我们需要考虑 Haskell 的 let 表达式的递归性。lambda 演算不允许递归定义,但给定一个原始的定点运算符fix
1,我们可以显式地编码递归性。例如,Haskell 表达式let x = x in x
与 具有相同的含义(fix \r x -> r x) z
。2(我已将x
应用程序右侧的重命名z
以强调它x
与 lambda 内部没有隐式关系)。
应用定点运算符的通常定义fix f = f (fix f)
,我们对let x = x in x
reduce(或者更确切地说不是)的翻译如下:
(fix \r x -> r x) z ==>
(\s y -> s y) (fix \r x -> r x) z ==>
(\y -> (fix \r x -> r x) y) z ==>
(fix \r x -> r x) z ==> ...
因此,在语言开发的这一点上,我们已经从(类型化的)lambda 演算的基础上引入了 ⊥ 的语义,并带有内置的定点运算符。迷人的!
我们需要一个原始的定点运算(即内置于语言中的运算),因为不可能在简单类型的 lambda 演算及其近亲中定义定点组合子。(fix
Haskell 的 Prelude 中的定义并不矛盾——它是递归定义的,但我们需要一个定点运算符来实现递归。)
如果您以前没有看过这个,您应该阅读 lambda 演算中的定点递归。关于 lambda 演算的文本是最好的(网上有一些免费的),但一些谷歌搜索应该能让你继续前进。基本思想是,我们可以通过对递归调用进行抽象来将递归定义转换为非递归定义,然后使用定点组合器将我们的函数(lambda 抽象)传递给自身。定义良好的递归定义的基本情况对应于我们函数的一个固定点,因此函数会执行,一遍又一遍地调用自己,直到它到达一个固定点,此时函数返回其结果。非常整洁,对吧?