blocks, Procs, Methods, lambdas(也称闭包)是Ruby中最强大的一部分,用过你就会知道,同时也是最容易迷惑的。
这可能是因为Ruby处理闭包的方式有点怪。更甚的是,Ruby有4种处理闭包的方式, 第一次用,每种都不太顺手。

首先:blocks代码块

最常见、最简单、最富争议、最有Ruby风格的方式是blocks。写法如下:
array = [1, 2, 3, 4]
array.collect! do |n|n ** 2
end
puts array.inspect
# => [1, 4, 9, 16]
do…end构成一个block。然后把这个block通过collect!传给一个数组。就可以使用block中的n来迭代数组中每个元素。

collect!是Ruby库里的方法,下面我们来写一个自己的类似方法iterate!

class Arraydef iterate!self.each_with_index do |n, i|self[i] =     endend
end
array = [1, 2, 3, 4]
array.iterate! puts array.inspect
# => [1, 4, 9, 16]
首先,我们打开Array,并添加进iterate!方法。方法名以!结尾表示危险方法,引起注意。现在我们就可能像使用collect!一样使用iterate!
与属性不同,在方法中不需要指定block的名字,而是使用yield来调用。yield会执行block中的代码。同时,注意我们是怎么把n(each_with_index当前处理的数字)传给yield的。传给yield的参数即对应了block中的参数(||中的部分)。现在n就能被block调用并在yield调用中返回n**2。
整个调用如下:
1、一个整数组成的数组调用iterate!
2、当yield被调用时,把n(第一次为1,第二次为2,…)传给block
3、block对n进行n**2。因为是最后一行,自动作为结果返回。
4、yield得到block的结果,并把值重写到array里。
5、数据中每个对象执行相同操作。
以上仅仅是个开始,yield只是调用block的一种方式,还有一种叫Proc,看看。
class Arraydef iterate!(&code)self.each_with_index do |n, i|self[i] = code.call(n)endend
end
array = [1, 2, 3, 4]
array.iterate! do |n|n ** 2
end
puts array.inspect
# => [1, 4, 9, 16]
和上一段代码只有两个不同
1、为iterate!传递一个参数&code,&表示这个参数是block。
2、在iterate!中没有使用yield而是call。
结果相同,为什么还要这种不同的语法呢?让我们先来看一个到底什么是blocks吧?
def what_am_i(&block)block.class
end
puts what_am_i {}
# => Proc
block竟然是Proc!那Proc是什么?

Procs 过程

blocks很简单,但当我们需要处理很多blocks,多次使用一个blocks时,我们不得不重复代码。既然Ruby是完全面向对象的,我们就能把这些可复用的代码保存成object。这段可复用的代码就是Proc(procedure的简称)
block与Proc惟一的不同是:block是不能保存的Proc,一性的。
class Arraydef iterate!(code)self.each_with_index do |n, i|self[i] = code.call(n)endend
end
array_1 = [1, 2, 3, 4]
array_2 = [2, 3, 4, 5]
square = Proc.new do |n|n ** 2
end
array_1.iterate!(square)
array_2.iterate!(square)
puts array_1.inspect
puts array_2.inspect
# => [1, 4, 9, 16]
# => [4, 9, 16, 25]
注意:并没有在 iterate!的参数头部添加&,因为Proc只是一个普通类,不需要特殊处理。
上面的方式也是大多数语言处理闭包的方式。
而block是Ruby特有的方式。
另外Ruby不只使用blocks做闭包还有一个原因。比如有时我们需要传递多个闭包给一个方法,这时block马上力不从心了。但我们可以用Proc:
def callbacks(procs)procs[:starting].callputs "Still going"procs[:finishing].call
end
callbacks(:starting => Proc.new { puts "Starting" },:finishing => Proc.new { puts "Finishing" })
# => Starting
# => Still going
# => Finishing
所以,什么时候用blocks而不用Procs呢?我一般这样判断:
1、Block:方法把一个对象拆分成很多片段,并且你希望你的用户可以与这些片段做一些交互。
2、Block:希望自动运行多个语句,如数据库迁移(database migration)。
3、Proc:希望多次复用一段代码。
4、Proc:方法有一个或多个回调方法(callbacks)。

为什么block小写,而Proc大写

这只是我个人习惯。因为Proc是Ruby中的一个类似,而blocks并没有自己的类(本质上只是Procs),只是一种语法规则。后面的lambda 小写也是如此。

Lambda 拉姆达表达式

