形式陈述
CEK 的名称来自 Control、Environment、Kontinuation。对左到右传值调用 公理库 传值调用 Call by value · CBV 仅当实参先求值为值后才进行函数体替换的求值策略。 λ 演算,机器配置写成 ⟨ C , E , K ⟩ 。环境把变量映到值闭包 公理库 程序语言闭包 Programming-language closure · Function closure · Lexical closure 将函数代码与其定义位置的词法环境配对而成的运行时函数值。 ⟨ λ x . e , E ′ ⟩ ,续延为
K ::= mt ∣ ar ( e , E , K ) ∣ fn ( v , K ) . ar 保存尚未求值的参数及其环境,表示函数位置完成后再算参数;fn 保存已经得到的函数闭包,表示参数完成后进入函数体。核心转移为
若 ⟨ x , E , K ⟩ ⟼ ⟨ λ y . e , E ′ , K ⟩ 若 E ( x ) = ⟨ λ y . e , E ′ ⟩ , ⟨ e 1 e 2 , E , K ⟩ ⟼ ⟨ e 1 , E , ar ( e 2 , E , K ) ⟩ , ⟨ λ x . e , E , ar ( e 2 , E 2 , K ) ⟩ ⟼ ⟨ e 2 , E 2 , fn ( ⟨ λ x . e , E ⟩ , K ) ⟩ , ⟨ λ y . e 2 , E 2 , fn ( ⟨ λ x . e , E f ⟩ , K ) ⟩ ⟼ ⟨ e , E f [ x ↦ ⟨ λ y . e 2 , E 2 ⟩ ] , K ⟩ . 最后一条用函数定义环境 E f 扩展参数绑定,而不是使用调用点环境;这正是词法作用域。配置 ⟨ λ x . e , E , mt ⟩ 是终止状态,其结果是闭包 ⟨ λ x . e , E ⟩ 。这里环境始终映到值闭包,不能一面保存任意未求值表达式,一面仍称其为本 CEK 的值环境。
CEK 是抽象机器 公理库 抽象机器 Abstract machine · Operational abstract machine 把程序执行写成显式配置与局部转移规则,并通过解码或模拟关系连接源语言语义的操作模型。 的具体实例,其转移是运行语义。正确性可陈述为:闭合源项在 CBV 大步语义下求值到闭包,当且仅当从初始配置 ⟨ e , ∅ , mt ⟩ 出发的机器运行到表示同一函数值的终止配置。与源小步语义比较时,变量查找和帧管理可能对应零步或多步源归约,因此通常证明弱模拟,而非未经处理的逐步一一对应。
直觉
Control 保存当前要看的语法,Environment 负责把名字解析到定义时的函数值,Kontinuation 则把“算完之后做什么”拆成有限种帧。函数应用先压入 ar,函数位置完成后把帧换成 fn,参数完成后才进入函数体;两个帧的先后正好编码左到右 CBV。
图片加载失败 CEK 的 C/E/K 同步状态轨迹
例子与边界
令初始环境 E 0 ( z ) = v z = ⟨ λ w . w , E z ⟩ 。项 ( λ x . x ) ( ( λ y . y ) z ) 的完整控制轨迹为
⟨ ( λ x . x ) ( ( λ y . y ) z ) , E 0 , mt ⟩ ⟼ ⟨ λ x . x , E 0 , ar ( ( λ y . y ) z , E 0 , mt ) ⟩ ⟼ ⟨ ( λ y . y ) z , E 0 , fn ( ⟨ λ x . x , E 0 ⟩ , mt ) ⟩ ⟼ ⟨ λ y . y , E 0 , ar ( z , E 0 , fn ( ⟨ λ x . x , E 0 ⟩ , mt ) ) ⟩ ⟼ ⟨ z , E 0 , fn ( ⟨ λ y . y , E 0 ⟩ , fn ( ⟨ λ x . x , E 0 ⟩ , mt ) ) ⟩ ⟼ ⟨ λ w . w , E z , fn ( ⟨ λ y . y , E 0 ⟩ , fn ( ⟨ λ x . x , E 0 ⟩ , mt ) ) ⟩ ⟼ ⟨ y , E 0 [ y ↦ v z ] , fn ( ⟨ λ x . x , E 0 ⟩ , mt ) ⟩ ⟼ ⟨ λ w . w , E z , fn ( ⟨ λ x . x , E 0 ⟩ , mt ) ⟩ ⟼ ⟨ x , E 0 [ x ↦ v z ] , mt ⟩ ⟼ ⟨ λ w . w , E z , mt ⟩ . 轨迹依次查找 z ,再穿过内、外两个恒等函数,最终返回同一个值闭包 v z 。每次变量查找和帧切换都在轨迹中显式出现,没有把 β 归约暗中压成一步。
基础 CEK 没有 store,因而不能直接表达可变引用或地址别名;加入地址分配和存储后得到 CESK 一类机器。call/cc 还要求把当前 K 变成可存储、可恢复的值,并补充相应转移。不能把这些扩展暗中塞回三元 CEK 定义。
推论与应用
CEK 把续延 公理库 续延 Continuation 表示计算余下部分、接收当前结果并产生最终结果的对象。 的高阶“剩余计算”重化成 ar/fn 数据帧,是从求值器经 CPS 与 defunctionalization 导出控制机器的代表例子。它也为控制流分析提供具体状态空间;进一步有限化地址和环境可得到静态抽象,但必须另证抽象转移覆盖具体运行。
与SECD 机器 公理库 SECD 抽象机 SECD machine · Stack Environment Control Dump machine 以操作数栈、词法环境、控制指令和调用现场转储执行 applicative expression 的早期抽象机器。 相比,CEK 直接把源项放在 Control 中,并用一个 K 统一保存“待算实参”和“已有函数”等剩余工作;SECD 先把源项编译成指令,普通中间值进入 S ,函数边界才把调用现场放入 D 。两者都能实现词法作用域下的 CBV,但状态分解和单步粒度不同,不能把 CEK 的 K 与 SECD 的 D 逐字段等同。
机器可作为解释器蓝图,却不是实际 CPU 或性能模型。闭包分配、尾调用优化和环境裁剪可改变实现成本,只要仍保持本页状态转移所定义的可观察 CBV 行为。
参考资料
Matthias Felleisen and Daniel P. Friedman, “Control Operators, the SECD-Machine, and the λ-Calculus,” in Formal Description of Programming Concepts III , 1986,CEK-style control machines。
John C. Reynolds, “Definitional Interpreters for Higher-Order Programming Languages,” ACM Annual Conference , 1972。
David Van Horn and Matthew Might, “Abstracting Abstract Machines,” ICFP , 2010,CESK-style abstraction as a modern extension。