语义工程和PLT Redex

第1部分 规约语义

第1章 语义via句法

描述编程语言自句法始. 正如每个程序员所知, 语言的句法总以BNF (Backus-Naur Form) 语法的某个变种形式出现, 其枚举了符合语法的词汇和句子. 困难之处实际上在于刻画程序的意义, 即程序是如何进行计算的.

在这本书的第一部分, 我们建立了一个基于句法的语义描述方法. 我们从这样的观察开始, 即计算 (computation) 是对于算术 (calculation) 的一般化, 一个孩子所接受的算术训练从"11等于2"这样的材料开始. 诀窍在于看出这种形式的算术也可应用到程序上来.

程序的算术意味着观察一个表达式或者语句的句法, 然后将其与另一个表达式或者语句联系起来, 通常假定是更简单的. 对于表达式1+1而言, 前面的声明是容易理解的. 它等于2, 即1+12关联起来. 即便是函数应用于参数值也可以这种方式表达 [译注: 虽然+本身就是一个二元函数]:f(4)=24+55,如果定义f(x)=2x+55.

用数学的语言来说, 我们是将编程语言的语义描述为其句法上的关系. 对于学过函数式编程语言的人而言, 这种声明并不令人意外. 我们知道函数式编程并不超出将七年级代数转换为编程语言太多, 而代数定律是将代数表达式互相联系起来的等式. [译注: 在某种意义上来说, 这句话是一种虚张声势.] 但令人惊讶的是, 这种方法具有描述(几乎)所有编程语言的语义的可能性, 即便是包含命令式副作用的语言.

现在我们来介绍这种想法, 从将句法定义为集合开始, 这是必要的数学元知识.

第1.1节 定义集合

BNF语法可有多种用途. 一种含义是字符串的集合. 另一种解释是"树"的集合, 其常被称为抽象句法(树). 本书我们总是指后者.

对于本章和下一章而言, 我们使用下面的BNF语法作为例子:B=t|f|(BB)我们将其当作下列施加于抽象句法树集合B上的约束的缩写:tBfBaB 且 bB(ab)B从技术上说, B是满足以上约束的最小集合. 为了构造这个集合, 我们先容纳基本元素tf, 然后归纳地将其中的东西组合成复合元素.

记号: 我们有时用"B"表示"集合B", 但有时"B"也代表"B的任意一个元素". 从上下文来看, 含义总是明确的. 有时, 我们将下标或一撇附加在集合的名字上来集合的任意元素, 例如"B1"或者"B". 因此, 以上约束也可以写成tB[a]fB[b](B1B2)B[c]

在有限的空间之中枚举出B的所有元素显然是不可能的:B={t,f,(tt),(tf),}然而, 给定某个树, 我们可以通过论证它满足约束来证明其属于B. 例如, (t(ft))就在B中:1.tBby [a]2.fBby [b]3.tBby [a]4.(ft)Bby 2, 3, and [c]5.(t(ft))Bby 1, 4, and [c]通常, 这样的论证也可以被安排成所谓的证明树的形式:tB [a]fB [b]tB [a](ft)B [c](t(ft))B [c]绝大多数时候, 证明树以没有标签的形式出现, 因为每一步通常都是显然的:tBfBtB(ft)B(t(ft))B

练习1.1. 以下哪些是B的元素?
  1. t;
  2. ;
  3. ((ft)(ff));
  4. ((f)(t));
  5. "hello".
若是, 则提供一个证明树.

第1.2节 关系

关系是序对的集合. 例如, 我们可以将关系R定义成将B的每个元素与自身匹配的关系:aBa,aR对于像R这样的二元关系, 与其记a,aR, 不如写成aRa:aBaRa甚至可以更简单B1RB1只要其被理解为R的定义. 实际上, 关系R是自反的, 对称的, 传递的, 即其满足以下三个约束条件:一个关系R是自反的当且仅当aRa (对于任意的a而言)一个关系R是对称的当且仅当aRbbRa一个关系R是传递的当且仅当aRbbRcaRc如果一个关系是自然的, 对称的, 传递的, 那么其就被称为一个等价关系 (equivalence). 关系的特定名字, 例如=, 暗示了该关系是一个等价关系.

