关于元编程
Wikipedia 上关于元编程的定义说元编程就是将程序作为数据进行处理。“用程序来处理程序”,这就是“元”的来源了,这本身是一个容易产生混淆的地方,就像“用语言来描述语言”一样,数学上的许多悖论就来自于此呢。幸好我们用的编程语言比自然语言要简单许多,并且都有严格的定义规范,有兴趣的人可以尝试在自己喜欢的编程语言里面构造一下 “This statement is false” 这个经典悖论。
回到元编程,程序处理程序可以分为“处理其他程序”和“处理自己”,对于前者,有我们熟悉的 lex 和 lacc 作为例子。而对于后者,如果再细分,可以分为“宏扩展”、“源代码生成”以及“运行时动态修改”等几种。
宏扩展从最简单的 C 语言的宏到复杂的 Lisp 的宏系统,甚至 C++ 的“模板元编程”也可以包含在这一类里面,我在这里对它们进行了一些介绍。
源代码生成则主要是利用编程语言的 eval
功能,对生成出来的源代码(除了在 Lisp 这样的语言里面以外,通常是以字符串的方式)进行求值。有一类有趣的程序 quine ,它们运行的结果就是把自己的源代码原封不动地打印出来,通常要证明你精通某一门语言,为它写一个 quine 是绝佳的选择了,这里有我搜集的一些相关资料。
最后是运行时修改,像 Lisp 、Python 以及 Ruby 之类的语言允许在运行时直接修改程序自身的结构,而不用再经过“生成源代码”这一层。当然对源代码进行 eval
的方法也许是最灵活的,但是它的效率却不怎么样,所以相来说并不是特别流行。这里主要介绍的是这种方式的元编程在 Ruby 里面的应用,如果对元编程的其他方面感兴趣,前面的几个链接应该都是很好的去处。
类与对象
Ruby 是一门完全面向对象的语言,在 Ruby 里面,所有的东西都是对象。对象是什么?对象是类的一个实例。那么类又是什么?按照前面的说法,一切都是对象,那么类也应该是一个对象喽!没错,类也是一个对象。按照前面的说法,对象应该是某一个类的一个实例,这也没错,一个特定的类就是“类(Class)”这个类的一个实例。已经开始拗口了吧?因为已经开始接触到“元”了,也就是所谓的用自己来描述自己,而描述一个类的类,通常叫做“元类(metaclass)”,不过在 Ruby 里面,元类和普通类其实是同一个东西。事实上, Ruby 还是一个单根的面向对象语言,所有的类都继承自 Object 类,包括 Class 类。运行一下下面的单元测试代码就可以验证这一点(完整的代码可以在末尾找到下载链接)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
和其他面向对象语言一样。Ruby 里面对象保存成员变量的值,但是并不保存成员函数(方法),方法是放在对象所属的类里面。事实上,由于对象里面不会保存函数,Ruby 的底层实现里面类这种对象和普通对象还是有一定的区别的:
321 322 323 324 325 326 327 328 329 330 331 | struct RObject { struct RBasic basic; struct st_table *iv_tbl; }; struct RClass { struct RBasic basic; struct st_table *iv_tbl; struct st_table *m_tbl; VALUE super; }; |
只是我们在 Ruby 里面看起来它们是一样的,或者说,类的类型派生自对象的类型,加了一个放函数的表和一个指向父类的域。
另外,还有一类方法叫做“类方法”,它不能引用到对象的成员变量,例如:
class Foo
def Foo.func1
end
def Foo.func2
end
end
而在类的环境里面 self
是指向类自己的引用,因此也可以用 self
代替代码中的 Foo
,像这样:
def self.func1
另外,Ruby 还允许另外一种写法:
class Foo
class << self
def func1
end
def func2
end
end
end
事实上,在类方法里面不能引用到对象的成员变量的原因很简单,因为类方法的 self 引用到特定的类,而成员方法引用到特定的对象。看下面的测试代码:
def test_class_method
# foo is an instance of class Foo
foo = Foo.new
# klass is the class of the object foo, i.e. Foo
klass = foo.class
assert_equal Foo, klass
# class method of object foo is just instance method
# of object klass(which is the class of foo)
klass.set(10)
assert_equal 10, klass.get
# because get is not instance method of object foo
assert_raises(NoMethodError) { foo.get }
end
但是在 Foo
这个对象的类里面并不能找到 get
这个方法,如果说 get
是 Foo
的成员方法的话,那么它应该包含在 Foo.class.instance_methods
里面才对。不过这里其实另有乾坤,关系到一个 singleton class
,我们将在后面再回到这个问题上来。
Ruby 里面的元编程
一个比较常见的例子当属 define_method
。Ruby 里面有 attr_accessor
方法,例如:
class Foo
attr_accessor :foo, :bar
end
则 Foo
这个类就有了 foo
、foo=
、bar
、bar=
这些方法,用于读取和设置 foo
和 bar
两个属性。使用起来很方便,其实一点也不神奇,用 define_method
我们立马就可以写一个,由于 Ruby 的类可以随时重新打开添加东西,我们直接修改 Class
类:
class Class
def prop_accessor(*props)
props.each do |prop|
define_method prop do
instance_variable_get "@#{prop}"
end
define_method "#{prop}=" do |val|
instance_variable_set "@#{prop}", val
end
end
end
end
在定义 Foo
的时候 self
是引用到 Foo
(这是 Class
这个类的一个实例),因此可以使用 Class
类的成员方法,所以可以像前面的例子里面使用 attr_accessor
那样来使用这里定义的 prop_accessor
,效果是一样的。
对象真的不能持有自己的方法吗?
是的,对象不能持有自己的方法,在前面看到 Ruby 的实现代码里面 RObject
这个结构里面根本没有用于存放方法的表。但是我真的想让对象有它自己的方法,要怎么办呢?只有 RClass
结构能保存方法列表,于是就让 RObject
保存一个自己的 RClass
类就好了。这个类通常叫做“singleton class”,甚至也有叫“metaclass”的,但是通常我们认为“metaclass”是用于定义类的类,容易造成混淆,Matz 正在考虑在以后的版本里面把这个类叫做“eigenclass”,那么我们这里就称它为“eigenclass”好了。一个对象(例如 foo
)的 eigenclass 与它所属的类(可以通过 foo.class
获得)有几点不同:
- eigenclass 是对象所独有的,属于同一个类的不同对象拥有不同的 eigenclass 。
- Ruby 目前没有提供像
foo.class
这样的方法来直接获得这个 eigenclass ,不过幸运的是我们可以很轻松地实现这个方法。 - Ruby 会优先从 eigenclass 里面查找方法,其次才是对象所属的类以及各个祖先类。
虽然没有内置的轻松获取 eigenclass 的方法,但是将方法存放到 eigenclass 却是非常简单的,事实上语法我们已经很熟悉了。
def test_singleton_method
foo = Foo.new
# now we put the singleton_func in the eigenclass of foo
def foo.singleton_func
self
end
# singleton_func exists in foo, and self refer to foo
assert_equal foo, foo.singleton_func
# singleton_func is not available in other instance of class Foo
assert_raise(NoMethodError) { Foo.new.singleton_func }
end
另外,还可以这样写
class << foo
def singleton_func
self
end
end
是不是觉得似曾相识?没错,就是前面定义类方法的时候用的语法。可是那里点好前面是一个类,这里是一个对象,不是吗?没错!可是别忘了在 Ruby 里面,类也是一个对象!好了,让我们用 eigenclass 和 singleton_method 来解释一下前面的类方法的代码。
def Foo.func1
这是在 Foo
这个对象的 eigenclass 里面放入了 func1
这个方法。这也是我们在 Foo.class
里面找不到 func1
的原因了,因为它根本不在那里。现在我们已经迫不及待地想要取出 eigenclass 来看看了,怎么能容忍它偷偷地躲在那里呢?下面我们来给 Ruby 里面所有类的元祖 Object
类开开刀:
class Object
def eigenclass
class << self
self
end
end
end
这么简单几行代码就 OK 了,在 eigenclass
方法内,打开 self
(这里引用到当前的对象) 的 eigenclass ,打开以后里面那个 self
就引用到我们要的那个 eigenclass 了,注意最后一行是作为返回值的,这样我们就成功取出了 eigenclass ,赶紧测试一下!
def test_eigenclass
foo = Foo.new
def foo.singleton_func
self
end
# eigenclass is also an instance of the class Class
assert_equal Class, foo.eigenclass.class
# singleton_func is put as an instance method in the eigenclass
assert foo.eigenclass.instance_methods.include?('singleton_func')
# so here is the magic of the so called class method
assert Foo.eigenclass.instance_methods.include?('get')
end
回到类方法的问题上。我们进行如下定义以方便描述
foo = Foo.new
klass = foo.class
metaclass = klass.class
eigenclass = klass.eigenclass
foo
的成员方法很简单,它是存放在klass
中的方法。也就是klass
作为一个类所拥有的方法,调用klass
的instance_methods
就可以得到这些方法。foo
的类方法,它是klass
的成员方法,但是并不是像普通的成员方法那样存放在metaclass
中,而是存放于klass
的 singleton classeigenclass
中。
define_singleton_method
singleton method 并不是 Ruby 独有的,在 CLOS、Dylan 等语言里面也有,而像 Self 和 NewtonScript 之类的基于原型的语言甚至只有 singleton method 。Ruby 里面采用这种方式漂亮地实现了“类方法”。遗憾的是,目前的 Ruby 里面并没有提供一个和 define_method
同样功能的用于定义类方法的方法,我们只好自己写一个了。
class Object
def define_singleton_method name, &body
eigenclass.send :define_method, name, &body
end
end
由于 define_method
是私有方法,因此我们需要使用 send
来调用。或者也可以使用 instance_eval
的方式来实现。另外,在类里面使用的时候,我们可以用一个更明确的名字:
class Class
alias define_class_method define_singleton_method
end
Dwemthy’s Array 是一个很有趣的 Ruby 元编程的例子。仅用了几十行代码就写好了一个 RPG 游戏。在这里我们来把它的 Creature
类的 traits
类方法简化一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
参考文献
- Seeing Metaclasses Clearly
- A Ruby Metaprogramming Introduction
- Ruby User’s Guide: Singleton methods
- Dwemthy’s Array
- Use Ruby’s Alias to Improve Your Code