Ruby的Proc.new 与Lambda 的区别

Ruby的Proc.new 与Lambda 的区别

缘起,Block 与 Proc

Block 在ruby 中会像空气一般的存在于大量的代码当中。块最开始是用来循环时候用的东西,但慢慢的,block变成了委托利器,贯穿于各种代码之间。

举个例子,我有一个数组1,2,3,4,5,6 我想得到这个数组的平方 在传统的程序里面,我们这样实现

1
2
3
4
5
6
var arr = [1,2,3,4,5,6];
var answer = []`

for(var i = 0; i<arr.length;i++){
  answer[] = arr[i]**2;
}

如果在ruby中,我们即可使用块来完成

1
answer = [1,2,3,4,5].map {|item| item**2}

这里的{|item| item**2} 就是一个Block。

当然之所以这里的ruby 代码简洁的原因是因为map本身隐藏了一些逻辑,实际上map由模块Emuneration 实现 其实通过上面的代码基本上可以看出,Block类似其他语言中的C#或者java中的委托、事件的实现。

我们来仿照着写一个Block的应用(自行实现map)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyArray < Array

  def my_map
    for item in self
      p yield(item)
    end
  end
end

arr = MyArray.new
arr.push 1,3,5

arr.my_map do item
  item**2
end

# 结果输出 
# 1
# 9
# 25

我们把目光还是集中在{|item| item **2} 中,这个代码是我们手敲上去的,那有没有方式将其放入变量里呢? 答案是 有,就是Proc对象 就是我们的Proc.new 和lambda,他们会返回Proc对象,记得在做参数的时候要加&符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class MyArray < Array

  def my_map
    for item in self
      yield(item)
    end

  end

  def my_map_using_proc(&proc)   # 定义的时候就需要表示这是个Proc 对象
    answer = self.class.new
    for item in self
      #p proc.class    输出为Proc 对象
      answer.push(proc.call(item))
    end
    return answer

  end
end



arr = MyArray.new
arr.push 1,3,5

my_proc = Proc.new do |i|
  i**2
end

my_lambda = lambda do |i|
  i**2
end

arr2 = arr.my_map_using_proc(&my_proc)  # 使用的时候也要说明
p arr2

arr3 = arr.my_map_using_proc(&my_lambda)
p arr3

Ruby 下的Proc.new 和Lambda 都是为了生成我们的Proc 而使用的指令,从大方向上来说,他们功能上非常相似,但从细节上来讲,他们又有一些不同点

相同点非常简单,他们都返回Proc对象,结构一样,那么不同点呢?

  1. Proc.new 的参数不需要严格匹配,但是lambda 需要严格匹配

比如这里的func1 = Proc.new {|p,q| p p,q};func1.call("x"), 则输出 "x" nil,但是换做lambda,就不行:

  2.2.0 :014 > func2 = lambda {|p,q| p p,q};func2.call("x")
ArgumentError: wrong number of arguments (1 for 2)
  from (irb):14:in `block in irb_binding'
  from (irb):14:in `call'
  from (irb):14
  from /Users/atpking/.rvm/rubies/ruby-2.2.0/bin/irb:11:in `<main>'
  1. Proc.new 如果里面出现了return,则代表的外部的return,而不是Proc自己的return,则是返回一个Proc对象,比如说
  2.2.0 :035 > def Hello
  2.2.0 :036?>   p = Proc.new{return "inner proc"}
  2.2.0 :037?>   p.call
  2.2.0 :038?>   return "func return"
  2.2.0 :039?>   end
  2.2.0 :041 > Hello()
   => "inner proc"

注意此处,返回的是inner proc, 注意,如果直接在irb 里写p = Proc.new{return 1}
之后p.call 的化,是要报localJumpError的,因为此时irb 并没有结束,还没有return

  2.2.0 :061 > def Hello2
  2.2.0 :062?>   p = lambda{return "inner proc"}
  2.2.0 :063?>   p.call
  2.2.0 :064?>   return "func return"
  2.2.0 :065?>   end
   => :Hello2
  2.2.0 :068 > Hello2()
   => "func return"

此处是func return。

那么这些区别会带来什么变化呢?答案是,lambda 可以带参数的回传, 更加灵活了。比如下面的一个例子

1
2
3
4
5
6
7
8
9
2.2.0 :087 > def my_func(n)
2.2.0 :088?>   b = lambda {|item| return item*n }
2.2.0 :089?>   end
 => :my_func
2.2.0 :090 > my_func(2).call 5
 => 10
2.2.0 :091 > my_func(2).class
 => Proc
2.2.0 :092 >

而proc,就会出现下列的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
2.2.0 :094 > def my_func(n)
2.2.0 :095?>   b = proc {|item| return item*n }
2.2.0 :096?>   end
 => :my_func
2.2.0 :097 > my_func(2).class
 => Proc
2.2.0 :098 > my_func(2).call 5
LocalJumpError: unexpected return
  from (irb):95:in `block in my_func'
  from (irb):98:in `call'
  from (irb):98
  from /Users/atpking/.rvm/rubies/ruby-2.2.0/bin/irb:11:in `<main>'
2.2.0 :099 >

Comments