web-dev-qa-db-ja.com

戻り値型のオーバーロードが許可されないのはなぜですか? (少なくとも通常使用される言語)

すべてのプログラミング言語について知っているわけではありませんが、通常、戻り値の型(引数の数と型が同じであると仮定)を考慮してメソッドをオーバーロードする可能性がサポートされていないことは明らかです。

私はこのようなものを意味します:

 int method1 (int num)
 {

 }
 long method1 (int num)
 {

 }

プログラミングにとって大きな問題ではありませんが、場合によっては歓迎することもあります。

明らかに、どのメソッドが呼び出されているかを区別する方法なしにそれらの言語がそれをサポートする方法はありませんが、そのための構文は[int] method1(num)または[long] method1(num)のような単純なものにすることができますこれにより、コンパイラーは、どちらが呼び出されるかを認識します。

コンパイラーがどのように機能するかはわかりませんが、それほど難しいことではないように思われるので、なぜそのようなものが通常は実装されないのでしょうか。

そのようなものがサポートされない理由はどれですか?

9
user2638180

型チェックが複雑になります。

引数タイプに基づくオーバーロードのみを許可し、イニシャライザからの変数タイプの推定のみを許可する場合、すべてのタイプ情報は一方向に流れます:構文ツリーの上方。

var x = f();

given      f   : () -> int  [upward]
given      ()  : ()         [upward]
therefore  f() : int        [upward]
therefore  x   : int        [upward]

タイプ情報がboth方向に移動することを許可する場合(その使用から変数のタイプを推定するなど)、以下を決定するために制約ソルバー(Hindley–Milnerタイプシステムの場合はAlgorithm Wなど)が必要です。タイプ。

var x = parse("123");
print_int(x);

given      parse        : string -> T  [upward]
given      "123"        : string       [upward]
therefore  parse("123") : ∃T           [upward]
therefore  x            : ∃T           [upward]
given      print_int    : int -> ()    [upward]
therefore  print_int(x) : ()           [upward]
therefore  int -> ()    = ∃T -> ()     [downward]
therefore  ∃T           = int          [downward]
therefore  x            : int          [downward]

ここでは、xの型を未解決の型変数∃Tのままにする必要がありました。ここでわかっているのは、それが解析可能であることだけです。後で、xが具象型で使用されたときに、制約を解決して、型情報を伝播する∃T = intを決定するのに十分な情報がありますdown構文ツリー呼び出し式からxへ。

xのタイプを判別できなかった場合、このコードもオーバーロードされる(つまり、呼び出し元がタイプを判別する)か、あいまいさに関するエラーを報告する必要があります。

これから、言語設計者は次のように結論付けることができます。

  • 実装が複雑になります。

  • これにより、タイプチェックが遅くなります。病理学的なケースでは、指数関数的に遅くなります。

  • 適切なエラーメッセージを生成することは困難です。

  • 現状とはあまりにも違います。

  • 実装したくありません。

19
Jon Purdy

あいまいなので。例としてC#を使用:

_var foo = method(42);
_

どのオーバーロードを使用する必要がありますか?

わかりました、おそらくそれは少し不公平でした。仮説言語でそれを伝えないと、コンパイラーはどのタイプを使用すべきかを理解できません。だから、暗黙の型付けはあなたの言語では不可能であり、匿名のメソッドとそれに伴うLinqがあります...

これはどう? (要点を示すためにシグネチャを少し再定義しました。)

_short method(int num) { ... }
int method(int num) { ... }

....

long foo = method(42);
_

intオーバーロードまたはshortオーバーロードを使用する必要がありますか?わからないので、[int] method1(num)構文で指定する必要があります。これは解析するのが少し面倒で、正直に言うとwriteです。

_long foo = [int] method(42);
_

実は、これは驚くほどC#のジェネリックメソッドに似た構文です。

_long foo = method<int>(42);
_

(C++とJavaには同様の機能があります。)

つまり、言語設計者は、解析を簡素化し、より強力な言語機能を有効にするために、別の方法で問題を解決することを選択しました。

あなたはコンパイラについてよく知らないと言っています。文法とパーサーについて学ぶことを強くお勧めします。文脈自由文法が何であるかを理解すると、あいまいさがなぜ悪いことであるかについて、はるかに良い考えが得られます。

4
RubberDuck

すべての言語機能は複雑さを増すため、すべての機能によって生じる不可避な問題、コーナーケース、およびユーザーの混乱を正当化するのに十分な利点を提供する必要があります。ほとんどの言語では、これは単にそれを正当化するのに十分な利点を提供しません。

ほとんどの言語では、式method1(2)が明確な型と多かれ少なかれ予測可能な戻り値を持つことを期待します。ただし、戻り値のオーバーロードを許可すると、その式の一般的な意味を、その周囲のコンテキストを考慮せずに判断することは不可能になります。実装がunsigned long long foo()で終わるreturn method1(2)メソッドがあるとどうなるか考えてみてください。それはlong- returningオーバーロードまたはint- returningオーバーロードを呼び出す必要がありますか、それとも単にコンパイラエラーを与える必要がありますか?

さらに、戻り値の型に注釈を付けることでコンパイラーを支援する必要がある場合は、構文をさらに作成するだけでなく(機能を存在させるために前述のすべてのコストが発生します)、作成と同じことを効率的に実行できます「通常の」言語での2つの異なる名前のメソッド。 [long] method1(2)long_method1(2)よりも直感的ですか?


一方、非常に強力な静的型システムを備えたHaskellのような一部の関数型言語では、この種の動作が可能です。型推論は、これらの言語で戻り値の型に注釈を付ける必要がほとんどないほど強力だからです。しかし、それが可能なのは、これらの言語がすべての関数が純粋で参照透過であることを要求するとともに、従来の言語よりも真にタイプセーフを実施するためです。これは、ほとんどのOOP言語で実現可能なものではありません。

0
Ixrec

これはisで利用可能ですSwiftで正常に動作します。明らかに、両側にあいまいな型を指定できないため、左側にあります。

これを シンプルなエンコード/デコードAPI で使用しました。

public protocol HierDecoder {
  func dech() throws -> String
  func dech() throws -> Int
  func dech() throws -> Bool

つまり、オブジェクトのinitなど、パラメーターの型がわかっている呼び出しは、非常に簡単に機能します。

    private static let typeCode = "ds"
    static func registerFactory() {
        HierCodableFactories.Register(key:typeCode) {
            (from) -> HierCodable in
            return try tgDrawStyle(strokeColor:from.dech(), fillColor:from.dechOpt(), lineWidth:from.dech(), glowWidth: from.dech())
        }
    }
    func typeKey() -> String { return tgDrawStyle.typeCode }
    func encode(to:HierEncoder) {
        to.ench(strokeColor)
        to.enchOpt(fillColor)
        to.ench(lineWidth)
        to.ench(glowWidth)
    }

細心の注意を払っている場合は、上記のdechOpt呼び出しに気付くでしょう。呼び出しコンテキストがオプションであることを期待できるため、微分器がオプションを返す同じ関数名をオーバーロードするとエラーが発生しやすくなるという難しい方法を見つけました。

0
Andy Dent