以下定义了一个关系r, 它既不自反, 对称, 也不传递.(fB1)rB1[a](tB1)rt[b]在规约语义的上下文中, 这样的关系被称为规约的概念. 对于该定义的一个小小的修饰可以产生一个自反关系r:(fB1)rB1[a](tB1)rt[b]B1rB1[c]另一种定义r的方法在于扩展r并显式约束新的关系为自反的:B1rB2如果B1rB2[ab]B1rB1[c]因此关系rr自反闭包. 我们还可以通过添加对称和传递约束来定义另一个关系:B1rB2如果B1rB2[ab]B1rB1[c]B2rB1如果B1rB2[d]B1rB3如果B1rB2B2rB3[e]关系rr对称传递闭包, 并且它也是r自反对称传递闭包或者说等价闭包.

第1.3节 作为等价关系的语义

Br的实际例子暗示了一个编程语言是如何通过句法以及句法上的关系定义的, 或者更确切地说, 作为一个抽象句法树上的集合B以及该集合上的一个关系r. 实际上, 或许机敏的读者可能开始怀疑B是布尔表达式的语法, 其中t代表true, f代表false, 代表"或"运算符. 关系r将具有相同(布尔)值的B表达式等同起来.

的确, 使用上面的约束, 我们可以表明(ft)r(tt), 正如falsetrue=truetrue:(ft)rt [a](tt)rt [b]tr(tt) [d](ft)r(tt) [e]然而, 这并不能直接得出和布尔"或"表现得完全一致. 如果我们希望建立这种联系, 那么我们不得不证明关于的一般声明, 例如对于任意的表达式B1(B1t)rt.

换言之, 编程语言的语义和我们想要知道的这个语义的性质之间存在一般性的gap. 出于许多目的, 语义的性质和其将表达式或程序联系至的值一样重要. 例如, 如果的确满足"或"的法则, 那么一个编译器或许可以安全地将(B1t)优化为t. 类似地, 如果编程语言的语义可以保证数字不会被加到任何非数字的东西上去, 那么语义的实现就无需检查加法运算的参数以保证它们都是数字.

第1.4节 语义via规约

关系r让人想到小学里从算术和代数中学到的=. 正如在这种情况下我们教授学生出于各种目的都使用这样的等式推理, 我们可以使用r关系来证明特定表达式的等价性. 尽管如此, 在一般情况下, 这个关系并没有指明该如何从一个任意的B得到tf——这是我们在构建一个语义的解释器时所真正需要的东西.

在这种意义下, 关系r实际上比r更有用. 在r的定义的两种情况下, 一个表达式都与另一个更小的表达式联系起来. 而且, 对于任何表达式B, 要么Btf, 要么rB与至多一个另外的表达式联系起来. 因此, 我们可以将r想成是一个单步规约, 与解释器采取单一的求值步骤迈向最终的值对应起来.

使用r, 我们可以定义其自反传递闭包𝕣:B1𝕣B1B1𝕣B2如果B1rB2B1𝕣B2如果B1𝕣B3B3𝕣B2这产生了一个多步规约关系. 特别地, 多步关系𝕣将一个表达式映射至许多其他的表达式, 但是tf之中最多只能有一个.

关系r𝕣被有意设计为非对称的, 这强调了求值具有特定的方向性. 例如, 给定表达式(f(f(tf))), 我们可以表明存在一个从它到t规约序列:(f(f(tf)))r(f(tf))r(tf)rt左列的空白隐式地被前一行的右列的表达式所填充. 于是, 每一行都是对于(f(f(tf)))𝕣t的论证中的一步.

练习1.2. 通过构造基于单步关系r的规约序列证明(f(f(ff)))𝕣f.

第1.5节 上下文中的规约

表达式((ft)f)该怎样规约呢? 根据r或者𝕣, 它压根就不能规约. 从直觉上来说, ((ft)f)应该被规约为(tf), 即根据(ft)rt来简化第一个子表达式. 然而, r的定义中没有能够匹配((ft)f)作为源表达式的规则. 也就是说, 我们只能规约具有形式(fB)或者(tB)的表达式. 尽管最外层的的右侧表达式可以是任意的, 但左侧的表达式必须是t或者f才行 [译注: 即才能规约].

