临时小驻

求仁得仁,复无怨怼。

Ruby 的 block 与 proc

2017-12-11 12:52:00 +0800

在一个作用域里,变量名与一组变量值相绑定,改变了作用域,就可以与另一组变量值相绑定,且不影响原作用域的值。使用 Kernel#local_variables 查看当前作用域下的变量。

开启作用域的地方称为作用域门(scope gate),ruby 里只有三个

  1. 类定义:class ... end
  2. 模块定义:module ... end
  3. 方法定义:def ... end

proc 不开辟新的作用域,当你需要新的作用域怎么办?

使用 lambda 创建“提供了一层新作用域”的 Proc 对象。

p1 = lambda { |x| x+1 }
p1.class # Proc

p2 = -> { }
p2.class # Proc

虽然 lambda 的结果仍然是 Proc 对象,但与通过 block 产生的 Proc 对象不同。

Ruby 1.9 开始,为了强调两者的不同,在 puts 时你可以看到这样的区别:

irb(main):001:0> a = lambda {}
=> #<Proc:0x00007fa35185dd00@(irb):1 (lambda)>
irb(main):002:0> b = proc {}
=> #<Proc:0x00007fa35184ef80@(irb):2>

值得注意的是,Ruby 没有 Block 类,没有 Lambda 类,只有 Proc 类,用以代表各种形式的代码块。

可以认为,lambda 产生的是“匿名方法”,而 proc 产生的是纯粹的“匿名代码块”。这种认知能够解释种种 proc 与 lambda 的不同行为。


这一层新的作用域带来了什么样的变化?想想 def 定义方法的时候。

def test_proc
    p = proc { return 10 }
    return p.call() * 2
end

puts test_proc() # 10

def test_lambda
    p = lambda { return 10 }
    return p.call() * 2
end

puts test_lambda() # 20

相比于 block 只能由最后一个表达式作为返回值,lambda 可以使用 return 返回结果。而 block 使用 return 相当于在 def 定义的方法的作用域中返回了。

同理,block 中的 break/next 是在 block 外部的作用域中执行,而 lambda 中则是在其内的新作用域中执行。


lambda 产生的匿名方法,与 def 定义的方法一样会严格检查传参,而不似 proc 般宽容。

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

p2 = lambda { |x,y| x+y }
puts p2.call(1)     # given 1, expected 2, ArgumentError
puts p2.call(1,2)   # 3
puts p2.call(1,2,3) # given 3, expected 2, ArgumentError

从参数检查、返回行为、上下文的绑定三个点出发就能掌握 block 和 lambda 的使用。

正如 聊聊 Ruby 中的 block, proc 和 lambda 中所说:

用 block,用 lambda,慎用 proc,让 proc 做好自己的幕后工作。

我觉得,大致可以用这么几句话总结:

  • block 是 Ruby 的一种字面量(Literal)。
  • block 以及 Proc 化的 block,它的行为就像宏展开。
  • lambda 是真正的匿名方法。
  • Proc 对象是 block 和 lambda 的容器。

原文链接 https://blog.xupu.name//p/2017-12-block-and-proc-in-ruby/

如无特别指明,本站原创文章均采用 CC BY-NC-ND 4.0 许可,转载或引用请注明出处,更多许可信息请查阅这里