block, proc, lambda

&:to_i是一个block,block不能独立存在,同时你也没有办法直接存储或传递它,
必须把block挂在某个方法后面。

一、从最简单的看起


  def f1
    yield
  end

  def f2(&p)
    p.call
  end

  def f3(p)
    p.call
  end

  f1 { puts "f1" }
  f2 { puts "f2" }

  f3(proc{ puts "f3" })
  f3(Proc.new{ puts "f3" })
  f3(lambda{ puts "f3" })
  f3(->{ puts "f3" })

由于 &p 并不作为方法的参数,所以f2
不能传递一个参数,f2需要的是一个block。

&p 相等于一种申明,当方法后面有block的时候,会把block捕捉进来。

f3 需要一个Proc的参数,所以就需要传递一个Proc进去。

二、block

block,ruby中的block是方法一个重要但非必要的组成部分,我们可以认为方法
的完整定义类似于


  def f(零个或多个参数,&p)
    ...
  end

注意&p不是参数,&p类似于一种声明,当方法后面有block时,会将block捕捉起来
存放在变量p中,如果方法后面没有block,那么&p什么也不干。


>> def f(&p)
>>   end
=> :f
>> f(1)
ArgumentError: wrong number of arguments (1 for 0)
    from (irb):72:in `f'
    from (irb):74
    from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>> f
=> nil
>> f(){ puts "f" }
=> nil
>> f()
=> nil

从以上代码的运行结果可以知道&p不是参数


>> def f(a)
>>   puts a
>>   end
=> :f
>> f(1) { puts 2 }
1
=> nil

所以任何方法都可以挂载一个block,如果你定义的方法想使用block做点事情,
那么你需要使用yield关键字或&p


>> def f1
>>   yield
>>   end
=> :f1
>> def f2(&p)
>>   p.call
>>   end
=> :f2
>> f1
LocalJumpError: no block given (yield)
    from (irb):88:in `f1'
    from (irb):93
    from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>> f2
NoMethodError: undefined method `call' for nil:NilClass
    from (irb):91:in `f2'
    from (irb):94
    from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>>

为了保证不抛出异常,我们可以这么修改


  def f1
    yield if block_given?
  end

  def f2(&p)
    p.call if block_given?
  end


这样,f1和f2后面无论挂不挂block都不会抛异常了。


def f2(&p)
  puts p.class
  puts p.inspect
  p.call
end

>> f2{->{ puts "123" }}
Proc
#<Proc:0x007fb57c0884d0@(irb):102>
=> #<Proc:0x007fb57c088390@(irb):102 (lambda)>

["1", "2", "3"].map(&:to_i),其效果和["1", "2", "3"].map {|i| i.to_i }一样, 但简洁了许多,并且更加拉风。
这里的魔法在于符号&会触发:to_i的to_proc方法, to_proc执行后会返回一个proc实例, 然后&会把这个proc实例转换成一个block,我们需要要明白map方法后挂的是一个block,而不是接收一个proc对象做为参数。&:to_i是一个block,block不能独立存在,同时你也没有办法直接存储或传递它,必须把block挂在某个方法后面。

:to_i是Symbol类的实例,Symbol中的to_proc方法的实现类似于


  class Symbol
    def to_proc
      Proc.new { |obj| obj.send(self) }
    end
  end



给自己的类编写to_proc 方法,然后使用&耍下酷,比如


>> class AddBy
>>   def initialize(num = 0)
>>     @num = num
>>     end
>>   def to_proc
>>     Proc.new { |obj| obj.send('+', @num) }
>>     end
>>   end
=> :to_proc
>> add_by_9 = AddBy.new(9)
=> #<AddBy:0x007fba3997c2c8 @num=9>
>> puts [1,2,3].map(&add_by_9)
10
11
12
=> nil


ruby中,block 存在形式


do |...|
  ...
end

有时候是这样


{|...| ...}

或者类似 &p, &:to_i, &add_by_9 之类,但是它无体,无体
的意思就是说block无法单独存在,必须挂在方法后面,并且你
没有办法直接把它存到变量中,也没有办法直接将它作为参数
传递给方法,所以当你想存储,传递block时,你可以使用proc
对象了。


 p = Proc.new(&:to_i)
 p = Proc.new {|obj| obj.to_i }
 p = Proc.new do |obj|
   obj.to_i
 end
 p = proc(&:to_i)
 p = proc {|obj| obj.to_i}
 p = proc do |obj|
   obj.to_i
 end


