代码块

一个块包含的代码块。你可以分配一个名称,一个块。 块中的代码总是被括在大括号里({})或是do...end里。


[1, 2, 3].each do |i|
  puts i
end

#=> 1
    2
    3

上面这个例子, each方法后面加一个do...end结构,那就是一个块。

Ruby中任何一个方法你都可以传递一个块。

   def test;end
   test{ puts i}

def test
    yield
  end
  test{puts "hello test!"}

  def test(x)
    yield(x)
  end
  test('world!'){|x| puts "hello #{x}"}

yield关键字不仅可以挂载块(block)代码,而且可以给块传递参数。


def test(&block)
    block.call("world")
  end

  test{|msg| puts "hello #{msg}"}

block到了方法内部,已经被&转化为了一个Proc对象。


 def test(&block)
    inner_test(&block)
  end

  def inner_test
    yield("haha!")
  end

  test{|msg| puts "hello #{msg}"}

test方法传进去的block被转化为了Proc对象,而其内部的inner_test又利用「&」把这个Proc对象转化为了块(block)

作用域

在Ruby中,关键字class、moduel、def都有自己的作用域范围。

class People
  father = 'God'

  def my_father
    puts father
  end

end


module Faith
  def my_father
    father = 'My God'
    puts father
  end
end

然后我们创建一个对象:

 person = People.new
 person.my_father
#=> NameError: undefined local variable or method `father' for #<People:0x00000003248990>

我们看到,报错了,因为作用域的问题,在my_father方法中,找不到这个father的变量。

 person.extend Faith
 person.my_father #=> "My God"

我们把Faith模块extend到person对象之后,就可以调用my_father方法,这是因为在模块Faith中定义了father变量。两个father变量明显不同。

穿透作用域的块

块(block)有个功能,就是可以穿透上面所说的作用域。


 class People
   father = 'The God'
   define_method :priest do
     puts "I can talk with #{father}"
   end
 end

 person = People.new
 person.priest #=> "I can talk with The God"

上面代码中, define_method是可以动态的定义一个方法,使用define_method方法的主要原因是想使用块,因为它后面可以加一个块, 也就是 do ... end中包括的内容。我们可以看到上面块中的代码, 直接使用了Class作用域的father变量,并且成功的输出了结果。

这就证明了block有穿透作用域的能力。

lambda 和 proc

我们在前面展示了一些block的例子。 我们说Ruby一切皆对象,但是这个block,确不是对象, 不过也不影响那句话,因为block是无法单独存在的,它必须要依靠于一个方法。如果你想让一个block单独被调用,那么就需要把块变成一个Proc对象。


lambda = ->(x, y){x * y}
#=> #<Proc:0x00000002e593c0@(pry):35 (lambda)>

lambda.call(2,3) #=> 6

#也可以省略call,但不可以省略点「.」
lambda.(2,3) #=> 6

lambda,是Proc对象的一种类型。它是一个可以被call的对象。


proc = proc{|x, y| x * y}
#=> #<Proc:0x00000002d1ee38@(pry):38>

proc.call(2, 3) #=> 6
proc.(2, 3) #=> 6

proc也是一个Proc对象, 注意看lambda和proc生成的Proc对象的差别。

具体的差别可以查看我的blog文章:大话Rubyblock: http://tao.logdown.com/posts/166766-vernacular-ruby-block

结语

在Chef中, block的应用是非常常见的, 比如我们随便写个cookbook,都必须得用到, 下面的例子来自于 chef-server的cookbook


# Ensure :file_cache_path exists
directory Chef::Config[:file_cache_path] do
  owner 'root'
  group 'root'
  recursive true
  action :create
end

现在你看懂这样的代码了吗?