深入理解Blocks,Procs和lambdas

http://blackanger.blog.51cto.com/140924/123034

首发在:[url]http://blog.enjoyrails.com/?p=125[/url]

原文:[url]http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/[/url]

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)
    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,第二次4,以此类推)传给被给的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对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值