Callable objects
在 Ruby 当中一切都是对象,但是有一个例外,那就是 block。Block 和 Proc 类似,但是还是有稍有差别的,Block 更常用一些。最近在看《Metaprogramming Ruby》,在这节中有个例子是这样的。
require 'highline'
hl = HighLine.new
friends = hl.ask("Friends?" , lambda {|s| s.split(',' ) })
puts "You're friends with: #{friends.inspect}"
⇒
Friends?
Bill,Mirella,Luca
You're friends with: ["Bill", "Mirella", "Luca"]
这里看起来 hl.ask 把 Proc 当作参数来传递,而不是接受了一个 block,接受 Block 是另外一种使用模式:
require 'highline'
hl = HighLine.new
new_pass = hl.ask("password: ") { |prompt| prompt.echo = false }
在 highline 代码可以看到相应的处理方式,第一种方式 lambda 构造成的 Proc 其实传递给了 answer_type,而 yield 来处理 block。
def initialize( question, answer_type )
# initialize instance data
@question = question
@answer_type = answer_type
# allow block to override settings
yield self if block_given?
Proc, Lambda, Block
有三种方式转化 Block 为 Proc, Proc.new、Lambda、&Operator。但是在使用过程中 Block 还是比 Proc 要常见,在给一个函数传递这种 callable objcts 的时候,可以隐式或者显示传递,像这样:
def foo(*args)
yield(args.join(' '))
end
foo('Yukang', 'Chen'){|name| puts "Hello #{name}"} # => "Hello Yukang Chen"
def foo(*args, &blk)
blk.call(args.join(' '))
end
foo('Yukang', 'Chen'){|name| puts "Hello #{name}"} # => "Hello Yukang Chen"
隐式传递要比显式传递 performance 要好一些。这很早就有讨论,具体原因是根据 Ruby 的实现一个 Block 在 yield 的时候并没有转换为 Proc 或者其他对象,所以少了一些开销。Ruby 中的函数块是高阶函数的一种特殊形式的语法,Matz 在设计块的时候考虑到: (1) 在高阶函数中,这种只有一个函数参数非常常见,在实际使用中几乎没有必要在一个地方使用多个函数参数,(2) 外观和形式上更直观,Enumerable 利用块写的代码简洁易懂。