Ruby [新手指南] Ruby 的 Block 以及 Proc.new 与 Lambda 的区别

jicheng1014 · 2015年04月19日 · 最后由 chcyellow 回复于 2016年08月11日 · 4386 次阅读

自己重读了 ruby 入门书,做了点笔记,如果您对 block 这块不太熟悉,可以参考一下,若有错误,还请指教,谢谢

缘起,Block 与 Proc

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

举个例子,我有一个数组 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 中,我们即可使用块来完成

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

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

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

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

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 对象,记得在做参数的时候要加&符号

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>'
    
  2. 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 可以带参数的回传,更加灵活了。比如下面的一个例子

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,就会出现下列的问题

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 >

前段时间我也写了一篇关于理解 block 的 http://www.jiang-di.org/ruby/2015/04/01/rubys-wired-block.html

再讲一下 closure 就全面了。

如果我没记错的话 block:匿名代码块 Proc:可复用代码块 lambda:匿名方法

需要 登录 后方可回复, 如果你还没有账号请 注册新账号