函数式编程语言,我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生

Lucida——一门函数式程序设计语言的诞生

起因

完成了SM的第一个项目之后,老大给了我一段学习的时间,在这个期间我重温了一下CLR,并学习使用了Haskell,并研究了下Python的itertools和functools。不过不停的看书和看文档实在是一件无聊的事情。于是就想搞个看起来比较NB的东西做做。 自己从本科那会就开始想写一个编程语言,不过那会缺乏编译的功底。自从阅读了DSL和Language Implementation Patterns,并写了一个一定规模的Parser之后,对编译有了一个比较全的认识,大概也能想清一个语言的运行机理和实现过程。而且到现在自己用过的语言也不少了,过程的有C,OO的有C++、C#、Java,脚本的有Javascript、Python,函数式的有Scheme、Haskell,看的书更是数都数不清,以至于到后来都觉的没什么意思了。 正好现在很多人都在热衷F#、Scala什么的函数式程序设计语言,说实话,与其去学这些东西,不如自己写一个出来,那这比学这些玩意要深刻的多。倒不是追求reinvent the wheels,我只是觉得现在的wheels未免太多了些,学完这个再学那个,这辈子也学不完。与其被这些东西搞的晕头转向,不如自己弄一个出来,以后再出个什么新东西也不至于会被弄的措手不及。

设计

经过短暂的思考,我决定以Scheme、Haskell和Python为基础,在尽可能短的时间内弄一门函数式编程语言出来,支持函数式编程的所有基本特性。此外,我需要为这个语言提供一个易用的interactive interface,就像GHCi或是IDLE那样。 为了便于构建解析器,我几乎毫不犹豫的选择了Scheme的RPN(逆波兰表达式)语法,尽管这个语法有些诡异: (def (f a) (* a a)) (f 3) => 9 有了Scheme这个参考,构建语言的词法分析器和语法分析器就相当简单了,用了不到两个小时就搞出了Tokenizer和Parser,前端的部分就搞定了。 由于对代码生成不熟悉,况且我要弄的是一门解释型语言,因此有一个中间结构就足够了,这里我用了我比较熟悉的异构抽象语法树:通过一个基类型节点导出各种功能的子类型节点,同时子类型节点可以保存对基类型节点的引用,简单的来说就是Composite Pattern。
我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
常用的语法结构无所谓就是定义,引用,调用函数,分支语句这些,只需按照之前写好的BNF逐一对应上就可以了,算上在草稿纸设计对象模型的时间,这部分大概用了我一个多小时。 接下来就是类型系统了,为了尽快的做出第一个原型,我挑选了四个最基本的类型:数字(Number),列表(List),函数(Function),名字(Name)。
我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
数字自然保存一个整数值,直接使用CLR提供的Int64就足够了,这个很简单。 列表的设计花了我点时间,主要是在同构列表(Haskell风格)和异构列表(Python风格)之间抉择,但想到同构列表还要做类型推导这类蛋疼的工作,我选择了更简单的异构列表(而且功能似乎要更强大,只是无法提供静态类型检查)。 由于函数式语言中的函数也可以作为值传递,因此无需为它命名,只需设置它的形参和方法体即可。在这里它的方法体就是一段语法树,执行这个函数就是对语法树里进行实参代换,然后规约出结果即可,很自然的东西。 名称就是对上面这些类型实例的名字,名称保存一个对基类型的引用,因此它可以指向任何类型,这样使得函数赋值变的很简单,比如说:
我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
这部分花的时间比较长,大概用了三个小时的样子,因为推翻了几个不靠谱的设计,多花了些时间。 接下来就是最外层的解释器了,在这里用的就是常用解释型语言的模式:Read and Eval:首先用Read读入代码,做一个简单的语法检查。然后再由Eval把这段代码执行(实际上就是转换成语法树并规约出结果),然后由控制台输出结果,这里不需要进行什么设计,倒是控制台的配色花了点时间,我向来很不爽GHCi或是IDLE的配色,因此我绝不会让我自己设计的解释器的配色方案跟它们一样烂: 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
做了几个简单的Test之后,把符号表弄了进去,测试了一下,变量赋值,函数递归的都能用,就是列表的递归有些问题,看了下时间,已经连续搞了快10个小时了,于是休息。第二天上午用了近两个小时才把bug揪出来,原来是函数生成List时共享了一部分内存引用,修改了就好使了。再次体会到除错的恶心。 接下来就是引入基本的操作了,这个很简单:写一个预定义的方法表,然后特定的操作符回调方法表中对应的方法。由于以前就用过这种手法,所以几乎没花什么时间,一个下午的时间,算术操作符,逻辑操作符以及基本的列表操作就搞定了,晚饭回来之后顺便写了三个最常用的高阶函数:map、reduce、filter。只要玩过函数式编程或是用过Python的人都知道这几个函数的作用,非一般的好用。 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言 我一直很喜欢Haskell和Python的List Comprehension,于是就琢磨着把它弄进来,但考虑到RPN语法的限制,于是就参考了Python的range函数,以便快速生成一个list。 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言 再次测试,由于都是很明显的东西,所以也出不了什么错。 事实上到这里Lucida已经成为了一个全功能的语言了:变量赋值,递归,列表,高阶函数,以及常用的内置函数,作为一个语言应该有的几样基本都具备了。 之前一直认为写个语言会是个很麻烦的事情,但写完了发现实际上也不算什么,anyway, who could not design and implement a programming language in two days?

接下来的工作

引入浮点型,以及浮点型和整形数字的混合操作:
我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
引入匿名函数和Curry函数: 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
引入字符和字符串类型,并设计它们与列表和数字之间的转换操作: 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
为操作符添加一些有趣的属性: 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言 设置操作符映射,从而可以写出极其简短的代码: 我是如何设计并实现一门程序设计语言——一门函数式编程语言Lucida的诞生函数式编程语言
最后就是写一个语言的手册了,这个更好写,直接仿照K&R的The C Programming Language写就行了。有兴趣的同学可以到这个链接下载阅读。

后记


算上这篇回顾,Lucida从无到原型,再到现在的基本完备,也就是不到一周时间的而已,所以在这里建议那些动不动就要学习新语言的同学停下来,花时间自己写一门语言出来,这比学语言深刻的多,再说写一个小语言也用不了多少时间。 刚刚看到坛子里有一个关于编译的很不错的连载博客,博主好像是叫装配脑袋吧(好奇怪的名字),他把编译那些常用的东西已经讲的很清楚了,认真的看一遍的话写一个语言是不成问题的。只可惜我看到的大多回复都毫无营养,只是毫无意义的顶来顶去。 Anyway, that's all. 有兴趣的话可以交流下编程语言的知识:[email protected]
Tags: 

延伸阅读

最新评论

发表评论