メソッドは構文糖衣以上のものをオーバーロードしていますか?[閉まっている]


19

メソッドは多型の型をオーバーロードしていますか?私には、同じ名前と異なるパラメーターを持つメソッドを単に区別しているように思えます。だから、stuff(Thing t)およびstuff(Thing t, int n)これまでのコンパイラとランタイムが懸念している限り全く異なる方法です。

呼び出し側では、異なる種類のオブジェクトで異なる動作をする同じメソッドであるという幻想を作成します-多態性。しかし、それは幻想に過ぎません。実際stuff(Thing t)stuff(Thing t, int n)まったく異なる方法だからです。

メソッドは構文糖衣以上のものをオーバーロードしていますか?何か不足していますか?


構文糖の一般的な定義は、純粋にローカルであるということです。コードの一部をその「甘くされた」同等物に変更する、またはその逆の意味は、プログラムの全体的な構造に影響を与えないローカルな変更を伴います。そして、メソッドのオーバーロードはこの基準に正確に適合すると思います。例を見てみましょう:

クラスを考えてみましょう:

class Reader {
    public String read(Book b){
        // .. translate the book to text
    }
    public String read(File b){
        // .. translate the file to text
    }
}

次に、このクラスを使用する別のクラスを考えます。

/* might not be the best example */
class FileProcessor {
    Reader reader = new Reader();
    public void process(File file){
        String text = reader.read(file);
        // .. do stuff with the text
    }
}

はい。メソッドのオーバーロードを通常のメソッドに置き換える場合に変更する必要があるものを見てみましょう。

readメソッドReaderに変更readBook(Book)readFile(file)。名前を変更するだけです。

の呼び出しコードはFileProcessorわずかにreader.read(file)変更されreader.readFile(file)ます。

以上です。

ご覧のとおり、メソッドのオーバーロードを使用することと使用しないことの違いは、純粋にローカルです。そして、それが純粋な構文糖として適格だと思う理由です。

何かあれば、あなたの異議を聞きたいです。たぶん私は何かを逃しています。


48
結局、プログラミング言語の機能は、生のアセンブラーの構文糖衣にすぎません。
フィリップ14

31
@フィリップ:申し訳ありませんが、それは本当に愚かな声明です。プログラミング言語は、構文ではなくセマンティクスから有用性を引き出します。型システムのような機能は、実際にもっと書く必要がある場合でも、実際の保証を与えます。
back2dos 14

3
これを自問してください:演算子のオーバーロードは単なる構文糖ですか?あなたが真実と
考える

5
@ back2dos:完全に同意します。「すべてはアセンブラーの単なる構文糖」という文を頻繁に読んでいますが、明らかに間違っています。構文糖は、新しいセマンティクスを追加しない既存の構文の代替(おそらくより優れた)構文です。
ジョルジオ14

6
@Giorgio:そうです!Matthias Felleisenの表現力に関する画期的な論文には、正確な定義があります。基本的に:構文糖は純粋にローカルです。言語機能の使用を削除するためにプログラムのグローバル構造を変更する必要がある場合、それは構文上の砂糖ではありません。すなわち、アセンブラーで多態的なOOコードを書き換えるには、通常、純粋にローカルではないグローバルディスパッチロジックを追加する必要があります。したがって、OOは「アセンブラーの単なる構文糖」ではありません
ヨルグWミットタグ14

回答:


29

これに答えるには、最初に「構文糖」の定義が必要です。ウィキペディアのものに行きます:

コンピューターサイエンスでは、構文シュガーは、読みやすく、表現しやすいように設計されたプログラミング言語内の構文です。これにより、人間が使用する言語が「甘く」なります。物事をより明確に、より簡潔に、または一部の人が好む代替スタイルで表現できます。

[...]

具体的には、言語の構成は、言語の機能に影響を与えずに言語から削除できる場合、構文糖と呼ばれます。

したがって、この定義の下では、Javaの可変引数やScalaのfor-comprehensionなどの機能は構文上の砂糖です。これらは基礎となる言語機能(最初の場合は配列、2番目の場合はmap / flatmap / filterの呼び出し)に変換され、削除されます言語でできることを変えないでください。

ただし、メソッドのオーバーロードは、この定義では構文上のシュガーではありません。これを削除すると、基本的に言語が変更されるためです(引数に基づいて個別の動作にディスパッチできなくなります)。

確かに、メソッドの引数にアクセスする方法がある限り、メソッドのオーバーロードをシミュレートでき、与えられた引数に基づいて「if」構成を使用できます。しかし、その構文糖を考慮すると、チューリングマシンより上のすべてのものも同様に構文糖であると考える必要があります。


22
オーバーロードを削除しても、言語でできることは変わりません。以前とまったく同じことを行うことができます。いくつかのメソッドの名前を変更するだけです。これは、ループを脱糖するよりも些細な変更です。
ドーバル14

9
先ほど言ったように、すべての言語(機械語を含む)は単にチューリングマシンの上の構文糖衣であるというアプローチを取ることができます。
kdgregory 14

9
私が見るように、メソッドのオーバーロードは、単にsum(numbersArray)andのsum(numbersList)代わりにsumArray(numbersArray)and を実行させますsumList(numbersList)。私はドーバルに同意します、それは単なる合成糖のようです。
アビブコーン14

3
ほとんどの言語。実施試みるinstanceof用いて、クラス、継承、インターフェイス、ジェネリック、反射又はアクセス指定子をifwhileおよびブール演算子とまったく同じセマンティクス。コーナーケースはありません。これらのコンストラクトの特定の使用法と同じことを計算することに挑戦しているわけではないことに注意してください。ブールロジックと分岐/ループを使用して、あらゆるものを計算できることは既に知っています。これらの言語機能のセマンティクスの完全なコピーを、それらが提供する静的な保証を含めて実装するようお願いしています(コンパイル時のチェックはコンパイル時にも行われなければなりません。)
Doval 14

6
@ Doval、kdgregory:構文糖を定義するには、いくつかのセマンティクスに関連して定義する必要があります。あなたが持っている唯一のセマンティクスが「このプログラムが何を計算するのか?」である場合、すべてがチューリングマシンの単なる構文上の砂糖であることは明らかです。一方、オブジェクトとオブジェクトに対する特定の操作について話すことができるセマンティクスがある場合、特定の構文を削除すると、言語がチューリング完全であっても、それらの操作を表現できなくなります。
ジョルジオ14

13

通常、構文糖という用語は、機能が置換によって定義される場合を指します。この言語は、機能が何をするかを定義するのではなく、他の機能とまったく同じであることを定義します。たとえば、for-eachループ

for(Object alpha: alphas) {
}

になる:

for(Iterator<Object> iter = alpha.iterator(); iter.hasNext()) {
   alpha = iter.next();
}

または、可変引数を持つ関数を使用します。

void foo(int... args);

foo(3, 4, 5);

どちらになる:

void Foo(int[] args);

foo(new int[]{3, 4, 5});

そのため、他の機能の観点から機能を実装するための構文の些細な置換があります。

メソッドのオーバーロードを見てみましょう。

void foo(int a);
void foo(double b);

foo(4.5);

これは次のように書き換えることができます。

void foo_int(int a);
void foo_double(double b);

foo_double(4.5);

しかし、それは同等ではありません。Javaのモデル内では、これは別のものです。作成foo(int a)されるfoo_int関数を実装しません。Javaは、あいまいな関数に面白い名前を付けることでメソッドのオーバーロードを実装しません。構文糖としてカウントするために、javaは実際に書いfoo_intfoo_double機能するふりをする必要がありますが、そうではありません。


2
シンタックスシュガーの変換は些細なことだと言った人はいないと思います。たとえそれが行われたとしてもBut, the transformation isn't trivial. At the least, you have to determine the types of the parameters.、型を決定する必要がないため、私は非常に大ざっぱな主張を見つけます。それらはコンパイル時に知られています。
ドーバル14

3
「構文糖として数えるために、javaは、foo_intおよびfoo_double関数を実際に書いたふりをする必要がありますが、そうではありません。」-ポリモーフィズムではなくメソッドのオーバーロードについて話す限り、foo(int)/ foo(double)foo_int/の違いは何foo_doubleですか?私はJavaをあまりよく知りませんが、そのような名前の変更はJVMで実際に起こると思います(まあ-おそらくfoo(args)むしろ使用しますfoo_args-それは少なくともC ++でシンボルマングリングを行います(OK-シンボルマングリングは技術的に実装の詳細であり、一部ではありません言語の))
マチェイピエチョトカ14

2
@Doval:「シンタックスシュガーの変換はささいなことだと言った人はいないと思います。」–本当ですが、ローカルでなければなりません。私の知っているシンタックスシュガーの唯一の有益な定義は、言語表現力にマタイアス・フェライセンの有名な論文からのものであり、基本的にそれはあなたができるかどうかの言語で書かれたプログラムを再書き込みすることを述べているL + Y(つまり、いくつかの言語L、いくつかの機能を持つY)での言語L(すなわちAサブセットその言語の特徴なしでYプログラム(すなわち、ローカル変更を行う)の全体構造を変えず)は、その後、Yはで構文糖であるY L +とし
ヨルグWミットタグ14

2
Lの表現力を増やさないでください。しかし、あなたがいる場合はできません、あなたのプログラムのグローバルな構造に変更を加える必要があればそれを行う、すなわち、それはありません糖衣構文と、実際のメイクにL + Yよりも表現力L。たとえば、拡張forループを使用したJavaは、ループを使用しないJavaよりも表現力がありません。(より良く、より簡潔で、より読みやすく、そして全体的に良くなっていると私は主張しますがより表現力豊かではありません。)しかし、オーバーロードのケースについてはわかりません。確かに論文を読み直す必要があるでしょう。私の腸はそれ構文糖であると言います、私にはわかりません。
ヨルグWミットタグ14

2
@MaciejPiechotka、言語定義の一部であり、関数の名前が非常に変更されていて、それらの名前で関数にアクセスできれば、構文糖衣だと思います。しかし、実装の詳細として隠されているので、私はそれが構文糖衣であることから不適格だと思います。
ウィンストンイーバート14

8

名前のマングリングが機能することを考えると、それは構文糖にすぎない必要はありませんか?

呼び出し元は、呼び出していないときに同じ関数を呼び出していると想像できます。しかし、彼は彼のすべての機能の本当の名前を知ることができました。型付けされていない変数を型付けされた関数に渡すことで遅延ポリモーフィズムを実現し、その型が確立されて名前に従って正しいバージョンに呼び出せる場合にのみ、これは真の言語機能になります。

残念ながら、これを行う言語を見たことはありません。あいまいさがある場合、これらのコンパイラはそれを解決せず、ライターが解決するように主張します。


探している機能は「マルチプルディスパッチ」と呼ばれます。Haskell、Scala、および(4.0以降)C#を含む多数の言語でサポートされています。
イアンギャロウェイ

クラスのパラメーターを単純なメソッドのオーバーロードから分離したいと思います。単純なメソッドのオーバーロードの場合、プログラマーはすべてのバージョンを記述し、コンパイラーはどのバージョンを選択するかを知っています。これは単なる構文上の糖であり、複数のディスパッチに対しても、単純な名前マングリングによって解決されます。---クラスにパラメータが存在する場合、コンパイラは必要に応じてコードを生成します。これにより、これが完全に変更されます。
ジョンジェイオーバーマーク14

2
誤解していると思います。メソッドへのパラメータのいずれかがある場合、例えば、C#で、dynamic次にオーバーロード解決はしないコンパイル時に、実行時に起こります。これが複数のディスパッチであり、関数の名前を変更しても複製できません。
イアンギャロウェイ14

とてもクール。ただし、変数の型をテストすることはできますが、これはまだ構文糖衣にオーバーレイされた組み込み関数です。これは言語機能ですが、かろうじて機能します。
ジョンジェイオーバーマーク14

7

言語に応じて、それは構文糖衣であるかどうかです。

たとえば、C ++では、オーバーロードとテンプレートを使用して、複雑化なしでは不可能なことを行うことができます(テンプレートのすべてのインスタンス化を手動で記述するか、多くのテンプレートパラメーターを追加します)。

動的ディスパッチはオーバーロードの形式であり、一部のパラメーターで動的に解決されることに注意してください(一部の言語では特別なもののみthisですが、すべての言語がそれほど制限されているわけではありません)。


本質的に間違っている場合、他の答えがどのように優れているかはわかりません。
テラスティン14

5

現代言語の場合、それは単なる構文糖です。完全に言語に依存しない方法で、それ以上のものです。

以前は、この答えは単に構文の砂糖以上のものであると述べていましたが、コメントでわかるように、Falcoは現代の言語がすべて欠落しているように見えるパズルが1つあると指摘しました。メソッドのオーバーロードと、同じステップで呼び出す関数の動的な決定を混在させません。これは後で明らかにされます。

これがもっとあるべき理由です。

メソッドのオーバーロードと型なし変数の両方をサポートする言語を検討してください。次のメソッドプロトタイプを使用できます。

bool someFunction(int arg);

bool someFunction(string arg);

いくつかの言語では、コンパイル時にこれらのどれがコードの特定の行によって呼び出されるかを知ることにおそらく辞任するでしょう。ただし、一部の言語では、すべての変数が型付けされているわけではない(またはすべて暗黙的に型付けされObjectている)

dict roomNumber; // some hotels use numbers, some use letters, and some use
                 // alphanumerical strings.  In some languages, built-in dictionary
                 // types automatically use untyped values for their keys to map to,
                 // so it makes more sense then to allow for both ints and strings in
                 // your code.

それでは、someFunctionこれらの部屋番号の1つに適用する場合はどうでしょうか。あなたはこれを呼ぶ:

someFunction(roomNumber[someSortOfKey]);

されるsomeFunction(int)と呼ばれる、またはされるsomeFunction(string)と呼ばれますか?ここでは、特に高レベル言語で、これらが完全に直交する方法ではない1つの例を示します。言語は-実行時に-これらのうちのどれを呼び出すかを把握する必要があるため、これらは少なくともこれらと少なくとも同じメソッドであると見なす必要があります。

単純にテンプレートを使用しないのはなぜですか?型付けされていない引数を使用しないのはなぜですか?

柔軟性ときめ細かな制御。テンプレート/型指定されていない引数を使用する方が適切な場合もありますが、そうでない場合もあります。

