CatCoding

Ruby's Block and Proc

2012-11-14

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 利用块写的代码简洁易懂。

公号同步更新,欢迎关注👻