【编译器实现笔记】4.优化的方向

原文地址:https://lisperator.net/pltut/

循环爆栈的问题

λanguage 没有实现循环。其实可以通过递归实现循环

下面就是等价于循环 1~10 的代码

print_range = λ(a, b) if a <= b {
                        print(a);
                        if a + 1 <= b {
                          print(", ");
                          print_range(a + 1, b);
                        } else println("");
                      };

print_range(1, 10);

如果将 range 从 10 增加到 1000,就会发现一个问题:“Maximum call stack size exceeded”。递归最终会消耗光 JavaScript 的堆栈。

当然可以不用递归,再多定义一个 for while 这样的迭代关键字,但首先想想如何在保留递归的同时解决此问题

我们将在下一章节解决此问题

缺乏数据结构的问题

我们的 λanguage 语言似乎只有三种类型:数字,字符串和布尔型。看起来不可能创建复杂的结构,比如对象或列表。但实际我们还有一种类型:函数。

事实证明,在 λ 演算(λ-calculus)中,我们能利用函数来构造任意的数据结构,甚至包括带有继承的对象。

下面我们将通过已有的 λanguage 特性实现数据结构

通过修改 λanguage 本身实现对象的部分留到后面的章节

列表的构造

理论

pair

我们期望有一个函数 cons,会构建一个持有两个值的特殊对象;这个特殊对象我们称为 cons cell 或 pair

两个值一个叫做 car,另外一个叫做 cdr。对于一个给定的 cell 对象,我们可以使用函数 car 和 cdr 来获取对应的值。所以:

x = cons(10, 20);
print(car(x));    # prints 10
print(cdr(x));    # prints 20
从 pair 到列表

一个列表就是一个 cell 对象:

  1. 该对象在其 car 属性中保存了首个(first)元素
  2. 在 cdr 属性中保存了剩下(rest)的元素,而 cdr 这个元素又是一个 cell 对象,如此递归下去

Q:何时递归停止?
A:cdr 为空列表时停止

我们引入一个新的特殊对象 NIL,代表空列表
可以像 cell 一样来操作 NIL(可以应用 car 和 cdr,但是结果是其自身)— 但它不是一个真正的 cell

定义列表就可以像这样:

x = cons(1, cons(2, cons(3, cons(4, cons(5, NIL)))));

查找列表中的第 i 项就可以这样

print(car(x));                      # 1
print(car(cdr(x)));                 # 2  Lisp 中可以简写为 cadr(x)
print(car(cdr(cdr(x))));            # 3                   caddr(x)
print(car(cdr(cdr(cdr(x)))));       # 4                   cadddr(x)
print(car(cdr(cdr(cdr(cdr(x))))));  # 5  到了第五项就没有简写了

基础 cons car cdr NIL 的实现

cons = λ(a, b) λ(f) f(a, b);        # 接收两个值 (a, b)
                                    # 返回一个函数,这个函数 cell 对象
                                    # 等价于:
                                    # cons = function(a, b){
                                    #     return function(f) {
                                    #         f(a, b);
                                    #     }
                                    # };

car = λ(cell) cell(λ(a, b) a);      # 接收一个 cell 函数对象
                                    # 调用 cell,传入的参数是一个函数,该函数会接受两个参数并返回第一个参数。
                                    # 等价于:
                                    # car = function(cell){
                                    #     return cell(function(a, b){return a;});
                                    # }

cdr = λ(cell) cell(λ(a, b) b);      # 类似 car, 但是传入的函数会返回第二个参数。

NIL = λ(f) f(NIL, NIL);             # 模仿一个 cell
                                    # 等价于:
                                    # NIL = function(f){
                                    #     return f(NIL, NIL);
                                    # }
                                    # 用两个 NIL 来作为函数参数,所以 car(NIL) 和 cdr(NIL) 都会返回 NIL

x = cons(1, cons(2, cons(3, cons(4, cons(5, NIL)))));      # 生成一个 {1, 2, 3, 4, 5} 的列表

其他和列表有关的实现,多用到递归

foreach 的实现

foreach = λ(list, f)
            if list != NIL {
              f(car(list));
              foreach(cdr(list), f);
            };
# 等价于
# foreach = function(list, f){
#     if (list != NIL) {
#         f(car(list));
#         foreach(cdr(list), f);
#     };
# }

# 打印列表中的每一个元素,一行一个
foreach(x, println);

range:通过首尾元素构建列表

range = λ(a, b)
          if a <= b then cons(a, range(a + 1, b))
                    else NIL;

# range = function(a, b){
#     if (a <= b){
#         return cons(a, range(a + 1, b));
#     }
#     else {
#         return NIL;
#     }
# }

# 打印1~8的平方
foreach(range(1, 8), λ(x) println(x * x));

可变的 car 和 cdr

目前为止我们的列表都是不可变的
大多数 Lisp 方言都提供了改变 cons cell 对象的方法。
Scheme 中叫 set-car! / set-cdr!。Common Lisp 中富有提示性的命名为 rplaca / rplacd。这次我们更倾向于使用 Scheme 的命名:

cons = λ(x, y)
         λ(a, i, v)
           if a == "get"
              then if i == 0 then x else y
              else if i == 0 then x = v else y = v;

car = λ(cell) cell("get", 0);
cdr = λ(cell) cell("get", 1);
set-car! = λ(cell, val) cell("set", 0, val);
set-cdr! = λ(cell, val) cell("set", 1, val);

# NIL 这时候可以是一个真正的 cons
NIL = cons(0, 0);
set-car!(NIL, NIL);
set-cdr!(NIL, NIL);

## 测试:
x = cons(1, 2);
println(car(x));
println(cdr(x));
set-car!(x, 10);
set-cdr!(x, 20);
println(car(x));
println(cdr(x));

其他数据结构的构造

通过列表的构造,展示了 λanguage 实现复杂可变数据结构的能力,实现对象的过程也是相似的

但是仅仅是使用 λanguage 实现,而不是直接更改 λanguage 使它支持对象,会使得对象非常笨重。

要是能在分词器 / 解析器中引入新的语法,并在求值器中增加新的语义就好了

很多时候为了取得可接受的性能,这也是所有主流语言的实现方式。下一节我们将来探索增加一些新的语法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值