Ruby 闭包:Block、Proc、lambda
Ruby 中的闭包非常强大,也非常重要,所以决定系统的学习下。
闭包(Closure),是指未绑定到任何对象的自由代码,闭包中的代码与任何对象和全局变量无关,只与执行此段代码的上下文相关。
Ruby 中闭包的实现有:Block,Proc,Lambada。在学习它们的区别的时候,网上讲的都不是很全面,突然记起 vincent 在以前的一篇开发周记中专门讲到这个主题,遂又仔细的通读了一遍,讲的非常详细全面,顿时就彻底弄懂了,于是也记录在这里,以便分享与以后查阅。
首先,我们来看 Block
arr = [1,2,3,4,5]
arr.each { |item| puts item }
这段代码,我们使用了 Array 对象的 block 方法,将 ary 中的每个元素打印出来。从例子中我们可以看到 block 使用起来很方便,想必传统的 Java 以及 C 编码,省略掉了冗余的循环,让你更加专注业务代码,这也是 ruby 的好处之一。
使用 block 的最大好处就是可以省略重复的冗余的无用的代码,我们再来看一个读文件的例子:
File.open(__FILE__) do |f|
puts f.readlines
end
对比用 Java 代码来读文件,每次都很反感那个冗长的 try-catch-finally。从上面的 ruby 代码中我么可以看到,ruby 语言给你做了处理,通过 block 你可以不用写无用的系统代码了。
从上面的例子我们可以看到,Ruby 中的 Block 对开发者来说,不用再写那些冗余无用的系统代码,而是更加专注业务代码,提升开发效率。
其次,我们再看 Proc
如果你经常使用 Block,你会发现一个问题:代码中会有很多重复的 Block,比如好多地方需要打印文件内容。怎么解决呢?你第一个想到的是写一个公共函数,不错,可以解决。但是记住你使用的是 ruby 语言,不要重新造轮子。Ruby 提供了函数化的 Block:Proc。
我们看一个简单的 Proc 例子:
p = Proc.new{|f| puts f.readlines}
File.open(__FILE__, &p)
上面例子可以看到,将 Block 代码定义为一个 Proc 对象,然后在使用 Block 的地方用 Proc 替换,这样就做到了完美的代码复用。
最后我们看看 lambda
Lambda:拉姆达表达式,ruby 中可以通过 lambda 表达式创建 Proc 对象,可以把它理解成一种匿名函数。我们先看例子:
new_p = lambda{|f| puts f.readlines}
File.open(__FILE__, &new_p)
上面例子中,我们使用系统的 lambda 方法创建了一个 Proc 对象,其效果与 Proc.new 创建出来的是一样的。其实使用 lambda 与使用 Proc.new 效果基本是一样的。下面就来讲下他们的区别:
yield 和 block call 的区别
yield 和 block call 两种都能实现运行闭包,从实际运行效果来说,区别不大。其区别主要在于:
1. 闭包的传递和保存
因为闭包已经传递到参数里,所以可以继续传递或保存起来,例如:
class A
def foo1(&b)
@proc = b
end
def foo2
@proc.call if @proc
end
end
a = A.new
a.foo1 { puts "proc from foo1" }
a.foo2
2. 性能
yield 不是方法调用,而是 Ruby 的关键字,yield 要比 block call 比快 1 倍左右。
block 和 proc、lambda 的区别
很简单直接,引入 proc 和 lambda 就是为了复用,避免重复定义,例如在上例中,使用 proc 变量存储闭包,避免重复定义两个 block 。
proc 和 lambda 的区别
这大概是最让人困惑的地方,从行为上看,他们的效果是一致的,为什么要用两种不同的表达方式。
proc = Proc.new { puts "foo in proc" }
lambda_proc = lambda { puts "foo in lambda" }
确实,对于简单的情况,他们的行为是一致的,但是主要在两个地方有明显差异:
1. 参数检查,lambdas 检查参数的个数,Procs 不会。
还是例子说话
def foo
x = 100
yield x
end
proc = Proc.new { |a, b| puts "a is #{a.inspect} b is #{b.inspect}" }
foo(&proc) #=> a is 100 b is nil
lambda_proc1 = lambda { |a| puts "a is #{a.inspect}" }
foo(&lambda_proc1) #=> a is 100
lambda_proc2 = lambda { |a, b| puts "a is #{a.inspect} b is #{b.inspect}" }
foo(&lambda_proc2) #=> ArgumentError: wrong number of arguments (1 for 2)
可见,proc 不会对参数进行个数匹配检查,而 lambda 会,如果不匹配还会报异常,所以安全起见,建议优先用 lambda。
2. return 不同。lambdas 的 return 是返回值给方法,方法会继续执行。 Proc 的 return 会终止方法并返回得到的值。
还是例子说话
def foo
f = Proc.new { return "return from foo from inside proc" }
f.call # control leaves foo here
return "return from foo"
end
def bar
f = lambda { return "return from lambda" }
puts f.call # control does not leave bar here
return "return from bar"
end
puts foo #=> return from foo from inside proc
puts bar #=> return from lambda
#=> return from bar
可见,proc 中,return 相当于执行了闭包环境里的 return,而 lambda 只是返回 call 闭包所在环境。
为什么会有这样的不同?
答案在于 procedures 和 methods 概念上的不同。Ruby 中的 Procs 是代码片段(code snippets),不是方法。因此,Proc 的 return 就是整个方法的 return。但 lambdas 就像是单独的 methods(只不过是匿名的),所以它要检查参数个数,且不会覆盖整个方法的返回。
因此,最好把 lambdas 当作另一种 methods 的写法,一种匿名的方式。
总结
闭包是 Ruby 的强大特性,它的几种表达方式 block,proc 和 lambda 有细微差别。blocks 和 Procs 看起来像在代码中插入代码片段。而 lambdas 和 Methods 看起来像方法。通过几个例子和比较,希望能了解如何灵活运用闭包,游刃有余!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Ruby Range 范围
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论