【发布时间】:2012-03-11 12:00:38
【问题描述】:
Ruby 不支持方法重载,而是覆盖现有方法。谁能解释一下为什么这种语言是这样设计的?
【问题讨论】:
标签: ruby
Ruby 不支持方法重载,而是覆盖现有方法。谁能解释一下为什么这种语言是这样设计的?
【问题讨论】:
标签: ruby
“重载”是一个在 Ruby 中根本没有意义的术语。它基本上是“基于参数的静态调度”的同义词,但 Ruby 没有静态调度根本。所以,Ruby 之所以不支持基于参数的静态分派,是因为它不支持静态分派,句号。它不支持任何类型的静态调度,无论是基于参数的还是其他的。
现在,如果您实际上不是专门询问重载,而可能是动态基于参数的调度,那么答案是:因为 Matz 没有实现它.因为没有其他人愿意提出它。因为没有其他人费心去实现它。
一般来说,在具有可选参数和可变长度参数列表的语言中,基于动态参数的调度非常难以正确,甚至更难保持可以理解。即使在具有基于 static 参数的分派且没有可选参数的语言中(例如 Java),有时也几乎无法判断一个普通人,哪个重载是将被选中。
在 C# 中,您实际上可以将 任何 3-SAT 问题编码为重载解决方案,这意味着 C# 中的重载解决方案是 NP-hard。
现在尝试使用 dynamic 调度,您可以在其中保留额外的时间维度。
有些语言可以根据过程的所有参数动态分派,而面向对象的语言只在“隐藏的”第零个self 参数上分派。例如,Common Lisp 调度动态类型,甚至所有参数的动态值。 Clojure 调度所有参数的任意函数(顺便说一句,这非常酷且非常强大)。
但我不知道任何具有基于动态参数的调度的 OO 语言。 Martin Odersky 说他可能考虑将基于参数的调度添加到 Scala,但只有如果他可以同时移除重载并且向后- 与使用重载的现有 Scala 代码兼容并与 Java 兼容(他特别提到了 Swing 和 AWT,它们发挥了一些极其复杂的技巧,几乎可以执行 Java 相当复杂的重载规则的每一个令人讨厌的黑暗角落案例)。我自己有一些关于向 Ruby 添加基于参数的调度的想法,但我永远无法弄清楚如何以向后兼容的方式来实现。
【讨论】:
def method(a, b = true) 不起作用,因此方法重载是不可能的”那么简单。它不是;这很难。然而,我发现这个答案非常有用。
方法重载可以通过声明两个具有相同名称和不同签名的方法来实现。这些不同的签名可以是,
method(int a, int b) vs method(String a, String b)
method(a) vs method(a, b)
我们无法使用第一种方式实现方法重载,因为ruby(动态类型语言)中没有数据类型声明。所以定义上述方法的唯一方法是def(a,b)
使用第二个选项,看起来我们可以实现方法重载,但我们不能。假设我有两个参数数量不同的方法,
def method(a); end;
def method(a, b = true); end; # second argument has a default value
method(10)
# Now the method call can match the first one as well as the second one,
# so here is the problem.
所以 ruby 需要在方法查找链中维护一个具有唯一名称的方法。
【讨论】:
我想你正在寻找这样做的能力:
def my_method(arg1)
..
end
def my_method(arg1, arg2)
..
end
Ruby 以不同的方式支持这一点:
def my_method(*args)
if args.length == 1
#method 1
else
#method 2
end
end
一个常见的模式也是将选项作为哈希传递:
def my_method(options)
if options[:arg1] and options[:arg2]
#method 2
elsif options[:arg1]
#method 1
end
end
my_method arg1: 'hello', arg2: 'world'
希望有帮助
【讨论】:
方法重载在具有静态类型的语言中很有意义,您可以在其中区分不同类型的参数
f(1)
f('foo')
f(true)
以及不同数量的参数之间
f(1)
f(1, 'foo')
f(1, 'foo', true)
第一个区别在 ruby 中不存在。 Ruby 使用动态类型或“鸭子类型”。第二个区别可以通过默认参数或使用参数来处理:
def f(n, s = 'foo', flux_compensator = true)
...
end
def f(*args)
case args.size
when
...
when 2
...
when 3
...
end
end
【讨论】:
这并没有回答为什么 ruby 没有方法重载的问题,但是第三方库可以提供。
contracts.ruby 库允许重载。改编自教程的示例:
class Factorial
include Contracts
Contract 1 => 1
def fact(x)
x
end
Contract Num => Num
def fact(x)
x * fact(x - 1)
end
end
# try it out
Factorial.new.fact(5) # => 120
请注意,这实际上比 Java 的重载更强大,因为您可以指定要匹配的值(例如 1),而不仅仅是类型。
您会发现使用此功能会降低性能;您将不得不运行基准测试来决定您可以容忍多少。
【讨论】:
我经常做如下结构:
def method(param)
case param
when String
method_for_String(param)
when Type1
method_for_Type1(param)
...
else
#default implementation
end
end
这允许对象的用户使用干净和清晰的方法名:方法 但是如果他想优化执行,他可以直接调用正确的方法。
此外,它使您的测试更清晰、更好。
【讨论】:
对于问题的原因,已经有了很好的答案。但是,如果有人在寻找其他解决方案,请查看受 Elixir pattern matching 功能启发的 functional-ruby gem。
class Foo
include Functional::PatternMatching
## Constructor Over loading
defn(:initialize) { @name = 'baz' }
defn(:initialize, _) {|name| @name = name.to_s }
## Method Overloading
defn(:greet, :male) {
puts "Hello, sir!"
}
defn(:greet, :female) {
puts "Hello, ma'am!"
}
end
foo = Foo.new or Foo.new('Bar')
foo.greet(:male) => "Hello, sir!"
foo.greet(:female) => "Hello, ma'am!"
【讨论】:
我对 Ruby 的创造者 Yukihiro Matsumoto(又名“Matz”)进行了一次精彩的采访。顺便说一句,他在那里解释了他的推理和意图。这是对@nkm 对问题的出色示例的一个很好的补充。我已经强调了回答你关于为什么 Ruby 是这样设计的问题的部分:
正交与和谐
Bill Venners:Dave Thomas 还声称 如果我要求你添加一个 正交的特征,你不会做。你想要的是 和谐的东西。这是什么意思?
Yukihiro Matsumoto:我相信一致性和正交性是工具 设计,而不是设计的主要目标。
Bill Venners:正交性在这种情况下意味着什么?
Yukihiro Matsumoto:正交性的一个例子是允许任何 小功能或语法的组合。 例如,C++ 支持 函数的默认参数值和重载 基于参数的函数名称。 两者都是很好的特性 一种语言,但由于它们是正交的,因此您可以同时应用 同时。编译器知道如何同时应用两者。如果 这是模棱两可的,编译器会标记一个错误。但如果我看 代码,我也需要用我的大脑来应用规则。我需要猜测如何 编译器工作。如果我是对的,而且我足够聪明,那就不是 问题。但如果我不够聪明,而且我真的不够聪明,它会导致 混乱。结果对于普通人来说将是出乎意料的。 这个 是正交性有多糟糕的一个例子。
资料来源:《Ruby 的哲学》,与松本幸弘的对话,第一部分 作者:Bill Venners,2003 年 9 月 29 日:https://www.artima.com/intv/ruby.html
【讨论】: