blocks, Procs, Methods, lambdas(也称闭包)是Ruby中最强大的一部分,用过你就会知道,同时也是最容易迷惑的。
这多是由于Ruby处理闭包的方式有点怪。更甚的是,Ruby有4种处理闭包的方式, 第一次用,每种都不太顺手。 数据库
最多见、最简单、最富争议、最有Ruby风格的方式是blocks。写法以下: 数组
array = [1, 2, 3, 4] array.collect! do |n| n ** 2 end puts array.inspect # => [1, 4, 9, 16]
do…end构成一个block。而后把这个block经过collect!传给一个数组。就可使用block中的n来迭代数组中每一个元素。
collect!是Ruby库里的方法,下面咱们来写一个本身的相似方法iterate! ruby
class Array def iterate! self.each_with_index do |n, i| self[i] = yield(n) end end end array = [1, 2, 3, 4] array.iterate! do |n| n ** 2 end puts array.inspect # => [1, 4, 9, 16]
首先,咱们打开Array,并添加进iterate!方法。方法名以!结尾表示危险方法,引发注意。如今咱们就可能像使用collect!同样使用iterate! 闭包
与属性不一样,在方法中不须要指定block的名字,而是使用yield来调用。yield会执行block中的代码。同时,注意咱们是怎么把n(each_with_index当前处理的数字)传给yield的。传给yield的参数即对应了block中的参数(||中的部分)。如今n就能被block调用并在yield调用中返回n**2。
整个调用以下:
一、一个整数组成的数组调用iterate!
二、当yield被调用时,把n(第一次为1,第二次为2,…)传给block
三、block对n进行n**2。由于是最后一行,自动做为结果返回。
四、yield获得block的结果,并把值重写到array里。
五、数据中每一个对象执行相同操做。 函数
以上仅仅是个开始,yield只是调用block的一种方式,还有一种叫Proc,看看。 spa
class Array def iterate!(&code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array = [1, 2, 3, 4] array.iterate! do |n| n ** 2 end puts array.inspect # => [1, 4, 9, 16]
和上一段代码只有两个不一样
一、为iterate!传递一个参数&code,&表示这个参数是block。
二、在iterate!中没有使用yield而是call。
结果相同,为何还要这种不一样的语法呢?让咱们先来看一个到底什么是blocks吧? 翻译
def what_am_i(&block) block.class end puts what_am_i {} # => Proc
block居然是Proc!那Proc是什么? code
blocks很简单,但当咱们须要处理不少blocks,屡次使用一个blocks时,咱们不得不重复代码。既然Ruby是彻底面向对象的,咱们就能把这些可复用的代码保存成object。这段可复用的代码就是Proc(procedure的简称)
block与Proc唯一的不一样是:block是不能保存的Proc,一性的。 对象
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array_1 = [1, 2, 3, 4] array_2 = [2, 3, 4, 5] square = Proc.new do |n| n ** 2 end array_1.iterate!(square) array_2.iterate!(square) puts array_1.inspect puts array_2.inspect # => [1, 4, 9, 16] # => [4, 9, 16, 25]
注意:并无在 iterate!的参数头部添加&,由于Proc只是一个普通类,不须要特殊处理。 three
上面的方式也是大多数语言处理闭包的方式。
而block是Ruby特有的方式。
另外Ruby不仅使用blocks作闭包还有一个缘由。好比有时咱们须要传递多个闭包给一个方法,这时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
因此,何时用blocks而不用Procs呢?我通常这样判断:
一、Block:方法把一个对象拆分红不少片断,而且你但愿你的用户能够与这些片断作一些交互。
二、Block:但愿自动运行多个语句,如数据库迁移(database migration)。
三、Proc:但愿屡次复用一段代码。
四、Proc:方法有一个或多个回调方法(callbacks)。
为何block小写,而Proc大写
这只是我我的习惯。由于Proc是Ruby中的一个相似,而blocks并无本身的类(本质上只是Procs),只是一种语法规则。后面的lambda 小写也是如此。
上面的Procs与blocks用法很像其它语言中的匿名函数(即lambdas)。Ruby也支持lambdas.
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array = [1, 2, 3, 4] array.iterate!(lambda { |n| n ** 2 }) puts array.inspect # => [1, 4, 9, 16]
lambdas看起来很像Procs,但它们有2个细微的区别。
一、lambdas检查参数的个数,Procs不会。
def args(code) one, two = 1, 2 code.call(one, two) end args(Proc.new{|a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}"}) args(lambda{|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 # *.rb:8: ArgumentError: wrong number of arguments (2 for 3) (ArgumentError)
能够看到,在Proc中,多余的参数被设为nil。但lambdas中,Ruby抛出了一个错误。
二、return不一样。lambdas的return是返回值给方法,方法会继续执行。Proc的return会终止方法并返回获得的值。有点拗口,下面看例子。
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 puts lambda_return
proc_return中,执行到Proc.new中的return时,直接返回”Proc.new”,不继续执行。
lambda_return中,执行到lambda中的return时,返回”lambda”,方法继续执行。
为何会有这样的不一样?
答案在于procedures和methods概念上的不一样。
Ruby中的Procs是代码片断(code snippets),不是方法。所以,Proc的return就是整个方法的return。
但lambdas就像是单独的methods(只不过是匿名的),因此它要检查参数个数,且不会覆盖整个方法的返回。
所以,最好把lambdas看成另外一种methods的写法,一种匿名的方式。
因此,何时用lambda而不是Proc呢?能够参考下面代码:
def generic_return(code) code.call return "generic_return method finished" end puts generic_return(Proc.new { return "Proc.new" }) puts generic_return(lambda { return "lambda" }) # => *.rb:6: unexpected return (LocalJumpError) # => generic_return method finished
Ruby语法中通常参数(例子中为Proc)不能含有return。但使用了lambda后能够用return。
还能够参考:
def generic_return(code) one, two = 1, 2 three, four = code.call(one, two) return "Give me a #{three} and a #{four}" 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:9: unexpected return (LocalJumpError) # => Give me a 4 and a # => Give me a 3 and a 4
使用lambda,代码很天然。但若是用Proc,咱们须要对Arrays进行赋值。
当你想把一个方法以闭包的形式传递给另外一个方法,而且保持代码DRY。你可使用Ruby的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 array = [1, 2, 3, 4] array.iterate!(method(:square)) puts array.inspect # => [1, 4, 9, 16]
例子中,咱们先有了一个square方法。咱们能够把它转换成一个Method对象并以参数形式传递给iterate!方法。但,这个新对象属于哪一个类呢?
def square(n) n ** 2 end puts method(:square).class # => Method
如你所料,square不是Proc,而是Method。Method与lambda用法相同,由于它们的概念是同样的。不一样的是Method是有名字的method,而lambda是匿名method.
到此为止,咱们已经了解了Ruby的4种闭包类型:blocks, Procs, lambdas 和 Methods。
blocks和Procs看起来像在代码中插入代码片断。而lambdas和Methods看起来像方法。
经过几个例子和比较,但愿你能了解如何灵活运用闭包,游刃有余!
译至:http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/ 翻译很辛苦,转载请附连接:)