代码块
一个块包含的代码块。你可以分配一个名称,一个块。 块中的代码总是被括在大括号里({})或是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
现在你看懂这样的代码了吗?