简洁的想法

仁爱、喜乐、和平、忍耐、恩慈、良善、信实、温柔、节制

Ruby -- Block Proc and Lambda

| Comments

Block是Ruby中相当sexy的特性。对于常年在C/C++中Coding的码农来说,不管是表达方式还是思考方式都有点让人感觉不适应。 Block是一个统称,中文名称又叫闭包,英文是Closure,表现形式有block, Proc and lambda。Proc是对block的面向对象的封装, lambda是对Proc的进一步封装。

block

虽然Ruby中万物皆对象,但block是作为一个特性而存在,不是对象。也许很多Rails程序员还没看Ruby语法就已经用scaffold写Blog了,对别人程序中的一些代码连蒙带猜也很看得差不多懂,就比如下面的代码:

1
2
3
4
5
6
my_array = [ 1, 2, 3, 4, 5 ]
my_array.each { | number | puts number}
my_array.each do | number |
  puts number
end
my_array.each_index { | index | puts "number has #{index}" }

简单来说,each后面的几种表达就是block,上面的例子就是调用Array对象的block方法。看起来好像很神秘,其实我们也可以为自己的类定义一个block方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyArray
  attr_accessor :my_arr
  def initialize( my_arr )
    @my_arr = my_arr
  end

  def my_each( &my_block )
    for i in 0..@my_arr.length-1
      my_block.call( @my_arr[i] )
    end
  end
end

a = MyArray.new( [1,2,3,4] )
a.my_each { | number | puts number }

结果很简单

1
2
3
4
1
2
3
4

之所以很多变量和方法都加个my前缀,是因为我想告诉大家这都是自定义的,是不是很帅?哦,不好意思,是不是很sexy?

既然已经说到自定义了,不能这样就结束了,我们可以再复杂一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyArray
  attr_accessor :my_arr
  def initialize( my_arr )
    @my_arr = my_arr
  end

  def my_each( &my_block )
    for i in 0..@my_arr.length-1
      my_block.call( @my_arr[i], i )
    end
  end
end

a = MyArray.new( [ 1, 2, 3, 4 ] )
a.my_each { | number, index | puts "number at #{index} has value #{number}" }

结果如下:

1
2
3
4
number at 0 has value 1
number at 1 has value 2
number at 2 has value 3
number at 3 has value 4

接下来不得不说一下yield这个关键字,我们从简单的例子开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyArray
  attr_accessor :my_arr
  def initialize( my_arr )
    @my_arr = my_arr
  end

  def my_yield
    yield
  end
end

a = MyArray.new( [ 1, 2, 3, 4 ] )
a.my_yield { puts "yield is also sexy!" }

请大家无视1,2,3,4, 上面的例子只会输出yield is also sexy!, 也就是说 a.my_yield 后面的所有内容都跑到 my_yield 中,替换了 yield,简单吧。

下面开始对其升级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyArray
  attr_accessor :my_arr
  def initialize( my_arr )
    @my_arr = my_arr
  end

  def my_yield
    yield( @my_arr )
  end
end

a = MyArray.new( [ 1, 2, 3, 4 ] )
a.my_yield {| my_tmp_arr |
  puts "yield with parameter!"
  my_tmp_arr.each{| number | puts number}
}

输出如下:

1
2
3
4
5
yield with parameter!
1
2
3
4

如果你不是高手,我相信你会回头再品一下代码的,这个my_yield中到底发生了什么事?其实也不难,按照上例中的,把a.my_yield后面的全部甩到my_yield中替换yield, 然后用@my_arr替换my_tmp_arr就可以了。

Proc

前面说到Proc是Ruby对block的面向对象的封装,简单来说,就是我自己定义一个可以多次重用的block。还是看个例子吧,比如我想计算一个长方形的面积:

1
2
rectangle_area = Proc.new{ | a, b | puts a * b }
rectangle_area.call( 5, 6 )

如果我的想固定长边,只输入宽度就好了,那我可以加入一个参数:

1
2
3
4
5
6
7
8
def rectangle_area_with_length (length)
  Proc.new{ | width | width * length }
end

area = rectangle_area_with_length(6)
area.call(3)
area[3]
area.class # => Proc

最后两种call的方式都行,结果都是18。我啰嗦一句,Ruby语法是可以省略return的,所以上面函数的返回值是个Proc,Proc里面的block返回值是width * length,没了return,眼睛里的确清静了很多。

lambda

lambda是Ruby的一个函数,用来创建Proc

1
2
3
multiply_lambda_proc = lambda { | x, y | x * y }
# and we can call as a normal Proc
multiply_lambda_proc.call( 3, 4 ) # return 12

其与 Proc 主要有两个不同点:

第一,lambda 会检查参数,而 Proc 不会。

1
2
3
4
5
6
7
8
multiply_lambda_proc = lambda { | x, y | x * y }
multiply_proc = Proc.new { | x, y | x * y }

multiply_lambda_proc.call( 3, 4, 5 ) # ArgumentError: wrong number of arguments (3 for 2)
multiply_proc( 3, 4, 5 ) # return 12 as normal

# This last command's error shows that Proc auto assigns missing argument with nil
multiply_proc( 3 )  # TypeError: nil can't be coerced into Fixnum

第二,lambda 会返回它的调用函数,但 Proc 会结束它所位于的 function。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def return_from_proc
  ruby_proc = Proc.new { return "return from a Proc" }
  ruby_proc.call
  return "The function will NOT reach here because a Proc containing a return statement has been called"
end

def return_from_lambda
  ruby_lambda = lambda { return "return from lambda" }
  ruby_lambda.call
  return "The function will reach here"
end

puts return_from_proc # display return from proc
puts return_from_lambda # display The function will reach here

参考: RUBY BLOCK, PROC & LAMBDA

理解Ruby中block的本质

Comments