11

从零写一个lambda演算解释器

 3 years ago
source link: https://zhuanlan.zhihu.com/p/347552573
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2020年夏天带着实习生搞了一个实验性的语言 HiLang:

https://gitee.com/hinus/HiLang gitee.com

这个语言是运行在 railgun 虚拟机上的,所以它是强类型的动态语言。因为有编译到字节码的过程,所以我希望为 HiLang 加上静态类型,就如 TypeScript 所做的那样。

由于参与这个项目的同学大多数都是编译器背景的,而不是编程语言背景的,对于在语法文件里直接添加类型感到很棘手。我突然想起来王垠曾经写过一篇解释器的文章:

http://www.yinwang.org/blog-cn/2012/08/01/interpreter www.yinwang.org

所以就想不如自己实现一个小的解释器,可以实现simply typed lambda calculus。通过这个系统,可以检验简单的类型推导算法。到刚刚为止,刚好完成了无类型的 lambda 演算。

由于我不喜欢安装各种环境,所以就没有照着王垠的racket的解释器去写,而是采用cpp重写了所有的逻辑,同时把前缀表达式也转成了中缀表达式。

来看一个例子,引自main.cpp

eval("fac = $f=>$n=>1 if n == 1 else n*f(n-1);"
         "U = $u=>u(u);"
         "Y = $f=>U($x=>f($v=>x(x)(v)));" 
         "print(Y(fac)(5));");

显然,这是一个Y combinator。

这个解释器也和普通的编译器一样,有词法分析器(lexer),文法分析器(parser),然后就直接在抽象语法树上做解释执行了。结构非常简单。为了避免采用bison, yacc等工具,这些部分全部手写了。而且,为了做到直观,也没有采用表格制导的词法/文法分析。

Lexical Scoping

王垠的文章里提到了Lexical Scoping和Dynamic Scoping的区别,我这里采用了和他一样的lexical scoping。从直观上说,对于语句

add = $x => $y => x + y;

显然的是lambda表达式的定义在前,而赋值给变量“add”在后,这就是说,采用lexical scoping,在定义lambda表达式的时候,变量 add 还没有定义,所以,这种写法在我们的解释器里是不成立的:

fact = $n=> 1 if n == 1 else n * fact(n-1);

因为在定义 lambda 表达式的时候,fact是没有定义的,这就会出错了。至此,在函数里不能通过名称调用自己这个结论就变得非常直接了。

解决的方法也比较简单:

Y Combinator

关于 Y 组合子,知乎上有很多答案了,其中这篇回答写得是比较直观的:

Y不动点组合子用在哪里? www.zhihu.com

更容易理解一些。

我们这个解释器提供了 if / else 的三元表达式,而U组合子和Y组合子天然就存在于lambda演算中。感觉已经可以做很多计算了。

未来的计划

主要就是为这套系统增加类型。这一部分工作大家如果有兴趣,可以fork到本地,然后将这个工程当做实验田,随便做各种类型系统相关的实验。

另外,词法错误,文法错误,运行时错误,虚拟机的运态类型,以及内存的统一管理和统一释放都还没做(不写delete的CPP,就问你怕不怕),这一部分由于已经写过很多遍了,懒得写了,如果大家有兴趣,可以帮助补充一下。欢迎提交PR。

谢谢大家。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK