元编程程序员Bob的故事
The Story of Bob, Metaprogrammer
Bob是一个Java程序员,他刚刚开始学习Ruby,他有一个宏伟的计划:他要为电影迷开发一个世界上最大的因特网社交网络系统。要实现这个计划,他需要一个包含电影和影评的数据库。Bob希望用它来练习编写可重用的代码,因此他决定创建一个简单的库来把对象持久化到数据库中。
Bob的第一次尝试
Bob的库把数据库中的每个表映射到一个类中,而每条记录则映射到一个对象中。当Bob创建一个对象或访问它的属性时,这个对象会产生一条SQL语句并发送给数据库。所有这些都包装在一个基类中,代码如下:
introduction/orm.rb
class Entity
attr_reader :table, :ident
def initialize(table, ident)
@table = table
@ident = ident
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@ident})"
end
def set(col, val)
Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@ident}"
end
def get(col)
Database.sql("SELECT #{col} FROM #{@table} WHERE id=#{@ident}")[0][0]
end
end
在Bob的数据库里,每个表都有一个id字段。每个Entity会保存这个字段的内容及它引用的表名。当Bob创建一个Entity对象时,这个对象会把它自身保存在数据库中。Entity#set()方法会创建SQL语句来更新一个字段的值,而Entity#get()方法会创建SQL语句读取一个字段的值。(万一你感兴趣,Bob的Database类用数组的数组作为返回的数据集。)
Bob可以继承Entity类来映射一个特定的类。例如,Movie类可以用来映射一个名为movies的表:
class Movie "Doctor Strangelove"
上面的代码首先创建了一个Movie对象,并包装了movies表中的一条记录。可通过Movie#title()和Movie#title=()方法访问title字段,但是这些方法在源代码中无迹可查。如果它们根本没有定义过,title()和title=()怎么能存在呢?可以从ActiveRecord的工作方式中找到答案。
表名部分很直接:ActiveRecord通过内省机制查看类的名字,然后根据某
种简单的习惯即可得到表名。因为类名是Movie,所以ActiveRecord会把它映射到名为movies的表中。(这个库知道怎么处理英语单词的复数。)
那些像title()和title=()这样访问属性的方法(简称为访问器)是怎样处理的呢?这就是元编程发挥作用的地方:Bob无须编写那些方法。从表的模式中得到字段名后,ActiveRecord会自动定义这些方法。ActiveRecord::Base会在运行时读取数据库模式,如果发现movies表有两个名为title和director的字段,就通过定义访问器创建两个同名属性。这意味着ActiveRecord在程序运行时无中生有地创建了诸如Movie#title()和Movie#director=()这样的方法!
相对于内省的“阴”,这就是它的“阳”:不仅可以读出语言构件,还可以写入它们。如果认为这个特性非常有用,那么你是对的。
再谈“元”
现在,你可以得到一个更加正式的元编程定义:
元编程是编写在运行时操纵语言构件的代码。
ActiveRecord的作者是怎样应用这个概念的?他不是为每个类的属性编写访问器方法,而是编写代码为每个继承自ActiveRecord::Base的类在运行时定义方法。这就是“编写代码的代码”时所指的东西。
你也许会认为这只是一个孤例,但是,如果看看Ruby(正如马上会做的),你会发现它们无处不在。
元编程和Ruby
Metaprogamming and Ruby
还记得早先讨论的鬼城和自由市场么?如果你希望“操纵语言构件”,那么那些构件必须在运行时存在。下面快速看看几种语言在运行时能给你多少控制权。
一个用C语言写的程序会跨越两个不同的世界:编译时和运行时。在编译时,可以拥有像变量和函数这样的语言构件;而在运行时,只有一大堆机器码。由于绝大多数编译时的信息在运行时都丢失了,所以C语言不支持元编程或内省。在C++语言中,一些语言构件可以在编译后生存下来,这也是为什么你可以向C++对象询问它的类的原因。在Java语言中,编译时和运行时的界线甚至更加模糊,你有足够的内省能力来列出一个类的方法,或者一直向上查询其超类链。
Ruby无疑是现今流行语言中对元编程最友好的一种语言。没有编译时,Ruby程序中所有的语言构件在运行时可用。在运行一个程序时,无须翻过一道横亘在所写程序与所运行程序中间的墙。这里只有一个世界。
在这个世界中,元编程无处不在。事实上,元编程如此深入Ruby语言,甚至无法和“普通”编程明确区分开来。你无法看着一段Ruby代码说,“这部分是元编程,而其他部分不是。”从某种程度上说,元编程不过是每个Ruby程序员的例行工作。
为明确起见,元编程并不是Ruby高手的抽象艺术,它也不只是创建像ActiveRecord这种复杂东西的利器。如果你要通向Ruby的高级编程之路,就会在每一步上看到元编程的身影。即使满足于目前你所学的Ruby知识,也很可能会在编程之旅中被元编程绊倒:可能是在某个流行框架的源代码中,也可能是在某个喜欢的类库中,甚至是从某个博客上看到的小例子。除非掌握了元编程,否则你不会拥有Ruby语言全部的战斗力。
学习元编程,还有一种也许不那么明显的原因。尽管Ruby第一眼看上去很简单,但很快会被它的精妙细微所打击。迟早你会问自己一些问题,比如“一个对象可以调用同属一个类的其他对象的私有方法么?”或者“可以通过导入一个模块来创建类方法么?”最终,这些看起来复杂的行为实际上都是基
于非常简单的原则。通过元编程,你会对这门语言更熟悉;通过学习这些规则,你会找到这些问题的答案。
如果你已经知道元编程是干什么的了,就表明已经做好阅读本书正文的准备了
……
展开
——松本行弘 Ruby之父