一段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分拆出来,所以,更加容易理解。当然,严格的说,二者还是稍有差别,这种实现必须要有一个参数。但是,在大多数情况下,这种实现已经足够了。
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分拆出来,所以,更加容易理解。当然,严格的说,二者还是稍有差别,这种实现必须要有一个参数。但是,在大多数情况下,这种实现已经足够了。
相关文章推荐
- 帮我解释一段代码
- java 类锁和对象锁的一段代码,不解释
- 引用的作用(用一段简单的代码来解释)
- 关于一段地址对齐的位运算代码的解释
- 一段神奇的代码-关于PHP字符变量奇怪现象的解释
- 解释一段java关于同步锁synchronized代码的结果
- 一段神奇的代码-关于PHP字符变量奇怪现象的解释
- 意外获得一段“飘窗”的代码,配有很详细的图文解释,下次要用“飘窗”,记得来找我!
- main中执行shellcode的一段代码的解释
- 一段代码解释retain,strong和copy的区别
- 关于com解释的一段代码,你懂的
- 解释某宝的一段混淆视听的代码
- 与WSGI中的中间件相关的一段代码的解释
- <转>VC编译的除法的一段汇编代码解释
- VC编译的除法的一段汇编代码解释
- 一段多个access表汇总的简单样例 (备忘 根据情况修改相应代码可实现excel多表入access汇总)
- Matlab里怎么注释掉一段(多行)代码
- 一段有趣的代码,猜生日
- 写一段简单的PHP建立文件夹代码
- 一段jquery代码,保存