动态语言的尴尬

August 6, 2007 – 10:28 pm

一提到动态语言,一般都会想到像 Python、Perl 以及 Ruby 之类的语言了,按照 Wikipedia 上的定义,动态语言是这样的:

Dynamic programming language is a term used broadly in computer science to describe a class of high level programming languages that execute at runtime many common behaviors that other languages might perform during compilation, if at all. These behaviors could include extension of the program, by adding new code, by extending objects and definitions, or by modifying the type system, all during program execution. These behaviors can be emulated in nearly any language of sufficient complexity, but dynamic languages provide direct tools to make use of them.

动态也不是说着玩的,一般 dynamic programming language 都是 dynamic typed ,换句话说:变量无类型,而值有类型。像以下的代码当然是基本要求了:

a = 12;
a = "foo";

当然还有其他动态的地方,比如,如果语言是面向对象的,那么对象的特征甚至类型通常都可以在运行时改变,甚至类也可以多次被“打开”进行修饰和修改,总之就是想怎么变就怎么变,让人写起程序来跟捏泥巴似的,哪儿不像了揉一下重新捏。再换个说法,动态语言之于静态语言,就好像它引入了时间,而不在是静止的了。静态语言编译好之后就是那个样子了,而动态语言得到的东西说不定过一会儿就和刚才不一样了(当然事情不是那么绝对,因为即使是静态语言也可以修改自己的二进制代码,关于这一点,甚至有关系到动态语言的定义的争论)。

然而动态语言这种特性却也有尴尬的一面,因为它让源代码变得“不可信”了。

在开源社区里面有一个普遍的观点:源代码是最好的注释。注释有可能(并且通常是)滞后于源代码,更关键的是,源代码没法骗人,代码怎么写,程序就必然是那个样子的了。而现在到了动态语言里面,这招不好用了,比如,在 Rails 里面,那些 model 通常是一些空类,但是你却看到在其他代码里面调用它们的许多乱七八糟的方法,而且这些方法在父类里面也没有定义:函数就这样凭空造出来了!真神奇!现在什么 grep ,TAGS 通通都不灵了!比如在 Pascal 里面,你可以搜索“function”就得到关于函数定义的语句。而在 Ruby 里面呢?比如,要找函数 foo ,可能可以搜索 def *(self.)?foo ,这是比较常用的定义函数的方法了,用简单的文本搜索工具就可以搜索到。但是定义函数的方法远远不止这一种啊。

在 Ruby 官方文档的 define_method 的文档里面有这样一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
  def fred
    puts "In Fred"
  end
  def create_method(name, &block)
    self.class.send(:define_method, name, &block)
  end
  define_method(:wilma) { puts "Charge it!" }
end
class B < A
  define_method(:barney, instance_method(:fred))
end
a = B.new
a.barney
a.wilma
a.create_method(:betty) { p self }
a.betty

可以看到这里定义的四个方法分别是用不同的方式来定义的

  • fred 采用传统的 def 进行定义。
  • barney 使用 define_method 以另外一个方法为蓝本进行定义。
  • wilma 使用 define_method 和 block 的方式进行定义。
  • betty 使用自己定义的一个 create_method 方法进行定义。

到这一步,简单的文本搜索已经不管用了,必须要对代码进行语意分析才行了。再过分一点,把上面的 symbol 换成使用字符串的方式,字符串再动态拼接一下,完了,语意分析也不行了,非得执行一遍才行。再过分一点,字符串需要用户交互产生,完了,无法在后台执行,或者,字符串从数据库里面来,或者其他有可能随时间变化的源头产生,甚至是与时间本身有关,就彻底完蛋了,除非在真正用户要执行代码的那一刻,否则分析出来的结果都是假的了。

类似的,像在 Lisp 这样的语言里,函数其实就是一个对象(lambda),与它的“名字”其实没有什么必然关系:

(define (foo a)
  (display a)
  (newline))

其实和

(define foo
  (lambda (a)
    (display a)
    (newline)))

是等价的。就是说,拿着一个“函数”(lambda),我可以随时把它“赋值”(bind)给某个“变量”(symbol)或者其他的变量。再让 Lisp 里面引以为傲的 macro 来凑一下热闹,函数的“根源”就更加无处可循了。

事实上 Lisp 采用一种增量式开发的方式:在一个交互环境定义一些函数变量一类的,并随时进行更改,因为一切都在运行时是可更改的,而这个交互环境就是正在运行的程序,因此一切都是“真实”的。结束以后将当前的整个环境 dump 成一个新的 core ,下次可以直接加载那个 core ,再进行相应的添加和修改,可以说是从最初一个基本的 Lisp 解释器增量式地进化为自己专有的程序。事实上动态语言大多都有非常强的“反射”功能,并且将程序注释作为程序的一部分,比如可以查看到某个函数说附带的注释(或者说,文档、说明),这在一定程度上算是替补了源代码的地位吧(其实如果能动态地根据函数查到它的源代码的话,似乎更舒服,当然,在 Lisp 这种“代码和数据是同样的东西”的语言里面,这几乎就是 trivial 的啊)。

其实我把这看作是动态语言尴尬的一面吧,起因是我在一个有点大的源码树里面找一个函数的定义的悲惨经历。我不知道有没有更好的解决办法了,要不然真的很尴尬。

其实我比较奇怪,Refactor 发源于 Smalltalk ,而 Smalltalk 也被 wikipedia 归到动态语言一类里面,而 Refactor 则是对源代码进行更改,并且据说 Smalltalk 的 IDE 在 Refactor 方面表现得非常智能。我就想着,如果光调用一个函数的方法就有千万种( 什么 call、send、apply、eval 一类的 ),那我要做一个“给函数改名”的重构,不知道这个 IDE 究竟要如何来做了?可惜我不会 Smalltalk ,有机会一定要去见识见识了。 :)

  1. One Response to “动态语言的尴尬”

  2. 动态语言这种特性却也有尴尬的一面,因为它让源代码变得“不可信”了
    严重同意!
    所以要研究ruby的代码,必须要有个Debug的IDE了。

    By pig345 on Dec 19, 2007

Post a Comment