Rubyがメソッドのオーバーロードをサポートしないのはなぜですか?


146

Rubyはメソッドのオーバーロードをサポートする代わりに、既存のメソッドを上書きします。言語がこのように設計された理由を誰かが説明できますか?

回答:


166

メソッドのオーバーロードは、名前が同じでシグネチャが異なる2つのメソッドを宣言することで実現できます。これらの異なる署名は、

  1. 異なるデータ型の引数。例: method(int a, int b) vs method(String a, String b)
  2. 可変数の引数。例: method(a) vs method(a, b)

ruby(動的型付き言語)にはデータ型宣言がないため、最初の方法ではメソッドのオーバーロードを実現できません。したがって、上記のメソッドを定義する唯一の方法はdef(a,b)

2番目のオプションでは、メソッドのオーバーロードを実現できるように見えるかもしれませんが、できません。引数の数が異なる2つのメソッドがあるとします。

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つのメソッドを一意の名前で維持する必要があります。


22
@JörgW Mittagの答えは、はるか下に埋もれており、間違いなく読む価値があります。
user2398029

1
さらに、@ Derek Ekinsの回答は、さらに埋められて、代替案を提供します
Cyril Duchon-Doris

将来のルビイストへの注意...契約gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDaveで

214

「オーバーロード」は、Rubyでは意味をなさない用語です。これは、基本的には、「静的引数に基づく派遣」の同義語ですが、Rubyはありません持っている静的なディスパッチをまったく。つまり、Rubyが引数に基づく静的ディスパッチをサポートしないのは、静的ディスパッチ(ピリオド)をサポートしないためです。引数ベースであろうとなかろうと、それはいかなる種類の静的ディスパッチもサポートしません。

さて、実際にオーバーロードについて具体的に尋ねているのではなく動的な引数ベースのディスパッチについて多分尋ねている場合、答えは次のとおりです。Matzが実装していないためです。他の誰もそれを提案することに悩まなかったので。他の誰もそれを実装することに悩まなかったからです。

オプションの引数と可変長引数リストを持つ言語で一般的な、ダイナミックな引数に基づく派遣では、ある非常に権利を取得するのは難しい、とさえ難しく理解し、それを維持します。静的な引数ベースのディスパッチがあり、オプションの引数を持たない言語(Javaなど)であっても、どの過負荷が選択されるのか単なる人間に伝えることはほとんど不可能です。

C#では、あなたが実際にエンコードすることができる任意の C#でのオーバーロードの解決はNP困難であることを意味オーバーロードの解決へ3-SAT問題を。

次に、動的ディスパッチでそれを試してみてください。頭に留めておく時間ディメンションが追加されています。

「非表示」のゼロ番目のself引数でのみディスパッチするオブジェクト指向言語とは対照的に、プロシージャのすべての引数に基づいて動的にディスパッチする言語があります。たとえば、Common Lispは、動的な型、さらにはすべての引数の動的な値をディスパッチします。Clojureは、すべての引数の任意の関数をディスパッチします(BTWは非常にクールで非常に強力です)。

しかし、動的な引数ベースのディスパッチを備えたオブジェクト指向言語については知りません。マーティン・オーダーズキーは、彼がいることを言ったかもしれないけど、スカラ座への引数ベースのディスパッチを追加することを検討してのみ、彼は同時に過負荷を取り除くことができればの両方の用途が過負荷状態にし、Javaと互換性のあることをScalaのコードを既存との下位互換性がある(彼は特にSwingとAWTを述べましたJavaのかなり複雑なオーバーロードルールのほとんどすべての厄介な暗い隅のケースを実行するいくつかの非常に複雑なトリックを実行します)。私は引数ベースのディスパッチをRubyに追加することについて自分自身でいくつかのアイデアを持っていますが、下位互換性のある方法でそれを行う方法を理解することはできませんでした。


5
これが正解です。受け入れられた答えは単純化しすぎです。C#には名前付きパラメーターとオプションのパラメーターがあり、オーバーロードを実装しているため、「機能しdef method(a, b = true)ないため、メソッドのオーバーロードは不可能です」ほど単純ではありません。そうではありません; それはただ難しいです。しかし、私はこの答えが本当に有益であることを発見しました。
tandrewnichols 2013

1
@tandrewnichols:C#での "難しい"オーバーロード解決がどのように行われるかについていくつかのアイデアを与えるために... C#でのオーバーロード解決として3-SAT問題をエンコードし、コンパイラーにコンパイル時に解決させて、C#NPでオーバーロード解決を行うことができます。 -hard(3-SATはNP完全であることが知られています)。今度は、コンパイル時に呼び出しサイトごとに1回ではなく、実行時のすべてのメソッド呼び出しごとにメソッド呼び出しごとに1回実行する必要があることを想像してください。
イェルクWミッターク

1
@JörgWMittag過負荷解決メカニズムに3-SAT問題のエンコーディングを示すリンクを含めてもらえますか?
2015


2
「オーバーロード」は「静的引数ベースのディスパッチ」の同義語ではないようです。静的な引数ベースのディスパッチは、単にオーバーロードの最も一般的な実装です。オーバーロードとは、「メソッド名は同じだが、同じスコープ内での実装が異なる」ことを意味する、実装に依存しない用語です。
2017年

85

私はあなたがこれを行う能力を探していると思います:

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'

それが役に立てば幸い


15
私たちの多くが知りたいことを提供するための+1:Rubyメソッドで可変数の引数を処理する方法。
ashes999 2014

3
この回答は、オプションの引数に関する追加情報から利益を得る可能性があります。(そして、おそらく名前付きの引数も、今ではそれらが物事です。)
Ajedi32 '12

9

メソッドのオーバーロードは、静的型付けを備えた言語で意味があり、異なるタイプの引数を区別できます。

f(1)
f('foo')
f(true)

だけでなく、引数の数の間

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

最初の区別はルビーには存在しません。Rubyは動的型付けまたは「ダック型付け」を使用します。2番目の違いは、デフォルトの引数または引数を操作することで処理できます。

def f(n, s = 'foo', flux_compensator = true)
   ...
end


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

これは強い型付けとは何の関係もありません。結局のところ、Ruby 強く型付けされています。
イェルクWミッターク

8

これは、なぜ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、単に型ではなく、一致する値(など)を指定できるためです。

ただし、これを使用するとパフォーマンスが低下します。許容範囲を決定するには、ベンチマークを実行する必要があります。


1
あらゆる種類のIOを使用する実際のアプリケーションでは、0.1〜10%(IOの種類によって異なります)のスローダウンしかありません。
Waterlink

1

私はしばしば次の構造をします:

def method(param)
    case param
    when String
         method_for_String(param)
    when Type1
         method_for_Type1(param)

    ...

    else
         #default implementation
    end
end

これにより、オブジェクトのユーザーはクリーンでクリアなmethod_name:methodを使用できますが、実行を最適化したい場合は、正しいメソッドを直接呼び出すことができます。

また、テストがより明確で優れたものになります。


1

質問の理由については、すでにすばらしい答えがあります。ただし、他のソリューションを探している人がElixir パターンマッチング機能に触発されたfunction-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!"   
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.