如果我们希望将这样的B表达式规约为答案, 那么我们必须扩展r关系为另一个能够支持子表达式规约的关系.B1rB2如果B1rB2[a](B1B2)r(B1B2)如果B1rB1[b](B1B2)r(B1B2)如果B2rB2[c]关系rr兼容闭包. 类似于r, r是一个单步规约关系, 但是r允许对于表达式的任意子表达式进行规约. 可规约表达式 (reducible expression) 被称为redex, 而一个redex周围的文本被称为其上下文.

特别地, 关系r包含((ft)f)r(tf). 我们可以用以下证明树来说明这个包含:(ft)rt(ft)rt [a]((ft)f)r(tf) [b]继续使用, 我们可以将((ft)f)规约至t:((ft)f)r(tf)rt最后, 如果我们将r定义为r的自反传递闭包, 那么我们就得到了((ft)f)rt. 因此, r是由r生成的自然规约关系.

一般来说, 仅仅关系r的自反闭包r, 等价闭包r, 以及自反传递闭包𝕣不是很有趣. 我们往往最感兴趣的是兼容闭包r及其自反传递闭包r. 这两种关系与表达式求值和解释的典型概念相对应. 而且, r的等价闭包=r也是有趣的, 因为其将产生相同结果的表达式联系起来.

练习1.3. 解释为什么(f((tf)f)),t𝕣.
练习1.4. 通过基于r的规约序列来证明(f((tf)f))rt.

第1.6节 求值函数

r使我们接近了求值的有用概念, 但是我们还没有抵达那里. 尽管((ft)f)rt, 但是我们也有((ft)f)r(tf)((ft)f)r((ft)f). 然而, 对于一个求值器而言, 我们仅关心一个B是否能求值到一个结果以及这个结果是f还是t. 其他一切都是无关紧要的.

我们使用两个定义来形式化地陈述这个想法. 首先, 我们需要刻画我们所认为的B"程序"的结果R是什么. R=t|f显然, RB的一个子集, 因为结果也是我们的"编程语言"的表达式.