我们经常在该挂block的时候,却把proc对象当参数传给方法了,
或者不明白&p就是block可以直接交给方法使用

** &p是block, p是proc 不到万不得已的情况下不要显式地创建proc **

三、lambda

lambda是匿名方法,lambda和proc也是两种不同的东西,但是在
ruby中lambda只能依附proc而存在,这点和block不同,block并不
依赖proc


  >> p = Proc.new {}
  => #<Proc:0x007fba3a8dd708@(irb):11>
  >> puts p
  #<Proc:0x007fba3a8dd708@(irb):11>
  => nil
  >> l = lambda {}
  => #<Proc:0x007fba3a8cd498@(irb):13 (lambda)>
  >> puts l
  #<Proc:0x007fba3a8cd498@(irb):13 (lambda)>

从这里可以理解ruby的设计者们确实在有意的区分
lambda和proc,并不想把lambda和proc混在一起,如同ruby中
没有叫Block的类,除非你自己定义一个,ruby中也没有叫
Lambda的类,于是将lambda对象化的活儿就交给了Proc。

当你用lambda弄出一个匿名方法时,发现它是一个proc对象,并且
这个匿名方法能干的活,proc对象都能做,于是我们不淡定了。


  Proc.new{} 这样可以
  proc {} 这样也没有问题
  lambda {} 这样做也不错
  -> {} 这个还是能行

lambda 和 proc 之间的区别除了那个经常用做面试题的return之外
还有一个区别就是lambda 不能完美的转换为block。而proc可以完美
的转换为block,注意,我说的lambda指的是lambda方法或者->符号生成
的proc,当然和方法一样lambda是严格检查参数的,这个特点也和proc不一样。


def f0()
  p = Proc.new { return 0}
  p.call
  1
end

def f1()
  l = lambda { return 0}
  l.call
  1
end

f0 # 返回0
f1 # 返回1

如果你能够理解proc在行为上更像block,lambda其实就是方法只不过是匿名的,那么你对上面的结果不会感到惊讶。

如果把f0,f1做一些修改,就更容易理解上面的结果了。


def f0()
  return 0
  1
end

def f1()
  def __f1
    return 0
  end
  __f1
  1
end

f0 # 返回0
f1 # 返回1


验证proc不需要参数校验,而lambda需要参数校验。


>> def f5()
>>   yield 1,2
>>   end
=> :f5
>> p1 = proc { |i,j| puts j }
=> #<Proc:0x007fba3a856370@(irb):42>
>> p2 = proc { |i| puts i}
=> #<Proc:0x007fba398f0c28@(irb):43>
>> l1 = lambda { |i,j| puts j }
=> #<Proc:0x007fba398e0580@(irb):44 (lambda)>
>> l2 = lambda { |i| puts i }
=> #<Proc:0x007fba3a845160@(irb):45 (lambda)>
>> f5(&p1)
2
=> nil
>> f5(&p2)
1
=> nil
>> f5(&l1)
2
=> nil
>> f5(&l2)
ArgumentError: wrong number of arguments (2 for 1)
    from (irb):45:in `block in irb_binding'
    from (irb):40:in `f5'
    from (irb):49
    from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>>

四. 总结

  1. block和Proc都是代码块,Proc可以复用,block不可以复用

  2. block和lambda不是类,Proc是类,所以block和lambda是小写

  3. lambda 是匿名函数

  4. lambda对参数个数验证,Proc不会验证

  5. lambda会执行return,Proc遇到return会中断

  6. lambda不会转换成block,Proc可以转换成block

方法定义中&p参数,相等于声明,如果后面包括block的话,就会将后面的block捕捉进来放在p中

任何方法都可以挂载block,如果想要block做一些事情,就需要在定义方法的时候,指定yield或&p

  1. ["1", "2", "3"].map(&:to_i) 相等于 ["1", "2", "3"].map{ |obj|
    obj.to_i }
    这里的魔法在于符号&会触发:to_i的to_proc方法,to_proc方法执行后
    会返回一个proc实例,然后&会把这个proc转换成block。并且map后面跟
    的也是一个block

  2. &p中,&p是block,而p是proc

ruby-china

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容