深入理解Blocks,Procs和lambdas

本文转载自:http://blackanger.blog.51cto.com/140924/123034  我哥们儿的一篇博文,觉得收益匪浅,转载过来供大家一起学习,以下是正文:

Blocks, Procs和lambdas是Ruby里最重要的方面,同时也是难点之一。这是因为Ruby处理闭包(closures)的方式比较特别。更复杂的是,Ruby有4种使用闭包的方式,它们之间还稍有不同。

First Up, Blocks

大多数情况下,使用闭包最Ruby的方式就是用Block了。

array = [1,2,3,4]
#[1, 2, 3, 4]
array.collect! do |n|
  n ** 2 (求平方)
end
#[1, 4, 9, 16]
puts array.inspect
# [1, 4, 9, 16]

so ,what’s going on here?

我们发送了‘collect!’方法给一个block代码的数组

在collect!方法内部给每个变量作平方(此处为n)

我们现在来实现自己的collect!方法,我们先建立一个iterate!方法:

class Array
  def iterate!
    self.each_with_index do |n,i|
      self[i] = yield(n)  #取得当前的n,并将这个n作为参数传递给待调用的block
    end
  end
end
array = [1,2,3,4]
#[1, 2, 3, 4]
array.iterate! do |n|
n ** 2
end
#[1, 4, 9, 16]

和属性不一样,你并不需要在你的方法内部指定block名字,你只需要使用yield关键字就ok了。请注意上例是如何给yield传参数n的。整个过程可以解释为:

给Array对象发送iterate!方法

当yield被调用的时候,把n(第一次1,第二次2,以此类推)传给被给的block里

block里有了可用的值,也就是n,那么就平方它。

yield输出block里返回的值,并且重写了array里面的值。

使用yield只是使用block代码的一个方法而已, 这里还有另外一个,那就是作为一个Proc来调用它。看看:

class Array
  def iterate!(&code)
    self.each_with_index do |n,i|
      self[i] = code.call(n)
    end
  end
end
arr = [1,2,3,4]
arr.iterate! do |n|
  n ** 2
end

#[1, 4, 9, 16]

和上一个例子非常相似。然而有两点不同。第一个就是iterate!的参数形式。第二点就是call方法。 输出结果是相似的,但是为什么语法不同呢?

def what_am_i(&block)
  block.class
end
puts what_am_i{}
#Proc
这是一个Proc对象。block是一个Proc。
<pre lang="RUBY">def what_am_i
  yield.class
end
puts what_am_i{ puts "x"}
=&gt; x
NilClass
puts what_am_i{ "x"}
# String

这说明,使用yield直接返回的是block表达式里的值。

那么什么是Proc了?

Procedures, AKA, Procs

我们经常需要使用一个块在多个地方,这样我们就需要不断的重复我们的block,那么为了解决这个问题,做到代码复用,我们就可以使用Proc, 其实block和Proc的唯一区别就是, block是个一次性的Proc。

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end
arr1 = [1,2,3,4]
arr2 = [2,3,4,5]
square = Proc.new do |n|
  n ** 2
end

arr1.iterate!(square)
#[1, 4, 9, 16]
arr2.iterate!(square)
#[4, 9, 16, 25]

你可能会说,用block不就完了吗?整个Proc不是多此一举。 那么请问,如果我们想给一个方法传多个closures怎么办?使用block会很不灵活。 但是用Proc就好多了:

def callbacks(procs)
  procs[:starting].call
  puts "Still going"
  procs[:finishing].call
end
callbacks(:starting => Proc.new{puts "Starting"},:finishing  => Proc.new{puts "Finishing"})

#
Starting
Still going
Finishing

那么什么时候用block,什么时候用Proc呢?
Block: 当你的方法是被分成多个小片段,而你想让你的用户与这些片段做一些交互。
Block: 当你需要运行多个原子性表达式的时候,比如数据库迁移(migration)
Proc : 当你需要重用一个block的时候。
Proc : 当你的方法有一个或多个callbacks的时候。
Lambdas
匿名函数,在其他语言里也有。Ruby里也可用:

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

咋看上去,lambda和Proc执行的好像是一样。但是这里有两个细微的不同点。第一个不同就是lambda检查参数的个数。

def arguments(code)
  one, two = 1, 2
  code.call(one,two)
end
arguments(Proc.new { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })
#Give me a 1 and a 2 and a NilClass
arguments(lambda { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })
#ArgumentError: wrong number of arguments (2 for 3)

第二个不同就是,lambda有一个‘微小’的返回,什么意思呢?Proc返回会阻止一个方法的执行,并返回这个值。lambda返回它们的值,但是方法还会继续执行。看看例子:

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

为什么会不同?答案就是procedures(过程)和methods(方法)的概念不同。Procs在Ruby里是代码片段,不是方法。因为这个,Proc return的就是proc_return这个方法的return,因为Proc那段就是那个方法里的代码片段。而lambdas的行为像一个方法,它检查参数的个数,而且不会覆盖调用方法的return。由于这个原因,你最好把lambdas理解为写一个方法的另类方式,只不过是匿名的而已。那么什么时候该用lamda代替Proc呢?看例子:

def generic_return(code)
  code.call
  return "generic_return method finished"
end
puts generic_return(Proc.new{return "Proc.new"})
#LocalJumpError: unexpected return
puts generic_return(lambda{return "lambda"})
# =&gt; generic_return method finished

Ruby语法里,参数部分不能带return,然而lambda行为像方法,所以它可以有个内部return。

def generic_return(code)
  one, two    = 1, 2
  three, four = code.call(one, two)
end
puts generic_return(lambda { |x, y| return x + 2, y + 2 })
puts generic_return(Proc.new { |x, y| return x + 2, y + 2 })
puts generic_return(Proc.new { |x, y| x + 2; y + 2 })
puts generic_return(Proc.new { |x, y| [x + 2, y + 2] })
# Give me a 3 and a 4
#*.rb:11: unexpected return (LocalJumpError)
#Give me a 4 and a
#  Give me a 3 and a 4

这个generic_return方法返回的是两个值,使用lambda,一切都很easy,而使用Proc,我们还不得不用数组指定x,y。

a,b =  generic_return(lambda { |x, y| return x + 2, y + 2 })
#[3, 4]
a
#3
b
#4

c,d = generic_return(Proc.new { |x, y| x + 2; y + 2 })
#[4]
c
#4
d
#nil
e,f = generic_return(Proc.new { |x, y| [x + 2, y + 2] })
#[3, 4]
e
#3
f
#4

Method Objects
如果你有一个工作完好的方法,你想把它传到另一个方法里,作为闭包,也为了使你的代码DRY,那么你可以使用这个method方法:

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
arr = [1,2,3,4]
arr.iterate!(method(:square))
#[1, 4, 9, 16]
puts method(:square).class
#  Method

这个方法对象的行为像是lambda,那么到底。。。:

puts lambda {}.class
#  Proc

呵呵,它是个Proc。
现在明白Ruby 的这四种闭包方式了吧。
block,Proc的行为更像一个代码片段,lambdas和method方法的行为更像方法。
block,Proc, lambdas都是Proc对象,而method,是Method对象。

你可能感兴趣的:(lambda)