理解 Ruby 闭包

/

Ruby 有4种闭包类型,blocks、procs、lambdas 和 method objects。

Ruby 处理闭包有两种形态:一是 snippets 型,包括 blocks 和 procs,其中 blocks 只是书写简化但受到一些限制的 procs;二是 methods 型,包括 lambdas 和 method objects,前者匿名后者具名。

# 示例1
class Array
  def iterate!
    self.each_with_index do |n, i|
      self[i] = yield(n)
    end
  end
end

[1, 2, 3, 4].iterate! { |n| n ** 2 }
=> [1, 4, 9, 16]
# 示例2
class Array
  def iterate!(&code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

[1, 2, 3, 4].iterate! { |n| n ** 2 }
=> [1, 4, 9, 16]
# 示例3
class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

[1, 2, 3, 4].iterate!(Proc.new { |n| n ** 2 })
=> [1, 4, 9, 16]

[1, 2, 3, 4].iterate!(lambda { |n| n ** 2 })
=> [1, 4, 9, 16]
# 示例4
class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

def square(n)
  n ** 2
end

[1, 2, 3, 4].iterate!(method(:square))
=> [1, 4, 9, 16]

blocks 和 procs 的区别

  1. procs 是对象,blocks 不是。proc 是 Proc 类的一个实例对象。
  2. 只能向方法传递一个 block,但是可以传递多个 procs。

     def multiple_procs(proc1, proc2)
       proc1.call
       proc2.call
     end
    
     a = Proc.new { puts "First proc" }
     b = Proc.new { puts "Second proc" }
    
     multiple_procs(a,b)
    

procs 和 lambdas 的区别

procs 和 lambdas 都是 Proc 类的实例对象。

  1. lambdas 检查参数个数,procs 不检查。

     lam = lambda { |x| puts x }
     lam.call(2)     => 2
     lam.call        => ArgumentError: wrong number of arguments (given 0, expected 1)
     lam.call(1,2,3) => ArgumentError: wrong number of arguments (given 3, expected 1)
    
     proc = Proc.new { |x| puts x }
     proc.call(2)     => 2
     proc.call        => nil
     proc.call(1,2,3) => 1(忽略多余参数)
    
  2. procs 和 lambdas 处理 return 关键字的机制不同。procs 中的 return 作为调用 procs 的函数的返回值返回, lambdas 中的 return 以 lambdas 代码返回值返回,调用 lambdas 函数的后续代码继续执行。

     def proc_return
       Proc.new { return "Proc.new"}.call
       return "proc_return method finished"
     end
    
     def lambda_return
       lambda { return "lambda" }.call
       return "lambda_return method finished"
     end
    
     puts proc_return    => Proc.new
     puts lambda_return  => lambda_return method finished
    

procs 的行为像 code snippet,是所在方法的一部分,所以运行到 return 就直接跳出方法了;而 lambdas 的行为像方法,它所在的方法对它进行调用,调用完了还返回原方法里面。这也是第一点区别的根源所在,procs 只是 code snippet,不去管你传进来几个参数;而 lambdas 则是实实在在的方法,会严格检测有没有搞错参数的数量。跟普通方法的区别在于 lambdas 是匿名的。

lambdas 和 method objects 的区别

示例4中 method(:square).class 的值是 Method,不是 Proc。跟 lambdas 相比,method objects 是具名的,其他没有任何差别。

& 的作用

['1', '2', '3'].map(&:to_i),其效果和 ['1', '2', '3'].map { |i| i.to_i } 一样。& 会触发 :to_ito_proc 方法, to_proc 执行后会返回一个 proc 实例, 然后 & 会把这个 proc 实例转换成一个 block。&:to_i 是一个 block。

:to_i 是 Symbol 类的实例, Symbol 中的 to_proc 方法的实现类似于:

class Symbol
  def to_proc
    Proc.new { |obj| obj.send(self) }
  end
end

参考:

  1. Understanding Ruby Blocks, Procs and Lambdas
  2. What Is the Difference Between a Block, a Proc, and a Lambda in Ruby?

Comments