您的位置:首页 > 编程语言 > Ruby

一段Ruby代码的解释

2011-08-22 00:59 405 查看
阅读Rails源码的时候,会发现代码中遍布着一些看上去比较奇怪的代码,大概会是这个样子:

people.collect(&:name)

这段代码实际上等价于

people.collect { |p| p.name }

但是,从Ruby的语法上来看,这行代码看上去是很难理解的,主要就是&:name。把这行代码当作Ruby脚本直接运行,你会发现,Ruby会向你报错,错误是:

wrong argument type Symbol (expected Proc) (TypeError)

那么为什么这样的代码遍布于Rails,却能正常运行呢?

最初困扰人的可能主要是&:name的写法,这看上去根本不像正常的Ruby语法,但只要将它进行适当的分解,我们就豁然开朗了。在Ruby中,“&”通常用来表示后面跟的是Proc,而:name在Ruby中表示一个Symbol。把二者结合在一起,矛盾变产生了,”&”要的是一个Proc,而我们给的一个Symbol,这就是前面错误的来源。

知道了错误的来源,接下来的问题是,Rails里究竟变了怎样的魔术,让这段代码通过呢?

其实,“&”后面如果跟的不是一个Proc,那么它会试图找寻一个Proc,在Ruby中,这意味着它会调用to_proc方法。这是Ruby中的一种标准协议,关于这种转换协议,可以参考《Programming Ruby》中《Duck Typing》一章相关的介绍。

由于后面的是一个Symbol,结合前面的说法,只要为Symbol类提供一个to_proc方法,至少在语言层面上,就会变得正确起来。事实上,Rails正是这样做的。

class Symbol

def to_proc

Proc.new { |*args| args.shift.__send__(self, *args) }

end

end

(activesupport/lib/active_support/core_ext/symbol.rb)

如果说to_proc仅仅是让这个魔术在语言层面上通过,那么上面这段代码也解开了其余部分的神秘面纱。不妨再进一步,看看它究竟是如何做到的。

通过开始的代码等价对比,我们已经知道了这行的代码意义,主要也就是调用了一个对象的方法。将它对应到Proc.new所附带的block上,我们便不难看出这段代码的意义所在了。

这里的*args是block的参数,在前面的例子中,p就是这个参数,我们要调用p的name方法,所以,p是作为receiver的,而args.shift正是将p提取了出来。通过__send__,我们就可以调用receiver的方法,而__send__需要一个Symbol指定要调用的方法,别忘了,我们正是在一个Symbol类中定义方法,于是self成为了一个自然的选择。至于剩余的参数(对*args调用shift之后),就作为参数传给方法了。实际上,大多数用法中,只有一个参数,所以,剩余的部分会是一个空数组。

通过这种变换,p.name就等价于args.shift.__send__(self, *args)了。

关于这段代码的实现,也许Ruby Extensions的实现更加清楚一些。

def to_proc

proc {|obj, *args| obj.send(self, *args) }

end

这段代码将receiver分拆出来,所以,更加容易理解。当然,严格的说,二者还是稍有差别,这种实现必须要有一个参数。但是,在大多数情况下,这种实现已经足够了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: