13

据我所知,Ruby 中本质上存在三种不同的闭包;方法、过程和 lambda。我知道它们之间存在差异,但我们难道不能只使用一种可以容纳所有可能用例的类型吗?

方法已经可以像 procs 和 lambdas 一样通过调用传递self.method(method_name),而我知道 procs 和 lambdas 之间的唯一显着区别是 lambdas 检查 arity 和 procs 在您尝试使用return. 那么我们不能将它们全部合并为一个并完成它吗?

4

2 回答 2

14

据我所知,Ruby 中本质上存在三种不同的闭包;方法、过程和 lambda。

不,有两个:方法不是闭包,只有 procs 和 lambdas 是。(或者至少可以,大多数都不是。)

有两种方法可以打包一段可执行代码以便在 Ruby 中重用:方法和块。严格来说,block 不是必须的,只用方法就可以了。但是块意味着在概念上、语义上和句法上都非常轻量级。对于方法而言,情况并非如此。

因为它们是轻量级且易于使用的,所以块的行为在某些方面与方法不同,例如参数如何绑定到参数。块参数的绑定更像是赋值的左侧,而不是方法参数。

例子:

将单个数组传递给多个参数:

def foo(a, b) end
foo([1, 2, 3]) # ArgumentError: wrong number of arguments (1 for 2)

a, b = [1, 2, 3]
# a == 1; b == 2

[[1, 2, 3]].each {|a, b| puts "a == #{a}; b == #{b}" }
# a == 1; b ==2

传递的参数少于参数:

def foo(a, b, c) end
foo(1, 2) # ArgumentError

a, b, c = 1, 2
# a == 1; b == 2; c == nil

[[1, 2]].each {|a, b, c| puts "a == #{a}; b == #{b}; c == #{c}" }
# a == 1; b == 2; c == 

传递的参数多于参数:

def foo(a, b) end
foo(1, 2, 3) # ArgumentError: wrong number of arguments (3 for 2)

a, b = 1, 2, 3
# a == 1; b == 2

[[1, 2, 3]].each {|a, b| puts "a == #{a}; b == #{b}" }
# a == 1; b == 2

[顺便说一句:上面的块都不是闭包。]

例如,这允许Enumerable始终为块产生单个元素的协议与Hashes 一起使用:您只需将单个元素设置为 anArray[key, value]依赖于块的隐式数组解构:

{one: 1, two: 2}.each {|k, v| puts "#{key} is assigned to #{value}" }

比您必须编写的内容更容易理解:

{one: 1, two: 2}.each {|el| puts "#{el.first} is assigned to #{el.last}" }

块和方法之间的另一个区别是方法使用return关键字返回值,而块使用next关键字。

如果您同意在语言中同时拥有方法和块是有意义的,那么接受 proc 和 lambda 的存在只是一小步,因为它们的行为分别类似于块和方法:

  • 来自封闭方法的procs return(就像块一样),它们绑定参数就像块一样
  • 来自自身的lambdas return(就像方法一样)并且它们绑定参数与方法完全一样。

IOW:proc/lambda 二分法只是反映了块/方法二分法。

请注意,实际上还有很多情况需要考虑。例如,是什么self意思?是不是意味着

  • self块被写入时的任何内容
  • self块运行时的任何内容
  • 块本身

那么return呢?是不是意味着

  • 从写入块的方法返回
  • 从运行块的方法返回
  • 从块本身返回?

已经为您提供了九种可能性,即使没有考虑到参数绑定的特定于 Ruby 的特性。

现在,出于封装的原因,上面的#2 确实是个坏主意,所以这在一定程度上减少了我们的选择。

与往常一样,这是语言设计者的品味问题。Ruby 中还有其他类似的冗余:为什么需要实例变量和局部变量?如果词法范围是对象,那么局部变量将只是词法范围的实例变量,您不需要局部变量。为什么你需要实例变量和方法?其中一个就足够了:一对 getter/setter 方法可以替换实例变量(有关这种语言的示例,请参阅 Newspeak),分配给实例变量的一流过程可以替换方法(请参阅 Self、Python、JavaScript)。为什么你需要类和模块?如果您允许混入类,那么您可以摆脱模块并将类用作类和混入。为什么你需要 mixins?如果一切都是方法调用,无论如何,类都会自动成为 mixins(同样,请参见 Newspeak 的示例)。当然,如果您允许在对象之间直接继承,则根本不需要类(参见 Self、Io、Ioke、Seph、JavaScript)

于 2013-08-09T14:27:51.310 回答
3

一些很好的解释http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/但我猜你想要更深入的哲学解释......

我相信“但我们不能仅仅使用一种可以容纳所有可能用例的类型吗?”的答案是您可以只使用一种。

它们存在的原因是 ruby​​ 试图使用来自函数式和面向对象范式的表达式尽可能提高开发人员的生产力,这使得不同类型的闭包成为“语法糖”。

于 2013-08-09T13:05:10.323 回答