たとえば、それぞれが引数としてintaとa stringをとる2つのメソッドシグネチャを持っているが、シグネチャごとに順序が異なる場合について考える必要があります。各署名の実装はほとんど同じことを行うことができますが、わずかに異なるひねりを加えているため、これを行う正当な理由があります。たとえば、ロギングが異なる場合があります。または、まったく同じことをしても、引数が指定された順序から特定の情報を自動的に収集できる場合があります。技術的には、擬似スイッチステートメントを使用して、渡された各引数のタイプを判別することもできますが、それは面倒です。

それでは、この次の例は悪いプログラミングの習慣ですか?

bool stringIsTrue(int arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(Object arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(string arg)
{
    if (arg == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

はい、全般的に。この特定の例では、誰かがこれを特定のプリミティブ型に適用しようとするのを防ぎ、予期しない動作を取り戻すことができます(これは良いことです)。ただし、上記のコードを省略し、実際には、すべてのプリミティブ型とObjectsのオーバーロードがあると仮定しましょう。次に、次のコードが実際により適切です。

bool stringIsTrue(untyped arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

しかし、これをintsとstringsにのみ使用する必要がある場合、およびそれに応じてより単純またはより複雑な条件に基づいてtrueを返すようにしたい場合はどうでしょうか。次に、オーバーロードを使用する正当な理由があります。

bool appearsToBeFirstFloor(int arg)
{
    if (arg.digitAt(0) == 1)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool appearsToBeFirstFloor(string arg)
{
    string firstCharacter = arg.characterAt(0);
    if (firstCharacter.isDigit())
    {
        return appearsToBeFirstFloor(int(firstCharacter));
    }
    else if (firstCharacter.toUpper() == "A")
    {
        return true;
    }
    else
    {
        return false;
    }
}

しかし、これらの関数に2つの異なる名前を付けるだけではどうでしょうか。あなたにはまだ同じ量のきめ細かな制御がありますよね?

前に述べたように、一部のホテルでは数字を使用し、一部のホテルでは文字を使用し、一部のホテルでは数字と文字を組み合わせて使用​​しているためです。

appearsToBeFirstFloor(roomNumber[someSortOfKey]);

// will treat ints and strings differently, without you having to write extra code
// every single spot where the function is being called

これは、実際に私が実際に使用するのとまったく同じコードではありませんが、私がうまく行っている点を説明する必要があります。

しかし...これが、現代の言語の構文糖に過ぎない理由です。

Falcoは、現在の言語は基本的にメソッドのオーバーロードと動的な機能選択を同じステップ内で混在させないというコメントの中でポイントを上げました。以前に特定の言語が機能することを理解していた方法appearsToBeFirstFloorは、上記の例でオーバーロードでき、言語は実行時に型指定されていない変数のランタイム値に応じて、呼び出される関数のバージョンを決定することでした。この混乱は、ActionScript 3.0のようなECMA類の言語での作業に一部起因します。ActionScript3.0では、実行時に特定のコード行で呼び出される関数を簡単にランダム化できます。

ご存知かもしれませんが、ActionScript 3はメソッドのオーバーロードをサポートしていません。VB.NETの場合、型を明示的に割り当てることなく変数を宣言および設定できますが、これらの変数をオーバーロードメソッドに引数として渡そうとすると、ランタイム値を読み取って呼び出すメソッドを決定する必要がありません。代わりに、型の引数を持つ型、Objectまたは型のないメソッド、またはそのような何かを見つけたいと思っています。そうint対のstring例上記のいずれか、その言語では動作しないでしょう。C ++にも同様の問題があります。ボイドポインターなどのメカニズムを使用する場合、コンパイル時に型を手動で明確にする必要があるためです。

最初のヘッダーにあるように...

現代言語の場合、それは単なる構文糖です。完全に言語に依存しない方法で、それ以上のものです。上記の例のように、メソッドのオーバーロードをより便利で関連性の高いものにすることは、実際に既存の言語に追加するのに適した機能である可能性があります(AS3で広く暗黙的に要求されているように)、または、新しい手続き型/オブジェクト指向言語の作成。


3
コンパイル時ではなく、実行時にFunction-Dispatchを実際に処理する言語に名前を付けることはできますか?ALL私は関数が呼び出されるのコンパイル時の確実性を必要と知っている言語...
ファルコ

@Falco ActionScript 3.0は実行時にそれを処理します。:あなたは、例えば、リターンランダムに3つの文字列の1、その後はランダムに三つの機能のいずれかを呼び出すためにその戻り値を使用することを機能を使用することができthis[chooseFunctionNameAtRandom](); た場合はchooseFunctionNameAtRandom()リターンはどちらか"punch""kick"あるいは"dodge"、あなたは以下のようなものをランダムに非常にシンプルに実装することができますたとえば、Flashゲームの敵のAIの要素。
パンツァークライシス14

1
はい-しかし、どちらも動的な関数ディスパッチを取得するための実際のセマンティックメソッドです。Javaにも同様のメソッドがあります。しかし、それらはオーバーロードとは異なり、オーバーロードは静的であり、単なる構文上の砂糖であり、動的なディスパッチと継承は新しい機能を提供する実際の言語機能です!
ファルコ14

1
...また、C ++のvoidポインターと基本クラスポインターも試しましたが、コンパイラーは関数に渡す前に自分で曖昧さを解消することを望んでいました。だから今、私はこの答えを削除するかどうか疑問に思っています。言語はほとんどの場合、同じ命令またはステートメントで動的関数の選択と関数のオーバーロードを組み合わせてすぐに歩き始めますが、最後の1秒間は離れます。しかし、それは素晴らしい言語機能でしょう。多分誰かがこれを持っている言語を作る必要があります。
パンツァークライシス

1
答えはそのままにしておきましょう。答えのコメントからあなたの研究の一部を含めることを考えてみてください
ファルコ14

2

「構文糖」の定義に依存します。私の頭に浮かぶ定義のいくつかに対処しようとします:

  1. 機能は、それを使用するプログラムが常にその機能を使用しない他のプログラムに翻訳できる場合、構文糖衣です。

    ここでは、翻訳できない基本的なフィーチャセットが存在することを前提としています。つまり、「フィーチャYを使用してフィーチャXを置換できます」および「フィーチャYをフィーチャXで置換できます」というループはありません。2つのうちの1つが真である場合、他のフィーチャは最初のフィーチャではないフィーチャ、またはプリミティブフィーチャのいずれかで表現できます。

  2. 定義1と同じしかし、翻訳プログラムは、タイプセーフあなたはあらゆる種類の情報を失うことはありません脱糖ことにより、すなわち、最初のと同じくらいであること、余分な要件に。

  3. OPの定義:機能が翻訳によってプログラムの構造が変更されず、「ローカルな変更」のみが必要な場合、機能は構文糖衣です。

Haskellをオーバーロードの例として見てみましょう。Haskellは、型クラスを介してユーザー定義のオーバーロードを提供します。たとえば、+and *操作はNum型クラスで定義されており、そのようなクラスの(完全な)インスタンスを持つ型はで使用できます+。例えば:

instance Num a => Num (b, a) where
    (x, y) + (_, y') = (x, y + y')
    -- other definitions

("Hello", 1) + ("World", 3) -- -> ("Hello", 4)

Haskellの型クラスについてよく知られていることの1つは、それらを削除できることです。つまり、型クラスを使用するプログラムを、それらを使用しない同等のプログラムで翻訳できます。

翻訳は非常に簡単です。

  • クラス定義が与えられた場合:

    class (P_1 a, ..., P_n a) => X a where
        op_1 :: t_1   ... op_m :: t_m
    

    それを代数的なデータ型に変換できます:

    data X a = X {
        X_P_1 :: P_1 a, ... X_P_n :: P_n a,
        X_op_1 :: t_1, ..., X_op_m :: t_m
    }
    

    ここX_P_iX_op_iされているセレクタ。つまり、値にX a適用さX_P_1れるtypeの値を指定すると、そのフィールドに格納されている値が返されるため、それらはtype X a -> P_i a(またはX a -> t_i)を持つ関数です。

    以下のために非常に粗い anologyあなたはタイプの値を考えることができX aようstructならば、SとxタイプのものでありX a、式:

    X_P_1 x
    X_op_1 x
    

    として見ることができます:

    x.X_P_1
    x.X_op_1
    

    (名前付きフィールドの代わりに定位置フィールドのみを使用するのは簡単ですが、例では名前付きフィールドの方が扱いやすく、定型コードを避けています)。

  • インスタンス宣言がある場合:

    instance (C_1 a_1, ..., C_n a_n) => X (T a_1 ... a_n) where
        op_1 = ...; ...;  op_m = ...
    

    これを、C_1 a_1, ..., C_n a_nクラスの辞書がtypeの辞書値(つまり、typeの値X a)を返す関数に変換できますT a_1 ... a_n

    つまり、上記のインスタンスは次のような関数に変換できます。

    f :: C_1 a_1 -> ... -> C_n a_n -> X (T a_1 ... a_n)
    

    (注nすることができます0)。

    実際、次のように定義できます。

    f c1 ... cN = X {X_P_1=get_P_1_T, X_P_n=get_P_n_T,
                     X_op_1=op_1, ..., X_op_m=op_m}
        where
            op_1 = ...
            ...
            op_m = ...
    

    ここop_1 = ...するop_m = ...に見出される定義でinstance宣言とget_P_i_Tによって定義される関数であるP_iのインスタンスT(ので、これらが存在しなければならないタイプP_iSは、のスーパークラスですX)。

  • オーバーロードされた関数の呼び出しを考えると:

    add :: Num a => a -> a -> a
    add x y = x + y
    

    クラスの制約に関連する辞書を明示的に渡し、同等の呼び出しを取得できます。

    add :: Num a -> a -> a -> a
    add dictNum x y = ((+) dictNum) x y
    

    クラス制約が単に新しい引数になったことに注意してください。+翻訳されたプログラムで前に説明したようにセレクタです。言い換えれば、変換されたadd関数は、引数の型の辞書が与えられると、まず実際の関数を「アンパック」して結果を計算し(+) dictNum、次にこの関数を引数に適用します。

これは、全体に関する非常に簡単なスケッチです。もし興味があれば、サイモン・ペイトン・ジョーンズらの記事を読んでください。

他の言語のオーバーロードにも同様のアプローチ使用できると思います。

ただし、これは、構文糖の定義が(1)の場合、オーバーロードが構文糖であることを示しています。あなたはそれを取り除くことができるからです。

ただし、翻訳されたプログラムは元のプログラムに関する情報を失います。たとえば、親クラスのインスタンスが存在することを強制しません。(親の辞書を抽出する操作はまだそのタイプである必要がありますがundefined、の値を構築X yせずに値を構築できるように、他の多態的な値を渡すことができるP_i yため、翻訳はすべてを失うことはありません型安全性)。したがって、それは(2)によるとシンタクティ糖ではありません

(3)について。答えがイエスかノーかはわかりません。

たとえば、インスタンス宣言が関数定義になるため、ノーと言います。オーバーロードされた関数は、新しいパラメーターを取得します(つまり、定義とすべての呼び出しの両方が変更されます)。

2つのプログラムはまだ1対1でマッピングされるため、「構造」は実際にはそれほど変化しないため、「はい」と言います。


つまり、オーバーロードによってもたらされる実際的な利点は非常に大きいため、「構文糖」などの「軽rog的な」用語を使用することは正しくないと思われます。

すべてのHaskell構文を非常に単純なCore言語(実際にコンパイル時に行われます)に翻訳できるため、Haskell構文のほとんどは、ラムダ計算と少し新しい構成要素の「構文糖」と見なすことができます。ただし、Haskellプログラムの方がはるかに扱いやすく、非常に簡潔であり、翻訳されたプログラムは読みにくく、考えるのが非常に難しいことに同意できます。


2

引数式の静的型のみに依存してコンパイル時にディスパッチが解決される場合、プログラマが静的型を「知っている」場合、2つの異なるメソッドを異なる名前で置き換える「構文糖」であると間違いなく主張できます。オーバーロードされた名前の代わりに正しいメソッド名を使用できます。それはまた、静的な多型の形が、その限定された形で、それは通常、非常に強力ではありません。

もちろん、変数の型を変更するたびに呼び出すメソッドの名前を変更しなければならないのは面倒ですが、たとえばC言語では管理しやすい面倒だと考えられているため、Cには関数のオーバーロードがありません(ただし現在、汎用マクロがあります)。

C ++テンプレート、および自明でない静的型推論を行う言語では、静的型推論が「構文糖」であると主張しない限り、これが「シンタックスシュガー」であると主張することはできません。テンプレートを持たないのは厄介であり、C ++のコンテキストでは、言語とその標準ライブラリに対して非常に慣用的であるため、「管理不能な迷惑」になります。したがって、C ++では、単なるヘルパーというよりも、言語のスタイルにとって重要であるため、「シンタックスシュガー」以上の名前を付ける必要があります。

Javaでは、たとえば、PrintStream.printおよびのオーバーロードがいくつあるかを考えると、単なる便利以上のものと考えるかもしれませんPrintStream.println。しかしDataInputStream.readX、Javaは戻り値の型でオーバーロードしないため、メソッドは多数あります。ある意味では、便宜上のものです。これらはすべてプリミティブ型用です。

私はクラスを持っている場合は、Javaで何が起こるか覚えていないAと、B拡張OIは、メソッドをオーバーロード、foo(O)foo(A)foo(B)して、その後、一般的な中で<T extends O>Iコールのインスタンスです。場合である私が過負荷に基づいて派遣を得るのですか、それは私が呼ばれたかのようですか?foo(t)tTTAfoo(O)

前者の場合、Javaメソッドのオーバーロードは、C ++のオーバーロードと同じように砂糖よりも優れています。あなたの定義を使用して、Javaで一連の型チェックをローカルに書くことができると思います(新しいオーバーロードにfooは追加のチェックが必要になるため、脆弱です)。その脆弱性を受け入れることは別として、呼び出しサイトでローカルに変更してそれを正しくすることはできません。代わりに、汎用コードの作成をあきらめなければなりません。私は、肥大化したコードを防ぐことは構文上の砂糖かもしれないと主張しますが、脆弱なコードを防ぐことはそれ以上です。そのため、一般に静的多型は単なる構文糖以上のものです。特定の言語の状況は、その言語が静的型を「知らない」ことでどこまで到達できるかによって異なります。


Javaでは、オーバーロードはコンパイル時に解決されます。型の消去の使用を考えると、そうでなければ不可能です。でも、型消去せずに、さらに、場合T:AnimalであるタイプでSiameseCat、既存のオーバーロードがありCat Foo(Animal)SiameseCat Foo(Cat)Animal Foo(SiameseCat)場合に選択されるべき過負荷、TありますかSiameseCat
supercat 14

@supercat:理にかなっています。そのため、覚えていない(または、もちろん実行する)ことなく答えを見つけ出すことができました。したがって、Javaのオーバーロードは、C ++のオーバーロードが汎用コードに関連しているのと同じように、砂糖よりも優れていません。単なるローカル変換よりも優れた方法が他にもある可能性は残っています。私の例をC ++に変更するのか、それとも何らかの想像上のJavaであり、実際のJavaではないままにしておくべきでしょうか。
スティーブジェソップ14

オーバーロードは、メソッドにオプションの引数がある場合に役立ちますが、危険な場合もあります。行long foo=Math.round(bar*1.0001)*5がに変更されたとしlong foo=Math.round(bar)*5ます。bar等しい場合、たとえば123456789Lの場合、セマンティクスにどのように影響しますか?
supercat 14

@supercatからlongへの暗黙的な変換が存在する本当の危険性を主張しdoubleます。
ドーバル14

@Doval:へdouble
supercat

1

「シンタクティックシュガー」は、役に立たない、または軽薄なように、軽sounds的に聞こえます。それが質問が多くの否定的な答えを引き起こす理由です。

しかし、あなたは正しい、メソッドのオーバーロードは、異なるメソッドに同じ名前を使用する可能性を除いて、言語に機能を追加しません。パラメーターのタイプを明示的にすることもできますが、プログラムは同じように機能します。

パッケージ名にも同じことが当てはまります。文字列はjava.lang.Stringの構文糖衣です。

実際、次のようなメソッド

void fun(int i, String c);

クラスMyClassは、「my_package_MyClass_fun_int_java_lang_String」のような名前にする必要があります。これにより、メソッドが一意に識別されます。(JVMは内部的にそのようなことを行います)。しかし、あなたはそれを書きたくありません。そのため、コンパイラはfun(1、 "one")を記述し、どのメソッドであるかを識別します。

ただし、オーバーロードでできることは1つあります。同じ数の引数を持つメソッドをオーバーロードすると、コンパイラーは、等しい型だけでなく、引数が一致する引数によって与えられる引数に最適なバージョンを自動的に見つけます。指定された引数は、宣言された引数のサブクラスです。

オーバーロードされたプロシージャが2つある場合

addParameter(String name, Object value);
addParameter(String name, Date value);

Datesには特定のバージョンのプロシージャがあることを知る必要はありません。addParameter( "hello"、 "world)は最初のバージョンを呼び出し、addParameter(" now "、new Date())は2番目のバージョンを呼び出します。

もちろん、メソッドを完全に異なることを行う別のメソッドでオーバーロードすることは避けてください。


1

興味深いことに、この質問に対する答えは言語によって異なります。

具体的には、オーバーロードジェネリックプログラミング(*)の間に相互作用があり、ジェネリックプログラミングの実装方法によっては、単なる構文糖(Rust)または絶対に必要なもの(C ++)になります。

つまり、明示的なインターフェイス(RustまたはHaskellでは、これらは型クラスになります)を使用して汎用プログラミングが実装されている場合、オーバーロードは単なる構文上の砂糖です。または実際には言語の一部でさえないかもしれません。

一方、汎用プログラミングが(動的または静的に)ダックタイピングで実装される場合、メソッドの名前は必須のコントラクトであるため、システムが機能するにはオーバーロードが必須です。

(*)メソッドを1回記述するという意味で使用され、さまざまなタイプを均一に操作します。


0

一部の言語では、単に構文上の砂糖にすぎないことは間違いありません。しかし、それが何であるかは、あなたの視点に依存します。このディスカッションの後半では、この回答で説明します。

今のところ、一部の言語では確かに構文糖ではないことに注意してください。少なくともまったく同じロジック/アルゴリズムを使用して同じことを実装する必要はありません。再帰が構文糖であると主張するようなものです(これは、ループとスタックを使用してすべての再帰アルゴリズムを記述できるためです)。

非常に使いにくい例の1つは、皮肉なことにこの機能を「関数のオーバーロード」とは呼ばない言語に由来しています。代わりに、「パターンマッチング」と呼ばれます(型だけでなく値もオーバーロードできるため、オーバーロードのスーパーセットと見なすことができます)。

Haskellのフィボナッチ関数の古典的な単純な実装は次のとおりです。

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

他の言語で一般的に行われているように、おそらく3つの関数はif / elseに置き換えることができます。しかし、それは基本的にまったく単純な定義になります。

fib n = fib (n-1) + fib (n-2)

ずっと厄介で、フィボナッチ数列の数学的概念を直接表現していません。

そのため、異なる引数を使用して関数を呼び出せるようにすることだけが目的の場合、構文シュガーになることがあります。ただし、それよりもはるかに基本的な場合もあります。


ここで、演算子のオーバーロードが砂糖であるかもしれないことについて議論します。1つのユースケースを特定しました-異なる引数を取る同様の関数を実装するために使用できます。そう:

function print (string x) { stdout.print(x) };
function print (number x) { stdout.print(x.toString) };

または、次のように実装できます。

function printString (string x) {...}
function printNumber (number x) {...}

あるいは:

function print (auto x) {
    if (x instanceof String) {...}
    if (x instanceof Number) {...}
}

ただし、演​​算子のオーバーロードは、オプションの引数を実装するためのシュガーでもあります(一部の言語には、オプションの引数ではなく、演算子のオーバーロードがあります)。

function print (string x) {...}
function print (string x, stream io) {...}

以下を実装するために使用できます:

function print (string x, stream io=stdout) {...}

このような言語(google "Ferite language")では、演算子のオーバーロードを削除すると、1つの機能(オプションの引数)が大幅に削除されます。どちらの機能もオプションの引数を実装するために使用できるため、両方の機能(c ++)のいずれかを削除する言語で付与されても、実質的な効果はありません。


Haskellは、演算子のオーバーロードが構文糖衣ではない理由の良い例ですが、より良い例は、パターンマッチング(パターンマッチングなしでは不可能だとわかっている限り)で代数データ型を分解することです。
11684 14

@ 11684:例を挙げていただけますか?私は正直にHaskellをまったく知りませんが、そのfibの例を(youtubeのコンピューター愛好家で)見たとき、そのパターンマッチングが非常にエレガントであることに気付きました。
スリーブマン14

data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfoタイプコンストラクターでパターンマッチできるようなデータタイプを指定します。
11684

このようにgetPayment :: PaymentInfo -> a getPayment CashOnDelivery = error "Should have been paid already" getPayment (Adress addr) = -- code to notify administration to send a bill getPayment (UserInvoice cust) = --whatever. I took the data type from a Haskell tutorial and have no idea what an invoice is。このコメントがある程度理解できることを願っています。
11684 14

0

それらはすべてコンパイル時に明確なFunction-callを必要とするため、ほとんどの言語(少なくとも私が知っているすべての言語)では単純な構文糖だと思います。また、コンパイラーは、関数呼び出しを正しい実装シグネチャーへの明示的なポインターに置き換えるだけです。

Javaの例:

String s; int i;
mangle(s);  // Translates to CALL ('mangle':LString):(s)
mangle(i);  // Translates to CALL ('mangle':Lint):(i)

したがって、最終的には、検索と置換を使用した単純なコンパイラマクロで完全に置き換えることができ、オーバーロードされた関数mangleをmangle_Stringとmangle_intに置き換えます-引数リストは最終的な関数識別子の一部であるため、これは実際に何が起こるかです->したがって、それは唯一の構文糖です。

オブジェクトでオーバーライドされたメソッドのように、実行時に関数が実際に決定される言語がある場合、これは異なります。しかし、method.overloadingは曖昧になりがちであり、コンパイラが解決できず、明示的なキャストでプログラマが処理する必要があるため、このような言語はないと思います。これは実行時に実行できません。


0

Javaでは、型情報はコンパイルされ、どのオーバーロードが呼び出されるかはコンパイル時に決定されます。

以下は、sun.misc.UnsafeEclipseのクラスファイルエディタで表示される(Atomicsのユーティリティ)のスニペットです。

  // Method descriptor #143 (Ljava/lang/Object;I)I (deprecated)
  // Stack: 4, Locals: 3
  @java.lang.Deprecated
  public int getInt(java.lang.Object arg0, int arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  iload_2 [arg1]
    3  i2l
    4  invokevirtual sun.misc.Unsafe.getInt(java.lang.Object, long) : int [231]
    7  ireturn
      Line numbers:
        [pc: 0, line: 213]

呼び出されるメソッドのタイプ情報(4行目)が呼び出しに含まれていることがわかります。

これは、型情報を取るJavaコンパイラを作成できることを意味します。たとえば、このような表記を使用すると、上記のソースは次のようになります。

@Deprecated
public in getInt(Object arg0, int arg1){
     return getInt$(Object,long)(arg0, arg1);
}

longへのキャストはオプションです。

他の静的に型付けされたコンパイル言語では、型に応じてコンパイラがどのオーバーロードを呼び出すかを決定し、それをバインディング/呼び出しに含める同様のセットアップがあります。

例外は、型情報が含まれていないCダイナミックライブラリであり、オーバーロードされた関数を作成しようとすると、リンカからエラーが発生します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.