&操作符

块就像是方法的匿名参数,绝大多数情况下,在方法中可以通过 yield 语句直接运行一个块。但在下面两种情况中, yield 将力不从心。

  1. 想把这个块传递给另外一个方法。

  2. 想把这个块转换为一个 Proc。

下面是把一个块传给另一个方法的例子:

  def math(a, b)
    yield(a, b)
  end

  def teach_math(a, b, &operation)
    puts "Let's do the math:"
    puts math(a, b, &operation)
  end

  teach_math(2, 3) {|x, y| x * y}

  #=> Let's do the math:
  #=> 6

如果想把这个块转换为一个 Proc 呢?实际上,如果在上面的代码中引用了 operation,就已经拥有了一个 Proc 对象。& 操作符的真正含义:这是一个 Proc 对象,我想把它当成一个块来使用。简单地去掉 & 操作符,就能再次得到一个 Proc 对象:

  def my_method(&the_proc)
    the_proc
  end

  p = my_method {|name| "Hello, #{name}!"}
  puts p.class
  puts p.call("Bill")

  #=> Proc
  #=> Hello, Bill!

可以再次使用 & 操作符把 Proc 转换为块:

  def my_method(greeting)
    puts "#{greeting}, #{yield}!"
  end

  my_proc = proc {"Bill"}
  my_method("Hello", &my_proc)

  #=> Hello, Bill!

当调用 my_method() 方法时, & 操作符会把 my_proc 转换为块,再把这个块传给这个方法。

Procs vs. Lambdas

在 proc 和 lambda 之间有两个重要的区别。第一个区别与 return 关键字相关,另一个区别则与参数检验相关。

  • 在 lambda 中, return 仅仅表示 从这个 lambda 中返回
    def double(callable_object)
      callable_object.call * 2
    end
    
    l = lambda {return 10}
    double(l)                    #=> 20
    
  • 在 proc 中, return 的行为则有所不同,它不是从 proc 中返回,而是 从定义 proc 的作用域中返回
    def another_double
      p = Proc.new {return 10}
      result = p.call
      return result * 2         #=> unreachable code!
    end
    
    another_double              #=> 10
    

prco 和 lambda 第二个区别来自它们检查参数的方式。lambda 的适应能力比 proc 差,如果调用 lambda 时的参数数量不对,则它会失败,同时会抛出一个 ArgumentError 错误。而 proc 则会把传递进来的参数调整为自己期望的参数形式:如果参数比期望的要多,那么 proc 会忽略多余的参数,如果参数数量不足,那么对未指定的参数, proc 会赋予一个 nil 值。

  • 整体而言,lambda 更直观,因为它更像是一个方法。它不仅对参数数量要求严格,而且在调用 return 时只从代码中返回。

简洁 lambda

简洁 lambda 操作符:

  p = ->(x) {x + 1}

上面的代码与下面的代码作用相同:

  p = lambda {|x| x + 1}