上面的Procs与blocks用法很像其它语言中的匿名函数(即lambdas)。Ruby也支持lambdas.
class Arraydef iterate!(code)self.each_with_index do |n, i|self[i] = code.call(n)endend
end
array = [1, 2, 3, 4]
array.iterate!(lambda { |n| n ** 2 })
puts array.inspect
# => [1, 4, 9, 16]
lambdas看起来很像Procs,但它们有2个细微的区别。
1、lambdas检查参数的个数,Procs不会。
def args(code)one, two = 1, 2code.call(one, two)
end
args(Proc.new{|a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}"})
args(lambda{|a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}"})
# => Give me a 1 and a 2 and a NilClass
# *.rb:8: ArgumentError: wrong number of arguments (2 for 3) (ArgumentError)
可以看到,在Proc中,多余的参数被设为nil。但lambdas中,Ruby抛出了一个错误。
2、return不同。lambdas的return是返回值给方法,方法会继续执行。Proc的return会终止方法并返回得到的值。有点拗口,下面看例子。
def proc_returnProc.new { return "Proc.new"}.callreturn "proc_return method finished"
end
def lambda_returnlambda { return "lambda" }.callreturn "lambda_return method finished"
end
puts proc_return
puts lambda_return
proc_return中,执行到Proc.new中的return时,直接返回”Proc.new”,不继续执行。
lambda_return中,执行到lambda中的return时,返回”lambda”,方法继续执行。
为什么会有这样的不同?
答案在于procedures和methods概念上的不同。
Ruby中的Procs是代码片段(code snippets),不是方法。因此,Proc的return就是整个方法的return。
但lambdas就像是单独的methods(只不过是匿名的),所以它要检查参数个数,且不会覆盖整个方法的返回。
因此,最好把lambdas当作另一种methods的写法,一种匿名的方式。
所以,什么时候用lambda而不是Proc呢?可以参考下面代码:
def generic_return(code)code.callreturn "generic_return method finished"
end
puts generic_return(Proc.new { return "Proc.new" })
puts generic_return(lambda { return "lambda" })
# => *.rb:6: unexpected return (LocalJumpError)
# => generic_return method finished
Ruby语法中一般参数(例子中为Proc)不能含有return。但使用了lambda后可以用return。
还可以参考:
def generic_return(code)one, two    = 1, 2three, four = code.call(one, two)return "Give me a #{three} and a #{four}"
end
puts generic_return(lambda { |x, y| return x + 2, y + 2 })
puts generic_return(Proc.new { |x, y| return x + 2, y + 2 })
puts generic_return(Proc.new { |x, y| x + 2; y + 2 })
puts generic_return(Proc.new { |x, y| [x + 2, y + 2] })
# => Give me a 3 and a 4
# => *.rb:9: unexpected return (LocalJumpError)
# => Give me a 4 and a
# => Give me a 3 and a 4
使用lambda,代码很自然。但如果用Proc,我们需要对Arrays进行赋值。

Method 对象

当你想把一个方法以闭包的形式传递给另一个方法,并且保持代码DRY。你可以使用Ruby的method方法。
class Arraydef iterate!(code)self.each_with_index do |n, i|self[i] = code.call(n)endend
end
def square(n)n ** 2
end
array = [1, 2, 3, 4]
array.iterate!(method(:square))
puts array.inspect
# => [1, 4, 9, 16]
例子中,我们先有了一个square方法。我们可以把它转换成一个Method对象并以参数形式传递给iterate!方法。但,这个新对象属于哪个类呢?
def square(n)n ** 2
end
如你所料,square不是Proc,而是Method。Method与lambda用法相同,因为它们的概念是一样的。不同的是Method是有名字的method,而lambda是匿名method.

总结

到此为止,我们已经了解了Ruby的4种闭包类型:blocks, Procs, lambdas 和 Methods。
blocks和Procs看起来像在代码中插入代码片段。而lambdas和Methods看起来像方法。

转载于:https://blog.51cto.com/flyingsnail/1088024

理解Ruby的4种闭包:blocks, Procs, lambdas 和 Methods相关推荐

  1. 深入理解Blocks,Procs和lambdas

    首发在:[url]http://blog.enjoyrails.com/?p=125[/url] 原文:[url]http://www.robertsosinski.com/2008/12/21/un ...

  2. 面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)

    文章目录 对JS闭包的理解及常见应用场景(闭包的作用) 1.变量作用域 2.如何从外部读取函数内部的变量? 3.闭包概念 4.闭包用途 5.闭包的理解 6.闭包应用场景 setTimeout传参 回调 ...

  3. 快速理解VirtualBox的四种网络连接方式

    转自:http://www.cnblogs.com/york-hust/archive/2012/03/29/2422911.html VirtualBox中有4中网络连接方式: NAT Bridge ...

  4. 理解JS的6种继承方式

    [转]重新理解JS的6种继承方式 写在前面 一直不喜欢JS的OOP,在学习阶段好像也用不到,总觉得JS的OOP不伦不类的,可能是因为先接触了Java,所以对JS的OO部分有些抵触. 偏见归偏见,既然面 ...

  5. Ubuntu系统安装Ruby的三种方法

    Ubuntu系统安装Ruby的三种方法 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs Ruby是一个开源的动态编程语言,它有优美的语法,可用于构建可伸缩 ...

  6. 三分钟理解Python函数式编程与闭包

    函数式编程 函数式编程这个概念我们可能或多或少都听说过,刚听说的时候不明觉厉,觉得这是一个非常黑科技的概念.但是实际上它的含义很朴实,但是延伸出来许多丰富的用法. 在早期编程语言还不是很多的时候,我们 ...

  7. 谷歌AI论文BERT双向编码器表征模型:机器阅读理解NLP基准11种最优(公号回复“谷歌BERT论文”下载彩标PDF论文)

    谷歌AI论文BERT双向编码器表征模型:机器阅读理解NLP基准11种最优(公号回复"谷歌BERT论文"下载彩标PDF论文) 原创: 秦陇纪 数据简化DataSimp 今天 数据简化 ...

  8. 深入理解MySQL的四种隔离级别

    深入理解MySQL的四种隔离级别[日期:2017-02-20] 来源:Linux社区 作者:Linux [字体:大 中 小]什么是事务事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个 ...

  9. 深入理解C++中五种强制类型转换的使用场景

    深入理解C++中五种强制类型转换的使用场景 1.C风格的强制类型转换 2.C++风格的强制类型转换 2.1.static_cast 2.1.1.用于基本内置数据类型之间的转换 2.1.2.用于指针之间 ...

  10. 举例理解Hibernate的三种状态:瞬时态、持久态、托管(即游离态)态及互相转化

    举例理解Hibernate的三种状态 初学Hibernate,了解到Hibernate有三种状态:transient(瞬时状态),persistent(持久化状态)以及detached(游离状态). ...

最新文章

  1. 滚动时域控制 matlab,在 Simulink 中设计神经网络预测控制器
  2. kafka_2.11-0.10.2.1中的auto.offset.reset
  3. php改变iframe的src,js动态改变iframe的src属性
  4. ESXi 内存分配原理
  5. java面试要点---Spring体系知识点复习,IOC,AOP---随时更新
  6. idea java 快捷键_图示Javahtml5开发中IDEA的一些常用默认快捷键
  7. mysql多表查询练习_MySQL多表查询综合练习答案
  8. JavaScript 遗漏知识再整理;错误处理,类型转换以及获取当前时间、年份、月份、日期;...
  9. Debugging with GDB 用GDB调试多线程程序
  10. mysql安装包下载与检核
  11. 「企业架构」TOGAF的权威指南
  12. python 手机号码归属地 软件,Python查询手机号码归属地几种方法
  13. edp和edt哪个好_香水edt和edp是什么意思
  14. 未来科技蒲公英大飞_大烟草的下跌告诉我们关于大科技的未来
  15. C++ 全局变量 静态全局变量 傻傻分不清
  16. vscode下载和前端工程师常用的20+插件,包含代码提示、语法高亮、括号颜色等等
  17. GlusterFS探究(一): dht,afr,fuse, mgmt 层 几个问题总结
  18. 解决中文乱码问题:使用编码转换工具
  19. 【安卓逆向】 浦X银行签名校验,逆向工程师绝不认输
  20. PaaS平台如何实现简化应用开发和部署

热门文章

  1. Android Studio .jar 包的引用方式(导入方式)
  2. Android Studio connot resolve symbol XXX 无法解析的符号
  3. javascript 定时跳转页面
  4. 看到这个密码,我就笑了
  5. R连接Cassandra数据库的方法总结
  6. R语言可视化学习笔记之gganimate包
  7. Egret入门学习日记 --- 问题汇总
  8. putty-gns3
  9. Java 并发编程(二)对象的公布逸出和线程封闭
  10. inotify监控文件变化