Ruby中Block, Proc, 和Lambda

Block

Blocks就是存放一些可以被执行的代码的块,通常用do...end 或者 {}表示

例如:

[1, 2, 3].each do |num|
  puts num
end

[1, 2, 3].each { |num| puts num }

Blocks可以和.each、.times等联合使用,用来对每一个元素执行一段指令。

还有很多非常有用的方法需要用到Blocks,例如 collect

collect方法可把数组中的每个元素都传给Blocks, 在Blocks中可对数组元素进行操作

例如:

my_nums = [1, 2, 3]
my_nums.collect! { |num| num ** 2 }

下面我们来想一下,为什么有些方法允许使用Blocks而有些不是

因为允许使用Blocks的methods有办法从调用方法把控制权转移到Blocks然后再转移回来到调用方法处。我们可以通过yield关键字来实现。

def block_test
  puts "We're in the method!"
  puts "Yielding to the block..."
  yield
  puts "We're back in the method!"
end

block_test { puts ">>> We're in the block!" }
--------------------------------------------------------------------
输出运行结果:
We're in the method!
Yielding to the block...
>>> We're in the block!
We're back in the method!
nil

从上段程序可以看出, 当我们调用block_test时, 先进入block_test程序,然后运行, 当程序走到了yield处, 程序的控制权同过yield转移到了block中(即块

{puts ">>> We're in the block!"}), block执行完毕后, 再把程序控制权还给block_test方法, 程序从yield关键字后继续运行。

我们还可以给yield传参数

 1 def yield_name(name)
 2   puts "In the method! Let's yield."
 3   yield("Kim")
 4   puts "In between the yields!"
 5   yield(name)
 6   puts "Block complete! Back in the method."
 7 end
 8 
 9 yield_name("Eric") { |n| puts "My name is #{n}." }
10 
11 # Now call the method with your name!
12 yield_name("Wei") { |n| puts "My name is #{n}"}
----------------------------------------------------------------- 输出:
In the method! Let's yield.
My name is Kim.
In between the yields!
My name is Eric.
Block complete! Back in the method.
In the method! Let's yield.
My name is Kim
In between the yields!
My name is Wei
Block complete! Back in the method.
nil

 

分析此段代码:

1.我们定义了带有一个参数name的yield_name方法

2.在程序第9行,我们调用了yield_name方法并传入了参数"Eric", 因为yield_name包含yield语句,所以我们要提供一个block块

3.在yield_name中,我们传给了yield一个参数"Kim", 在block块中n现在存放的就是"Kim", 所以我们输出的是"My name is kim"

4.程序回到yield_name中, 下面有把yield_name的参数name(name中存放的是"Eric")传给了yield,所以我们输出"My name is Eric"

Proc

我们说过, 在Ruby中所用的一切都是对象, 这其实并非事实, 因为blocks不是对象。因此blocks不能被保存到变量, 并且不具备变量所拥有的任何能力。因此我们需要proc。

你可以认为一个proc存储一个block。 就像你可以把一段代码放在一个方法中一样, 你也可以把一个有名字的block放进proc中。proc是保证你代码的DRY(don't repeat yourself)的非常好的方式。如果使用block你必须在每次你要用到它的地方重新写一遍, 但是使用proc你的代码只要写一次就可以多次使用它。

proc的定义非常简单。 你仅仅需要调用Proc.new(Proc首字母大写), 然后传入一个你想要保存的block就可以了

例如:

floats = [1.2, 3.45, 0.91, 7.727, 11.42, 482.911]
round_down = Proc.new { |n| n = n.floor }
ints = floats.collect(&round_down)

我们可以传递proc给一个需要block的方法,我们不必一遍遍重写这个block。&用来把proc转换为block(因为.collect!和.map!通常需要使用block).

使用proc的好处:

1.proc是成熟的对象, 因此它具有一般对象所拥有的一切能力,而block不是对象。

2.不同于block,proc能被一次次使用而不用需要重写。

group_1 = [4.1, 5.5, 3.2, 3.3, 6.1, 3.9, 4.7]
group_2 = [7.0, 3.8, 6.2, 6.1, 4.4, 4.9, 3.0]
group_3 = [5.5, 5.1, 3.9, 4.3, 4.9, 3.2, 3.2]

over_4_feet = Proc.new {|x| x >= 4}

can_ride_1 = group_1.select(&over_4_feet) 
can_ride_2 = group_2.select(&over_4_feet) 
can_ride_3 = group_3.select(&over_4_feet) 

我们还可以直接使用.call来调用proc

例如:

hi = Proc.new{ puts "Hello!" }
hi.call
------------------------------------------
输出
Hello!

 

当symbol遇到proc

我们同样也能用&把symbol转换为proc

strings = ["1", "2", "3"]
nums = strings.map(&:to_i)
# ==> [1, 2, 3]

lambda

和proc一样,lambda也是对象。 除了一些语法上的差异和一些行为上的特性, lambda和proc是一样的。

写法: lambda {puts "Hello"} 和 Proc.new {puts "Hello"} 

lambda的语法: lambda{ |para| block } 

lambda在proc可以使用的地方一样有用。

 

strings = ["leonardo", "donatello", "raphael", "michaelangelo"]
symbolize = lambda { |para| para.to_sym }
symbols = strings.collect(&symbolize)
--------------------------------------------------------------------
输出

[:leonardo, :donatello, :raphael, :michaelangelo]

 

上面代码中 lambda 有一个参数, 并且对这个参数使用.sym方法把字符串转换为symbol。 &用来把lambda转换成block。

lambda vs proc

如果你感觉proc和lambda非常相似, 这是因为他们确实非常相似。。。

它们只有两个主要区别:

1.lamba检查传进来参数的数量,而proc不用。这意味着当你传进错误数量的参数时lambda将会抛出错误,而proc将会忽略不被期望使用的参数,并把它们赋值为nil。

2.当lambda返回时, 它把控制权转移给方法, 而当proc返回时, 它直接返回而不回到调用方法。

例如:

def batman_ironman_proc
  victor = Proc.new { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_proc
--------------------------------------------------------------
输出
Batman will win!
nil
def batman_ironman_lambda
  victor = lambda { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_lambda
----------------------------------------------------------
输出:
Batman will win!
Iron Man will win!
nil

 

posted @ 2014-10-02 11:07  tardis  阅读(293)  评论(0编辑  收藏  举报