第四章:函数成为值
本节阅读量:前三章里,每个表达式最终都会算出一个整数。第四章加入一种新的值:函数。
这一章会一直使用下面的程序:
|
|
先看它在语言层面的动作:
|
|
这里没有“函数声明”这种额外机制。lambda 本身就是表达式,它产生的函数可以放进 let,也可以作为参数和返回值继续传递。
本章仍然保留前面已经跑通的两条路线:
|
|
这一章的信息比前三章多,适合分两个阶段学习:
|
|
第一阶段只要抓住“创建函数”和“调用函数”是两个时刻,不必提前记住 %rdi、%rax 或临时变量编号。
不过,两条路线里的“值”现在既可能是整数,也可能是函数:
|
|
先把这种对应关系留在脑中即可。后面的几节会分别把它落实到 AST、解释器、IR、栈帧和汇编,不需要在这一页提前记住所有细节。
先运行贯穿示例
程序已经放在 examples/function_value.lang:
|
|
AST 是:
|
|
解释器输出:
|
|
IR 第一次把主程序和函数体分开放置。这里先观察整体形状,不必理解每个临时名字的编号:
|
|
这里先注意三件事:
function lambda0把一段函数代码的入口变成值。inc0 = f.0和整数的let一样,只是在保存一个值。call inc0 41到运行时才取出被调用的函数值,通过它进入lambda0。
这一页先验证到 IR。完整的汇编、手动链接和退出状态检查会在栈帧与汇编两节走通;章末还有一次独立的 C++ runtime 联调,用 cout 直接显示完整结果。
本章增加的部分
|
|
整数、负整数、加法、变量、let、eq? 和 if 都会继续保留。新功能不是另起一套语言,而是让已有表达式可以出现在函数体和调用位置中。
这一章刻意停在哪里
第四章只实现单参数、且不读取外层局部变量的函数。它还没有第五章的 letrec 具名递归构造。环境捕获要等闭包章节;本章的编译器也还没有运行时类型标签和类型检查,因此编译路径只使用实际执行路径上值种类正确的小整数程序来讲解。
读完本章后,你应该能沿着贯穿示例说明:
lambda为什么不会立刻执行函数体。- 函数怎样被保存、传入、返回和调用。
- 解释器为什么为一次调用建立新环境。
- 编译器怎样把函数变成地址,再通过地址调用它。
- 主程序栈帧和函数栈帧怎样在调用期间各自保存数据。
本节目录