【问题标题】:Why doesn't ruby support method overloading?为什么 ruby​​ 不支持方法重载?
【发布时间】:2012-03-11 12:00:38
【问题描述】:

Ruby 不支持方法重载,而是覆盖现有方法。谁能解释一下为什么这种语言是这样设计的?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    “重载”是一个在 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 添加基于参数的调度的想法,但我永远无法弄清楚如何以向后兼容的方式来实现。

    【讨论】:

    • 这是正确的答案。接受的答案过于简单化了。 C# DOES 有命名参数和可选参数,并且仍然实现重载,所以它不像“def method(a, b = true) 不起作用,因此方法重载是不可能的”那么简单。它不是;这很难。然而,我发现这个答案非常有用。
    • @tandrewnichols:只是为了让大家了解一下 C# 中的“困难”重载解决方案是如何……可以将任何 3-SAT 问题编码为 C# 中的重载解决方案,并让编译器在编译时解决它,从而在 C# NP-hard 中进行重载解析(已知 3-SAT 是 NP-complete)。现在想象一下,不必在编译时对每个调用站点执行一次,而是在运行时对每个方法调用的每个方法调用执行一次。
    • @JörgWMittag 您能否在重载解决机制中包含一个显示 3-SAT 问题编码的链接?
    • 看起来“重载”并不是“基于静态参数的调度”的同义词。基于静态参数的调度只是最常见的重载实现。重载是一个与实现无关的术语,意思是“相同的方法名称,但在相同的范围内有不同的实现”。
    【解决方案2】:

    方法重载可以通过声明两个具有相同名称和不同签名的方法来实现。这些不同的签名可以是,

    1. 具有不同数据类型的参数,例如:method(int a, int b) vs method(String a, String b)
    2. 可变数量的参数,例如: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​​ 需要在方法查找链中维护一个具有唯一名称的方法。

    【讨论】:

    【解决方案3】:

    我想你正在寻找这样做的能力:

    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'
    

    希望有帮助

    【讨论】:

    • +1 提供了我们许多人只想知道的:如何在 Ruby 方法中使用可变数量的参数。
    • 此答案可能会受益于有关可选参数的其他信息。 (也许还有命名参数,现在这些都是一回事。)
    【解决方案4】:

    方法重载在具有静态类型的语言中很有意义,您可以在其中区分不同类型的参数

    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 强类型的。
    【解决方案5】:

    这并没有回答为什么 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),而不仅仅是类型。

    您会发现使用此功能会降低性能;您将不得不运行基准测试来决定您可以容忍多少。

    【讨论】:

    • 在具有任何类型 IO 的实际应用程序中,您只会有 0.1-10%(取决于哪种 IO)的减速。
    【解决方案6】:

    我经常做如下结构:

    def method(param)
        case param
        when String
             method_for_String(param)
        when Type1
             method_for_Type1(param)
    
        ...
    
        else
             #default implementation
        end
    end
    

    这允许对象的用户使用干净和清晰的方法名:方法 但是如果他想优化执行,他可以直接调用正确的方法。

    此外,它使您的测试更清晰、更好。

    【讨论】:

      【解决方案7】:

      对于问题的原因,已经有了很好的答案。但是,如果有人在寻找其他解决方案,请查看受 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!"   
      

      【讨论】:

        【解决方案8】:

        我对 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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-08-18
          • 1970-01-01
          • 1970-01-01
          • 2017-01-11
          • 1970-01-01
          • 2014-01-08
          相关资源
          最近更新 更多