首页 > 代码库 > 解释器求值的那个夏天
解释器求值的那个夏天
求值从何始?
开始思考求值的实际意义是在自己要实现一个解释器的时候,那个时候很头疼,一直在思考求值的自然含义,怎样求值,求值会遇到的问题。甚至思考值本身的意义是什么?
这可能是个很令人不屑一想的问题,试想在使用编程语言的时候,大多数时候是在思考算法,实现业务逻辑,架构设计,思考这个语言本身的问题,既不能加快开发,也不能明悟。
我为什么思考?
在我开始学习SICP的时候,我的小伙伴已经学到一半,并开始实现一个解释器,本来没打算写什么解释器,但是出于暑假无聊,我也"跟风"写了一个scheme解释器。
我coding过半,完成了词法,语法解析部分之后,开始为这个空的global环境增加一些基本的过程,例如最基本的:
+ - * / lambda define
但是这时候我开始思考这些基本过程的意义是什么,scheme这门语言是允许overwrite一些方法的,比如用户可以:
(define + -)
这就意味着+的功能被修改成了原来-的功能,那么我开始反问: 既然这些方法可以被重写,那么我现在设置这些值是为了什么,反正这些规则是可以改变的,到底有什么意义?如果我这样做是错的,那么到否意味着我应该把握住自己的“权威”,禁止用户去修改这些我设定的初始值;如果是对的,那么如果用户胡乱重写这些方法,那么最后可能这门语言什么用都没了,再也找不到+的意义,也再也找不到-的意义,最后没有意义。
这个问题我想了以后,发现是永远不可能有一个正确的答案,有的只是设计者的一些态度,世界本来就始于虚无,能够存在就是因爲创造,所以最后我将这些算数操作依然处理为用户可重写的,但是却保留了define,lambda这些关键字,纵然可以重新归于0,但不能缺乏创造。
在哪求值?
刚才我提到了global环境,环境便是值所在处,所谓猪肉长在猪身上,若要在环境中求值,必定先要在环境中注入值,譬如在global环境增加一个最常见的求阶乘的函数:
(define factorial (lambda (n)
(if (= n 1)
1
(factorial (- n 1)))))
在下次调用时只要身处这个global环境,就可以找到这个factorial函数:
(factorial 3) => 6
细心的朋友会问
- n是怎么传到这个函数的计算里的?
- 同时,还应该问=,-,甚至factorial是怎么被访问到的?
计算需要求值环境,所以这一切的值的访问,需要环境,在计算时任何变量都会绑定到一个环境中,如果在环境中搜索到值,那么求值ok,否则undefined。(factorial 3)在计算的时候绑定了global环境。
环境链
环境如果是单一的,那么对于各个对象的私有状态是个巨大的挑战。试想coder1将name修改为‘coder1‘,coder2将name修改为‘coder2‘,当coder1读取name时,意外地发现竟然不是自己的名字。而为了实现两个coder各自的状态,就得在变量命名上要求不重复,更坏的情况是coder越来越多,这个问题越来越突出。
利用私有环境可以维护各个对象的私有状态,coder1设置读取自己的name属性,就可以去这个环境中获得,而其它的变量依然可以去global中获取,所以环境链成了一个自然的解决方案,以下是(factorial 3)的计算环境:
-GLOBAL
^ - => [procedure:minus]
| * => [procedure:multiple]
| factorial => [procedure:factorial]
-ANONYMOUS COMPUTING ENVIRONMENT
^ n => 3
|
CURRENT
(factorial 3)在求值函数体的时候,将当前的环境指向了这个为了当前计算而产生的匿名计算环境,里面有的就是通过参数传入的n,名字为n的变量便绑定到了这个值,而当求值失败后,解释器判断是否存在下一个环境,如果存在,那么求值将在环境链下个环境中求值,直到求值成功或环境链遍历结束,返回undefined。
一个求值例子: javascript的this求值
js的求值本质适合scheme一样的,不过是语法的略微不同,而js中的this的求值比较好玩,它是一个异数,它是和当前对象绑定的,解释器根据当前执行这个函数的对象来判断这个this的值,this是只读的。
可以有几个有趣的例子来对this有个认识:
-
global this
var name = ‘global‘; alert(this); // [Object:Window] alert(this.name); // ‘gloabl‘
-
新建对象里的this
var name = ‘global‘; var Coder = { name : ‘srggggg‘, name2 : this.name, name3 : name, getName : function() { return this.name; }, }; alert(Coder.getName()); // ‘srggggg‘ alert(Coder.name); // ‘srggggg‘ alert(Coder.name2); // ‘global‘ alert(Coder.name3()); // ‘global‘
-
嵌套的function的this
var name = ‘global‘; var Coder = { name : ‘srggggg‘, getNameFunc : function() { return function() { return this.name; }; } }; alert(Coder.getNameFunc()()); // ‘global‘
第一种情况是最直白的由于在global环境中求值,this就是global环境下的Window对象。
第二种情况,this显式出现2次,隐式出现1次。
- name2的定义中this是绑定到global中的Window对象,定义本身是一种求值过程,实际上是这次的define执行被绑定到了global环境,所以name2是‘global‘
- name3的定义中没有this,但是由于name从global中被解析出来,所以可以看作this.name
- getName函数定义中用到了this,这个this是真正绑定到Coder对象的,getName的函数体执行肯定是在Coder对象定义之后,因此this就不会在Coder定义过程中被绑定到global,而是绑定到最接近自己Coder对象。
第三种情况,this对象绑定的是Window对象,说明了function本身不能作为this对象,Coder.getNameFunc返回的是一个function,Coder.getNameFunc()返回的是执行这个function返回的function,而如果function本身如何可以作为对象的话,那么根据第二个例子this绑定的是最近的object,那么this应该绑定的是getNameFunc这个"function object",可惜javascript并不把function真正作为一个可以充当this的对象,既然如此this就被绑定到了执行这个Coder.getNameFunc()的[Window Object]上了。
// Another sample
// Array Object
var array = new Array();
array.name = ‘array‘;
array[0] = function() { return this.name; };
alert(array[0]()); // ‘array‘
聊以此文,怀念我那纠结但是执着的暑假,在我的人生道路上留下一个印记