我们的第二个定义将求值刻画为evalrr关系, 其将每个表达式映射至一个结果.evalrr:BR,B{f如果Brft如果Brt这里我们使用了另外的记号来定义关系, 这暗示了其为函数, 即将每个元素映射至最多一个元素的关系. [译注: 这是所谓的partial function.] 我们使用函数记号的原因在于, 若evalrr作为求值器要make sense的话, 那么它就必然是一个函数 (至少对于确定性的编程语言而言).

这个关系的名字既有下标也有上标. 自然地, 前者只是在说这个函数基于关系r, 而后者是在告诉我们求值的定义基于关系r. 我们用这两者来装饰函数的名字, 因为存在许多其他变种定义. 例如, 以下定义使用基于r的等价关系而不是自然规约关系.evalr=r(B)={f如果B=rft如果B=rt等价关系=r当然是r的兼容, 自反, 传递, 对称闭包. [译注: 更确切地说, 是兼容闭包的等价闭包, 因为这两个运算并不可以交换.] 通过=r来定义一个求值函数表明了程序的计算真的就只是一般化了来源于代数的运算概念.

练习1.5. 关系r,r,𝕣,r,r,=r中哪些是函数? 对于非函数的关系, 找到一个表达式和另外两个与其关联的表达式.
练习1.6. 使用上述定义来找出evalr=r(((ft)t))evalrr(((ft)f))的结果.

第1.7节 记号总结

以下表格总结了我们到目前为止引入的概念和记号.名字定义直觉-表达式语法的成员上的基本关系没有上下文的单一"规约"步骤-相对于表达式语法的-的兼容闭包上下文中的单步规约--的自反传递闭包复数求值步骤 (零或更多)=--的对称传递闭包将产生相同结果的表达式等同起来eval--投影于某个范围的关系基于-或者=-的完全求值[译注: 如果仅是对称闭包, 一般情况下并不保持传递性, 所以需要对称传递闭包.] 最终, 我们希望得到eval-, 即一般的求值关系.

第2章 分析句法性的语义

一旦我们有了一个编程语言的句法和语义, 我们可以提出问题, 进行实验, 以及考虑变体. 在本部分中, 我们将看看编程语言理论家会提出的最基本的问题, 并研究如何回答它们. 在本书的第二部分, 我们将引入对于句法和语义进行实验的工具, 通常其将有助于提出猜想和问题.

现在我们使用第一章引入的句法和语义以刻画我们可以提出什么样的问题以及如何严格地回答这些问题. 第一节展现了如何用数学术语提出关于语言的问题. 第二节用数学定理和证明刻画了回答, 引入了对于本书的剩余部分而言关键的证明技术.

第2.1节 从问题到数学声明

第一章定义了数个求值器, 包括evalrr. 从编程语言实现者的角度来看, 这个evalrr函数使用了某种类似于机器的东西, 这个机器的初状态, 中间状态和终状态是B表达式, 而指令是r关系. 它启动程序, 等待直至机器到达一个终状态 (tf), 然后报告这个最终结果.

一个显然的问题是, 这个求值器是否对于某个固定的程序总是产生恰好一个结果. [译注: 从上下文看, 这里更确切的说法是产生至多一个结果, 因为产生恰好一个结果是更强的条件, 是说这个关系是一个total function.] 用数学术语来说, 我们是在问这个求值器是否是一个函数. 如果的确如此, 那么若我们观察到对于相同的程序产生了两个不同的结果, 那么就知道这个求值器的实现是错的.

现在回忆一下, 关系和函数都是序对的集合. 每个序对将一个输入和一个输出结合起来. 关系和函数的不同之处在于, 后者对于任何输入最多只包含一个相关的序对. 因此, 我们的第一个问题是在问以下声明是否成立:

对于所有的B0, (B0,R0)evalr=r(B0,R1)evalr=r可以推出R0=R1.
用函数记号的话, 那就变成了
对于所有的B0, evalr=r(B0)=R0evalr=r(B0)=R1可以推出R0=R1.
[译注: 读者应该将这种函数记号当成关系记号的等价物, 而不是函数真的一定产生了一个值, 因为我们尚不知道这个关系对于每个输入是否都存在至少一个输出. 或者, 读者可以将evalr=r(B0)=R0解释为evalr=r对于B0有定义且R0是一个相应的输出. 另外, 之前的evalrr到这里变成了evalr=r, 译者感到行文有点不太连贯, 虽然这并不影响后面的呈现的正确性.]

第1章不止定义了一个求值器, 实际上定义了两个. 从理想来说, 这两个定义应该引入的是相同的函数. 也就是说, 这两个求值器对于相同的程序应该产生同样的结果. 证明这样一个声明将允许我们根据需要交换地使用这两个定义. 例如, 当与一个数学老师争辩时, 我们可以使用evalr=r来刻画程序执行泛化了七年级代数. 而当与负责B编程语言实现的软件工程师讨论B的语义时, 或许我们应该使用evalrr.

因此, 我们的第二个问题关心的是等式求值器evalr=r和有向求值器evalrr之间的关系. 更确切地说, 我们想要知道它们是否是相同的函数:evalrr=evalr=r若将函数视为序对的集合, 那么问题就变成了这两个集合是否包含相同的元素:

对于所有的B0R0, (B0,R0)evalr=r当且仅当(B0,R0)evalrr
这个陈述一个大致上的翻译为
对于所有的B0R0, evalr=r(B0)=R0当且仅当evalrr(B0)=R0
[译注: 同样地, 我们应该将这种记号当作关系记号的等价形式.] 这个翻译的意思是说如果evalr=r对于B0有定义 [译注: 有定义即存在以B0为输入的相应序对] 而输出就是R0 [译注: 第一个问题解决了的话, 那么R0就是唯一的], 那么evalrr对于B0也有定义, 并且输出也是R0, 反方向也该这么解释. [译注: 如果这个问题解决了的话, 那么evalr=r是(部分)函数就可以推出evalrr也是函数.]

自然地, "有定义"这个表达暗示了另外一个问题, 即是否求值器对于所有可能的输入都产生一个输出. 这个陈述有一个直接的数学表达, 即

对于所有的B, 存在一个R满足(B0,R0)evalr
注意到, 这里我们丢掉了上标, 因为使用哪一个是无关紧要的.

第2.2节 作为定理的回答

既然我们已经理解了关于编程语言的句法和语义我们所能提问的最简单问题, 那么其回答显然应该是关于相应数学模型的定理. 我们的第一个定理是在陈述evalr=r是一个(部分)函数.

定理2.1. 如果evalr=r(B0)=R1并且evalr=r(B0)=R2, 那么R1=R2.

从证明evalr=r的定理开始, 我们遵循历史路线, 首先建立了等式演算的一致性. 一旦我们证明了这两个定义描述了相同的关系, 那么我们就知道evalrr也是一个函数. 同样根据传统, 在显然的时候我们去掉了定理中的量词前缀 (对于所有, 存在), 并且我们使用函数式记号来表达结果存在且是一个特定的值.

证明2.1. 为了证明该定理, 我们假设对于某B0,R1,R2evalr=r(B0)=R1evalr=r(B0)=R2. 基于假设, 现在我们试图证明R1=R2. 根据evalr=r的定义, 我们知道B0=rR1B0=rR2. 注意一下, 这里是=r而不是=. 因为(根据定义)=r是一个等价关系, R1=rR2. 为了到达我们想要的结果R1=R2, 即R1R2是等同的, 我们必须研究这个等价关系的性质和计算的性质. (未完待续)

前述证明的最后论证要求我们研究对于M,NB而言的M=rN的证明 (或者说证明树) 的形状. 既然=r是单步规约关系r的自反, 对称, 传递闭包, 证明M=rN的计算包含有一系列两个方向上的这样的单步规约:ML1L2L3L4L5N[译注: 原谅我将本来二维的图片用这样的单行的数学符号表达, 但是图片的意图应该清晰地传达了.] 在这张图片里, 每个表达式LiB而每个箭头都表示序列中的相邻项之间的一个r关系. 形式地, LiLjLirLj相对应.

来源于这张图片的关键性的洞察在于或许可以重塑这个计算, 使得所有的规约步骤都是从M到某个L以及从N到相同的L. 换言之, 如果M=rN, 那么存在一个表达式L满足MrLNrL:MLN如果我们能够证明对于两个等价的项总是存在这样一个L, 那么一致性的证明就结束了.

证明2.1. (剩下的证明) 回忆一下, 我们有R1=rR2根据(尚未证明的)声明, 必然存在表达式L满足R1rLR2rL但是, R的元素仅有tf, 它们不可能被规约为除了自身以外的任何其他表达式 [译注: 严格说来, 这也需要证明, 但是使用简单的结构归纳即可], 因此L=R1L=R2, 这意味着R1=R2.

根据前面这样的推理, 我们将evalr=r的一致性证明转换为了关于证明树的形状的声明, 即建立M=rN之后我们能够重塑它的证明树. 这个关键性的洞察应该归功于Church和Rosser, 它们运用这个想法分析了被称为λ演算的语言的一致性 (这是下一章的主题). 据此, 这样的引理以他们的名字命名.

引理2.2. [=r的一致性]: 如果M=rN, 那么存在表达式L满足MrLNrL.
证明2.2. 既然给定了M=rN, 以及=r的定义归纳性地扩展r为其对称传递闭包, 我们根据M=rN的推导的结构 (也就是其证明树的结构) 上的归纳来证明该引理:

又一次, 我们基于 (modulo) 另外一个关于规约系统的声明的证明完成了证明. 更确切地说, 我们假定如果遇到了左边的情况, 那么可以找到一个项LB使得我们能够构造右边的情况:LMNL这个性质被称为菱形性质, 因为这张图片需要"规约分支"能够被补全为菱形的形状. 当规约概念 (例如r) 的兼容闭包的自反传递闭包满足这样的性质时, 其被称为Church-Rosser的.

引理2.3. [r的Church-Rosser]: 如果LrMLrN, 那么存在表达式L满足MrLNrL.

既然LrM由一系列r证明步骤构成, 那么检查后者是否也满足菱形性质是很自然的事情. 如果的确如此, 那么我们可以将r的小菱形组合起来获得r的大菱形. 尽管菱形性质对于r并不那么成立, 一个足够强的性质的确可以满足.

引理2.4. [r的类菱形性质]: 如果LrMLrN对于MN成立, 那么以下三个条件恰有一条可以满足:换句话说, "规约分支"可以被补全为菱形, 或者存在"三角形"风格的补全方式.
证明2.4. 为了证明这条引理, 我们回忆一下r被归纳性地定义为r的兼容闭包. 因此, 自然的处理方式为假定LrM并于证明的结构上施行结构归纳:

第3章 λ演算

第3.1节 函数和λ演算

λ演算的句法提供了一种简单而规整的方法来待应用的函数, 抑或是作为其他函数的输入和输出的函数. 在λ演算之中, 对于这种函数的刻画专注于从参数到结果的规则, 而忽略了对于函数的命名, 以及其定义域和陪域. 例如, 一位数学家会将某个集合A上的恒等函数写成是f:{AAxx而若用λ演算的句法的话, 则为

第3.2节 λ演算: 句法和规约

第3.3节 编码布尔

第3.4节 编码序对

第3.5节 编码数字

第3.6节 编码和错误

第3.7节 递归

第3.8节 一致性和正规形式

第3.9节 正规形式和规约策略

第3.10节 历史

第4章 ISWIM

在1950年代晚期和1960年代早期, 人们发现编程语言和λ演算的诸多方面之间存在联系. 这里的动机是多样的, 有的只是想要刻画Algol 60的语义, 而有的是试图通过已经得到良好理解的数学系统来理解整个编程语言领域的面貌. 实际上, 人们希望学会如何系统设计编程语言.

Landin是这群研究者之中的新星. 他设计了一种被称为ISWIM的基于λ演算的语言, 并探索了其通过抽象或者说虚拟机器的应用和实现. 不严格地说, ISWIM几乎是一个函数式编程语言, Scheme编程语言是其最近的亲戚. [译注: 当然, 有的人可能认为Standard ML之类的语言比Scheme更加接近于ISWIM. 另外, ISWIM其实是一类语言, 而不是一个语言.]

虽然人们欣赏ISWIM的表达力并将其当作灵感的源泉, 人们也意识到ISWIM和λ演算之间的关系并不那么直接. Plotkin最终设计了λ演算的一个变种, 其与ISWIM有着直接而自然的对应关系. 他也表明了, 这种对应是一般性的原则. 他将这原则具体化为了ISWIM的两个变种: call-by-name和call-by-value.

这一章和接下来的一章里我们呈现了ISWIM, 其演算, 以及ISWIM和演算之间的关系. 具体来说, 本章呈现了ISWIM的句法和ISWIM的"算术"系统. 这包含一个求值器的定义以及对于这个求值器所定义的最广泛等式系统的探索. 接下来的一章展现了λ演算的一个元定理是如何自然地定义了ISWIM的一个高层次的抽象机器的.

第4.1节 ISWIM表达式

ISWIM是一族编程语言, 根据字面常量和原始运算符而有所不同. ISWIM族的每一语言都扩展了λ演算的句法以基本常量的集合b和一族多元函数on.

M,N,L,K=X|(λX.M)|(MM)|b|(onMM)

这里的on里的n不是确定的, 其代表了原始运算符的参数数目 (元数), 并且我们额外限制这个应用的实际参数的数目必须等于相应的原始运算符的元数.

为了明确起见, 我们使用b={n|n}o1={add1,sub1,iszero}o2={+,,,}其中n是表示数字n的numeral.

FV[]可以自然地扩展至新的句法上来.FV(b)=FV(X)={X}FV((λX.M))=FV(M){X}FV((M1M2))=FV(M1)FV(M2)FV((onM1Mn))=FV(M1)FV(Mn)b[XM]=bX1[X1M]=MX2[X1M]=X2, 其中X1X2(λX1.M1)[X1M2]=(λX1.M1)(λX1.M1)[X2M2]=(λX3.M1[X1X3][X2M2])其中X1X2X3FV((λX1.M1))X3FV(M2)(M1M2)[XM3]=(M1[XM3]M2[XM3])(onM1Mn)[XM]=(onM1[XM]Mn[XM])

第4.2节 使用ISWIM进行计算

受到Algol 60的call-by-value参数传递机制的启发, Landin以类似的方式设计了ISWIM的函数. 也就是说, 在ISWIM之中, 一个函数应用在函数取得控制之前对于其实际参数求值. 这么做可以消除在函数体中对于一个参数多次求值或者追踪一个参数是否已经被求值过了的开销. 这也排除了纯粹λ演算中的一些incongruities, 例如表达式((λx.1)(sub1(λy.(λx.y))))sub1应用于一个并非numeral的东西 [译注: 从句法上说], 会被call-by-value的参数传递机制发现. 而在纯粹λ演算之中, 这种错误应用 (mis-application) 因为可以直接施行β规约而不会被发现.

因为ISWIM的函数只接受被求值了的参数, 那么我们必须首先规定何谓. 从直觉上说, 值是对其求值可以立即知道结果的项. [译注: 一般我们在操作语义之中采用这种观点, 在其他场合, 值还有另外的含义.] 或者说, 值是任意的表达式在规约过程之中的目的地. 我们定义值的集合如下:V,U,W=b|X|(λX.M)基本的常量当然是值, λ抽象也是值, 不论其体的形状如何. 因此, Landin将函数作为第一级对象的想法引入了编程语言的研究之中. [译注: 至于第一级对象这个想法或者说术语, 最早提出者应该是Christopher Strachey, 他也算是指称语义学的奠基人之一.]

应用不可能是一个值, 因为它就像刻画计算的发动机一样. 变量也是值, 不过这值得额外的解释. λ演算中的变量有且只有一种目的: 作为形式参数 (formal argument) 或者说parameter. 对于一个程序员而言, parameter的作用在于为函数的实际参数充当占位符号 (place holder). 既然ISWIM的函数只能消化值, 那么变量就总是代表值. 因此, 我们暂时也将变量看成是值. 第4.7节回顾了这个决定.

基于对于值的刻画, 现在我们可以定义ISWIM的基本规约概念.((λX.M)V)βvM[XV]这个关系有别于β, 因为参数必须是V的成员. 在ISWIM中, 你不能将任意的项替换进函数的体里.

限制参数必须是一个值迫使参数表达式的规约在对于函数应用求值之前进行. 例如, ((λX.1)(sub1(λy.y)))就不能被规约至1, 因为(sub1(λy.y))V.

除了函数应用, 计算ISWIM的系统还必须考虑原始运算. 既然ISWIM是随着常量集和原始运算符集的改变而变化的一族语言, 那么对于这些集合我们需要一个足够一般的规约概念, 因而我们引入了一个部分δ函数:δ:on×b××bV也就是说, δon的元素和n个基本常量映射至一个值. 添加δ至演算引入了第二种规约概念.

对于我们之前约定好了的常量和运算符, 我们选择了以下具体的δ函数.δ(add1,m)=m+1δ(sub1,m)=m1δ(iszero,0)=(λx.(λy.x))δ(iszero,n)=(λx.(λy.y))n0δ(+,m,n)=m+nδ(,m,n)=mnδ(,m,n)=mnδ(,m,n)=mn

第4.3节 α, η和商

第5章 抽象句法机器

ISWIM求值器的定义是优雅的, 但是难于实现. 为了确定一个程序的结果, 程序员可以自由地应用等式系统的规则以任意的顺序, 以任意的方向, 在程序的任意位置. 然而, 这种灵活性并非将求值器实现为(元)程序的良好基础. 根据一致性定理,

第6章 抽象寄存器机器

依照所处的上下文, 求值器的效率可能并不重要. 例如, 如果

第6.1节 CC机器

前一章的句法机器的每个求值循环中都需要执行三个步骤. 首先, 其需要判断程序是否是一个值; 如果是的话, 那么机器就停止了. 其次, 或者说程序并非一个值, 那么机器会将程序分成一个求值上下文E和一个应用(UV)或者(onV1Vn). 最后, 如果这个应用是一个βv或者δ可规约表达式, 那么机器会构造contractum M, 然后填充求值上下文EM. 这一步的结果是下一个机器状态, 于是求值循环又重新再来.

这个机器显见的低效之处在于反复将程序分成一个求值上下文和一个可规约表达式.

第6.2节 SCC机器

第6.3节 CK机器

第6.4节 CEK机器

第6.5节 历史

第7章 尾调用和更多的空间节省

第8章 控制: 错误, 异常和延续

第9章 状态: 命令式赋值

第10章 简单类型ISWIM