浅谈ruby语言中的一些概念(lambda, proc, block)

本文参考于 ruby-china: 聊聊 Ruby 中的 block, proc 和 lambda,是一篇读后小结,主要便于自己理解.侵删

block 和 Proc


ruby中的block是方法的一个重要但非必要的组成部分,任何方法后面都可以挂载一个block,如果你定义的方法想使用block做点事情.那么你需要使用yield关键字或者&p.
def f1
  puts 'f1'
end

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

def f3
  puts 'f3'
  yield if block_given?
end

f1  ## 'f1', 普通方法调用不挂载块时
f1 {puts 'block'} ## 'f1' 普通方法调用挂载块时

f2 ## 'f2', 如果没有 判断 if block_given?, 将会报错
f2 {puts 'block'} ## 'f2' 'block', 挂载了块作为参数&p传入,p是一个proc,可以用关键字 call 来执行

f3 ## 'f2', 如果没有 判断 if block_given?, 将会报错
f3 {puts 'block'} ## 'f2' 'block',挂载了块,用关键字 yield触发
我们可以看到,任何方法都可以挂载一个block.
要触发block执行时,用关键字 yield会执行传入的块.
也可以通过在参数里定义&p, 传入block为一个参数,用call去触发.
block就是一段绑定了当前作用域变量代码,call的时候就是当前位置嵌入了代码

那么block作为参数传入方法后是一个什么对象呢?既然是参数,是不是可以再作为参数传递给其他方法呢?
def block_args(&p)
  puts p.class
  puts p
  p.call
  block_yield &p 
end

def block_yield
  puts 'yield'
  yield
end

block_args {puts 'hahaha'} 
## 'Proc' 
## #<Proc:0x007fb2f3ae5da0@(irb):334>
## 'hahaha'
## 'yield'
## 'hahaha'
由上可见,block作为参数传入方法后是一个Proc的实例.call是proc的method,传入的块是&p, 在方法里 p是proc, 如果要作为参数再进行传递,应该使用块 即 &p.

记住这句话: "&p是block, p是proc",一般挂载在方法后面的是block,proc是个'幕后工作者',一般不会显式地创建它.

yield和&p的用法类似,&p参数传入作为c可以作为参数再传递,yield无需在方法上定义参数,使用方便却也无法再传递.


那么理解了这个之后再回过头来看一些我们常用的技巧时,例如:

['1', '2', '3', '4'].map(&:to_i)
此处的&:to_i 类比为 &p的话,符号&会触发:to_i的to_proc方法,to_proc执行后会返回一个proc实例,然后&会把这个proc实例转换成一个block.
我们需要要明白map方法后挂的是一个block,而不是接收一个proc对象做为参数.
&:to_i是一个block,block不能独立存在,同时你也没有办法直接存储或传递它,必须把block挂在某个方法后面.
## :to_i是一个Symbol实例
class Symbol
  def to_proc
    Proc.new {|obj| obj.send(self) }
  end
end

## 所以 map(&:to_i)也应该是转化为 map{|i| i.to_i }
['1', '2', '3', '4'].map{|i| i.to_i }
## map 方法后面 传入的参数是个block.

做个小结,block和proc是两种不同的东西,block有形无体,proc可以将block实体化,可以把&p看做一种运算,其中&触发p的to_proc方法,然后&会将to_proc方法返回的proc对象转换成block.



lambda

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

在ruby里创建一个Proc的实例对象有如下几种方法:
Proc.new {} ## #<Proc:0x007fb2f39fd0f0@(irb):367>
proc {}  ## #<Proc:0x007fb2f3a02a50@(irb):368>
lambda {}  ## #<Proc:0x007fb2f3a00070@(irb):369 (lambda)>
-> {}  ## #<Proc:0x007fb2f41c9838@(irb):370 (lambda)>
  • 我们惊奇地发现在ruby里要创建一个Proc的方法居然有四种,不管是lambda,还是proc,亦或者是-> 居然都是Proc的实例.但是我们仔细看还是可以看出 Proc.new 和 proc 应该是一样的,区别于 lambda 和 -> (都含有 (lambda)). 所以我们说proc和lambda是不一样的

    那么具体哪些地方不一样呢?先来几个经典的例子:

## 首先我们在上面说了 proc来源用& 符号来触发转换成block
def f(p)
  instance_eval &p
  1
end
p1 = Proc.new {}
p2 = proc {}
l1 = lambda {}

f(p1) # 1
f(p2) # 1
f(l1) # ArgumentError wrong number of arguments (1 for 0)
f(l2) # ArgumentError wrong number of arguments (1 for 0)
  • proc 和 lambda 第一处不同点是,proc可以用&转化成block,而lambda不行.
p = proc{return 0}
l = lambda {return 0}
p.call ##  LocalJumpError: unexpected return
l.call ##  0

#############

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

def f1()
  p = proc { return 0 }
  p.call
  1
end

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

def f3()
  l = -> { return 0 }
  l.call
  1
end

f0  ## 0
f1  ## 0
f2  ## 1
f2  ## 1
  • proc 和 lambda 第二处不同点是对return关键字的处理不同.
  • proc的return只能在方法体里执行,proc单独call会报错,lambda则可以单独执行call.
  • 方法体里的 proc 对return敏感,执行时如果有return则中断跳出方法体,不会再继续往下执行.
  • 而 lambda 的执行更像是调用了某个方法,方法体里return返回后会继续往下执行.
p = proc {|a, b| puts 'proc args'}
l = lambda {|a, b| puts 'lambda args'}
p.call(1, 2) ## proc args
l.call(1, 2) ## lambda args
p.call ## proc args
l.call ## ArgumentError: wrong number of arguments (0 for 2)
proc 和 lambda 第三处不同点是对对参数的检查.proc 不检查参数,lambda检查参数.

小结

做个小结,lambda和proc是Proc的两种不同实例.proc是在方法体的当前上下文处执行一段block代码的.而lambda更像是一个方法,将block作为一个方法,call的时候去调用这个方法.

lambda 检查参数, return时正常返回, proc 不检查参数,return 时中断方法体.lambda更像是一个方法.匿名方法.

转载于:https://www.cnblogs.com/cadernn/p/6945293.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值