Ruby 中 Block、Proc 和 Lambda 的区别 (2013)
Ruby 中 Block、Proc 和 Lambda 的区别是什么?
2013年8月5日
Block、Proc 和 Lambda 是什么?
程序员的说法: Ruby 中 closures 的例子。 通俗易懂的说法: 对我们想要运行的代码进行分组的方式。
# Block 示例
[1,2,3].each { |x| puts x*2 } # block 在花括号之间
[1,2,3].each do |x|
puts x*2 # block 是 do 和 end 之间的所有内容
end
# Proc 示例
p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p) # '&' 告诉 Ruby 将 proc 转换为 block
proc = Proc.new { puts "Hello World" }
proc.call # Proc 对象的代码体在被调用时执行
# Lambda 示例
lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)
lam = lambda { puts "Hello World" }
lam.call
虽然它们看起来非常相似,但存在一些细微的差异,我将在下面介绍。
Block 和 Proc 之间的区别
1. Proc 是对象,而 Block 不是
Proc (注意小写的 p) 是 Proc
类的一个实例。
p = Proc.new { puts "Hello World" }
这使我们可以在它上面调用方法并将其分配给变量。 Proc 也可以返回自身。
p.call # 打印 'Hello World'
p.class # 返回 'Proc'
a = p # a 现在等于 p,一个 Proc 实例
p # 返回一个 proc 对象 '#<Proc:0x007f96b1a60eb0@(irb):46>'
相比之下,block 只是方法调用 语法 的一部分。它本身没有任何意义,只能出现在参数列表中。
{ puts "Hello World"} # 语法错误
a = { puts "Hello World"} # 语法错误
[1,2,3].each {|x| puts x*2} # 仅作为方法调用语法的一部分有效
2. 参数列表中最多只能出现一个 Block
相反,您可以将多个 Proc 传递给方法。
def multiple_procs(proc1, proc2)
proc1.call
proc2.call
end
a = Proc.new { puts "First proc" }
b = Proc.new { puts "Second proc" }
multiple_procs(a,b)
Proc 和 Lambda 之间的区别
在深入探讨 Proc 和 Lambda 之间的区别之前,重要的是要提到它们都是 Proc
对象。
proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }
proc.class # 返回 'Proc'
lam.class # 返回 'Proc'
但是,Lambda 是 Proc 的一种不同的“风格”。当返回对象时,这种细微的差异会显示出来。
proc # 返回 '#<Proc:0x007f96b1032d30@(irb):75>'
lam # 返回 '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'
(lambda)
符号提醒我们,虽然 Proc 和 Lambda 非常相似,甚至是 Proc
类的实例,但它们也略有不同。 以下是主要区别。
1. Lambda 检查参数的数量,而 Proc 不检查
lam = lambda { |x| puts x } # 创建一个接受 1 个参数的 lambda
lam.call(2) # 打印 2
lam.call # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3) # ArgumentError: wrong number of arguments (3 for 1)
相比之下,如果传递了错误的参数数量,Proc 并不在意。
proc = Proc.new { |x| puts x } # 创建一个接受 1 个参数的 proc
proc.call(2) # 打印 2
proc.call # 返回 nil
proc.call(1,2,3) # 打印 1 并忽略额外的参数
如上所示,如果传递了错误的参数数量,Proc 不会出错并引发错误。 如果 proc 需要一个参数但没有传递参数,则 proc 返回 nil
。 如果传递了太多的参数,则它会忽略额外的参数。
2. Lambda 和 Proc 对 'return' 关键字的处理方式不同
Lambda 中的 'return' 触发 lambda 代码之外的代码
def lambda_test
lam = lambda { return }
lam.call
puts "Hello world"
end
lambda_test # 调用 lambda_test 打印 'Hello World'
Proc 中的 'return' 触发执行 Proc 的方法之外的代码
def proc_test
proc = Proc.new { return }
proc.call
puts "Hello world"
end
proc_test # 调用 proc_test 不打印任何内容
那么什么是 Closure?
程序员的说法: '一个函数或对一个函数的引用,以及一个引用环境。 与普通函数不同,即使在词法作用域之外调用函数,Closure 允许函数访问非本地变量。' - Wikipedia
通俗易懂的说法: 类似于手提箱,它是一组代码,当打开它时(即调用),包含打包它时(即创建它时)其中的任何东西。
# Proc 对象保留本地上下文的示例
def counter
n = 0
return Proc.new { n+= 1 }
end
a = counter
a.call # 返回 1
a.call # 返回 2
b = counter
b.call # 返回 1
a.call # 返回 3
背景知识 第 1 部分:Lambda 演算和匿名函数
Lambda 的名称来自 1930 年代引入的一种演算,旨在帮助研究数学的基础。 Lambda 演算通过简化其语义来帮助使可计算函数更易于研究。 其中最相关的简化是它以“匿名”方式处理函数,这意味着没有为函数提供显式名称。
sqsum(x,y) = x*x + y*y #<-- 普通函数
(x,y) -> x*x + y*y #<-- 匿名函数
一般来说,在编程中,术语 lambda 指的是匿名函数。 这些匿名函数在某些语言(即 JavaScript)中非常常见且明确,而在其他语言(即 Ruby)中则隐式。
背景知识 第 2 部分:Proc 名称的由来
Proc 是 procedure(过程)的缩写,procedure 是一组打包在一起的指令,用于执行特定任务。 在不同的语言中,这些指令可能被称为函数、例程、方法或通用术语“可调用单元”。 它们通常被设计为多次调用和从程序中的多个位置调用。
总结差异
- Proc 是对象,而 Block 不是
- 参数列表中最多只能出现一个 Block
- Lambda 检查参数的数量,而 Proc 不检查
- Lambda 和 Proc 对 'return' 关键字的处理方式不同