『阿男的编程本质论』*08 Eval,Macro,Preprocessor,Homoiconicity(一)*

2017-01-12 09:51:05来源:oschina作者:阿男weli人点击

第七城市


『阿男的编程本质论』*08 Eval,Macro,Preprocessor,Homoiconicity(一)*
阿男这次想跟大家聊聊语言设计里面的几个概念,分别是Eval,Macro,Preprocessor和Homoiconicity。
首先是`Eval`,我们在很多语言里都可以看到它的影子,比如`Python`,`Perl`,`Ruby`还有`Javascript`,等等。以Ruby为例,下面是在Ruby里面使用`eval`的例子:
```ruby
irb(main):001:0> eval "1+1"
=> 2
```
从上面的例子我们可以看到,`eval`的用处就是接受一段字符串作为代码,并且执行这个代码。就像上面这样,我们的字符串是`1+1`,使用`eval`作用在这个字符串上面,它就作为了Ruby里面的代码来执行了,然后给出结果为`2`。
Perl和Python还有Javascript都是差不多的使用方法。我们可以想一想,似乎Java语言,C语言里面并没有`eval`这种功能,为什么?
我们得想一想`eval`是怎么实现的,把字符串作为代码,等于就是在程序Runtime的时候,这个字符串可以「编译」成代码。因此,也就是说这门语言需要具备Compile-at-runtime的能力。
C语言或者Java语言这种,是明确区分Compile Phase和Runtime Phase的。我们的C语言代码,需要被编译器先编译成汇编代码,变成一个可执行文件的。而我们使用的,是编译后的可执行文件,因此我们不可能让编译后的代码,去解析一个字符串,再把它变成汇编代码。
Java的编译过程也是和运行时互相独立的,需要把Java文件编译成class文件,class文件里面有bytecode,在JVM上面执行。
当然,Java发展到现在,也支持Compile-at-runtime,这种技术有个名字,叫做`JIT`,也就是`Just-In-Time` Compiler。有了这个能力,就允许代码在运行时被实时地编译,然后虚拟机里面的class文件在运行时被实时地加载。
但是光有Compile-at-runtime还不够,我们还得看这门语言自身的语言特性是否支持runtime时代码自身的改变。Java在设计的时候,对class之间的依赖关系,package之间的关系等等,都有很多约定,可以说Java是一门设计的比较
"严格"的语言,所以实现`eval`是有难度的。
因此,现在有很多语言,是架构在JVM平台上,最终也是compile成bytecode,但是设计的比Java语言更灵活一些,因此就可以实现`eval`的功能,比如Groovy,还有后续要讲到的Clojure。我们可以看看Groovy的eval的代码例子:
```groovy
$ groovysh
Groovy Shell (2.4.7, JVM: 1.8.0_112)
Type ':help' or ':h' for help.
-------------------------------------------------------------------------------
groovy:000> Eval.me('2 * 4 + 2')
===> 10
```
从上面的例子,我们可以看到Groovy的`Eval`的使用方法。接下来我们看Ruby的`eval`:
```ruby
$ irb
irb(main):001:0> eval "1+1"
=> 2
```
Ruby也有自己的虚拟机和virtual machine code叫做YARV,阿男之前给大家讲过了。因为Ruby也支持Compile-at-runtime,所以实现`eval`是没问题的。
Python,Perl和Javascript也是一样,所以我们要明白的是,如果一门语言在语言设计和虚拟机设计上支持Compile-at-runtime,那么这门语言就可以在运行时,把一串字符编译成代码并且加载执行,也就是实现了`eval`的功能。
这种功能固然很方便很强大,但是它有什么问题没有?
其实语言设计里面,并不存在绝对的优势,有的功能,它的优点同时也就是它的缺点,比如`eval`这种功能就是一个典型的例子。因为我们可以把代码封装在字符串里,然后在运行时编译并执行,那么如果这个字符串里面的代码有问题,这个问题会在运行时才发现。
因此Compile-at-runtime虽然很灵活,但是compile和runtime混在一起的话,也就意味着很多代码的问题会在运行时才能发现。而C语言或者Java,很多问题会在明确的compile这一步被发现,而不是runtime时才被发现。
最后我们要说一下,以上的讨论都是基于这一门语言有compile这一步骤而讨论的,其实还有更简单的实现,就是Interpreter。所谓Interpreter,就是指直接解析文本,然后根据文本直接执行命令,并没有把源代码转化成目标代码的过程,也没有用来运行目标代码的虚拟机。比如早期的Ruby就是使用的这种形式,提供一堆用C语言编写的API接口,然后Ruby代码就直接解析,然后调用这些接口来执行代码。
Interpreter的执行效率和功能上面都有局限,所以Ruby从1.9开始就转为使用虚拟机加自己的虚拟机YARV code的形式来执行代码,这一点和JVM平台越来越像。可以说,VM加VM code是主流趋势。
第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台