为什么Ruby不支持方法重载?

Why doesn't ruby support method overloading?

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


"重载"是一个在Ruby中根本没有意义的术语。它基本上是"基于静态参数的分派"的同义词,但Ruby根本没有静态分派。所以,Ruby不支持基于参数的静态调度的原因是它不支持静态调度period。它不支持任何类型的静态分派,无论是基于参数还是其他类型。

现在,如果您实际上不是特别询问重载,而是关于基于动态参数的调度,那么答案是:因为Matz没有实现它。因为没人愿意提出。因为没有其他人费心去实现它。

一般来说,在具有可选参数和可变长度参数列表的语言中,基于动态参数的调度很难得到正确的结果,甚至很难让人理解。即使在具有静态参数的调度和没有可选参数(例如Java)的语言中,有时几乎不可能分辨出一个凡人,超载将被挑选出来。

在C中,您实际上可以将任何3-SAT问题编码为过载分辨率,这意味着C中的过载分辨率是NP困难的。

现在,尝试使用动态调度,在那里您有额外的时间维来保存在您的头脑中。

有些语言是基于过程的所有参数动态调度的,而不是面向对象的语言,它们只在"隐藏"的第0个self参数上调度。例如,公共lisp会发送所有参数的动态类型甚至动态值。clojure发送所有参数的任意函数(btw是非常酷和非常强大的)。

但是我不知道任何一种基于动态参数调度的OO语言。Martin Odersky说,他可能会考虑向斯卡拉添加基于参数的调度,但前提是他可以同时删除重载并向后兼容现有的Scala代码,该代码使用超载和兼容Java(他特别提到Swing和AWT),它执行一些非常复杂的技巧,执行相当多的EV。JAVA的相当复杂的重载规则。对于向Ruby添加基于参数的分派,我自己也有一些想法,但是我从来没有想过如何以向后兼容的方式来实现它。


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

  • 具有不同数据类型的参数,例如:method(int a, int b) vs method(String a, String b)
  • 可变参数数,如:method(a) vs method(a, b)
  • 我们无法使用第一种方法实现方法重载,因为Ruby(动态类型语言)中没有数据类型声明。因此,定义上述方法的唯一方法是def(a,b)

    使用第二个选项,看起来我们可以实现方法重载,但是我们不能实现。假设我有两个方法具有不同数量的参数,

    1
    2
    3
    4
    5
    6
    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需要在方法查找链中维护一个具有唯一名称的方法。


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

    1
    2
    3
    4
    5
    6
    7
    def my_method(arg1)
    ..
    end

    def my_method(arg1, arg2)
    ..
    end

    Ruby以不同的方式支持这一点:

    1
    2
    3
    4
    5
    6
    7
    def my_method(*args)
      if args.length == 1
        #method 1
      else
        #method 2
      end
    end

    一个常见的模式也是以散列形式传入选项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
    2
    3
    f(1)
    f('foo')
    f(true)

    以及不同数量的参数之间

    1
    2
    3
    f(1)
    f(1, 'foo')
    f(1, 'foo', true)

    Ruby中不存在第一个区别。Ruby使用动态类型或"duck-typing"。第二个区别可以通过默认参数或使用参数来处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def f(n, s = 'foo', flux_compensator = true)
       ...
    end


    def f(*args)
      case args.size
      when  
         ...
      when 2
        ...
      when 3
        ...
      end
    end


    这并不能回答Ruby为什么没有方法重载的问题,但是第三方库可以提供。

    Ruby库允许重载。根据本教程改编的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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的重载更强大,因为您可以指定匹配的值(例如EDCOX1(0)),而不只是类型。

    不过,使用它您会看到性能下降;您必须运行基准来决定您能容忍的程度。


    我经常做以下结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def method(param)
        case param
        when String
             method_for_String(param)
        when Type1
             method_for_Type1(param)

        ...

        else
             #default implementation
        end
    end

    这允许对象的用户使用clean和clear方法u name:method但是如果他想优化执行,他可以直接调用正确的方法。

    而且,它使您的测试更清晰和更好。


    在问题的"为什么"方面已经有了很好的答案。但是,如果有人想寻找其他解决方案,请检查受elixir模式匹配功能启发的功能性RubyGem。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
         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!"