通过对.NET中LINQ的介绍和对LISP的重拾兴趣,一类元编程被重新关注起来。在LINQ中,可以使用表达式树,例如一棵由一段代码表示的树。
在LISP(或者类似语言)中,这种方法被称为宏或者宏展开。宏看上去很像函数调用,但不同的是函数调用在编译期就被估值,例如当代码载入的时候。宏可以得到宏调用的抽象语法树(AST),但是在AST宏返回时宏调用会被替换掉。这也就是说,宏调用并不是一段被实际执行的代码,而是返回的AST宏被执行了,例如宏调用展开的实际的代码。
Ruby并不在语言级别支持AST,于是有一些库做到了。最流行的一个要数ParseTree了,可以将s表达式作为AST返回。例如,符号紧凑列表和字符常量。有很多有用的工具就是基于ParseTree而构建的,例如:
现在,一些新库采用了LINQ的方式来使用ParseTree。Ambition允许用户使用Ruby语法来编写查询,例如:
LDAP::User.select { |m| m.name == 'jon' && m.age == 21 }
或者
SQL::User.select { |m| m.name == 'jon' && m.age == 21 }
在块中的代码实际上永远都不会运行,相反会被ParseTree用来得到AST。它会被分析并转换成目标查询语言的查询语句。Ambition提供可扩展的适配器,可以允许用户为Ruby的AST到查询语言的转化来编写新的转换器。
另外一个采用了此类型查询的库是Sequel。作为一个ORM,Sequel同样允许用户使用Ruby来编写查询:
old_nonruby_posts = posts.filter {:stamp > 1.month.ago && :category != 'ruby'}
需要重点注意的是,和Ambition不同,这仅仅是Sequel编写查询的方法之一,它同样支持通过字符串常量来编写查询。
一种很不同的Ruby代码AST使用方法可以在Merb中找到。它被用在参数化Action中:
参数化Action:
如果你在你的action方法中指定了参数,收到的查询参数将会自动被正确的指定。示例如下:
class Foos < Merb::Controller
def index(id, search_string = "%")
@foo = Foo.find_with_search(id, search_string)
end
end
访问/foos/index/12将会调用index方法并传入参数“12”和“%”(默认值)。访问/foos/index将 会抛出一个BadBehavior错误(状态码400),因为id是一个必须的参数,但是却没有被传入。访问/foos/index/5? search_string=hello将会调用index方法并传入参数“5”和“hello”。最后的示例说明你可以像是用一个真正的方法一般使用 action。这个特性是通过查看处理action方法的AST并抽取默认参数来实现的。通过这种方法,可以实现一种通常并不可用的、类似于内视/反射的特性。
require 'ripper'可以这样来使用:
class MyRipper < Ripper
def on_gvar(node)
puts node
end
def on_int(node)
puts node
end
# etc.
# Handle each element of the AST with an on_* method
end
f = MyRipper.new("$foo = 1")除了Ruby 1.9以外,ParseTree还提供了对其他可选Ruby实现的支持。Rubinius大量的使用了ParseTree的AST表示。JRuby几乎完全移植了ParseTree,但是基于.NET的Ruby实现似乎目前还不支持。
f.parse