Ruby 的 block 与 proc

2017-12-11 10:52:18来源:oschina作者:傅易人点击

分享

block 是形如下述的代码块


{ code }
{ |args| code }
do code; end
do |args| code end

block 的 { .. } 和 do .. end 语法是等价的。


可以看到,Ruby 的 block 是可以携带参数的。


只有在进行方法调用时,可以尾随参数传入一个 block。在方法中要使用 yield 来调用传入的 block。


def foo(a, b)
a + yield(b)
end
puts foo(1, 2) { |x| x - 1; } # 2

如果使用了 yield 但是没有传入代码块,会抛出异常


no block given (yield) (LocalJumpError)

可以使用 Kernel#block_given? 来判断是否传入了代码块


def foo(a, b)
if block_given?
a + yield(b)
else
a + b
end
end

Ruby 的 block 可携带参数,但不要求调用者遵守参数数量的约定。


p1 = proc { |x,y| x+y }
puts p1.call(1) # 1 + nil, TypeError
puts p1.call(1,2) # 3
puts p1.call(1,2,3) # 3

可以看到,缺少的补 nil,多余的忽略。


注意,Ruby 的 block 不开辟新的作用域。


def foo
p = proc { return 10 }
r = p.call()
return r * 2
end
puts foo() # 10

因此,使用 block 只能依赖于“最后一个表达式作为返回值”这一约定。


注意,一个 block 并不是一个对象,因此这么做是非法的:


{ puts "Hello" }.call() # syntax error

Ruby 提供了 Proc 类,作为 block 的对象“等价物”。


p1 = proc { |x| x+1 }
p1.class # Proc
puts p1.call(1) # 2
p2 = Proc.new { |x| x+1 }
p2.class # Proc

方法可以通过在末尾添加一个 &p 的参数声明,将传入的 block 转化为 Proc 对象,赋给变量 p。


进行方法调用时,可以通过在参数末尾添加一个 &p,将 Proc 对象 p 转化为 block 传入。


def foo(a, &p1)
bar(&p1)
puts a
return p1
end
def bar()
yield if block_given?
end
p1 = foo(1) { puts "Hello" }
p1.class # Proc
p2 = foo(1)
p2.class # NilClass

注意,&p 的声明只能出现在方法参数的末尾,而且只能有一个。如果没有传入 block 那么 p = nil。


如果 p 不是 Proc 对象,& 会先触发 p.to_proc() 方法,然后再转化为 block,因此以下代码可以生效:


["1", "2", "3"].map(&:to_i) # [1, 2, 3]

注意,block 不是对象,无法通过对象的规则使用它,因此 Ruby 提供了 yield, & 等关键字。


在实际编写方法时注意区分 Proc p 和 block &p。

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台