メソッドは一種の多態性をオーバーロードしていますか?私にとっては、名前が同じでパラメータが異なるメソッドの区別のように思えます。そのため、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)
に変更されます。
以上です。
ご覧のとおり、メソッドのオーバーロードを使用する場合と使用しない場合の違いは完全にローカルです。そして、それが純粋な構文糖としての資格があると私が思う理由です。
あなたが何かを持っているなら、私はあなたの反対を聞きたいです、多分私は何かを逃しています。
これに答えるには、最初に「構文糖」の定義が必要です。 Wikipedia's を使用します。
コンピュータサイエンスでは、シンタックスシュガーは、物事を読みやすくしたり表現したりできるように設計されたプログラミング言語内の構文です。それは言語を人間の使用に対して「より甘い」にします:物事はより明確に、より簡潔に、または一部が好むかもしれない別のスタイルで表現できます。
[...]
具体的には、言語の構成要素は、言語が実行できることに影響を与えずに言語から削除できる場合、構文シュガーと呼ばれます。
したがって、この定義では、Javaの可変引数やScalaのfor理解などの機能は構文上の砂糖です。これらは基礎となる言語機能(最初の場合は配列、2番目の場合はmap/flatmap/filterへの呼び出し)に変換され、それらを削除すると、言語でできることを変えないでください。
ただし、メソッドのオーバーロードは、この定義では構文上の砂糖ではありません。これを削除すると、言語が根本的に変わるためです(引数に基づいて異なる動作にディスパッチすることができなくなります)。
確かに、メソッドの引数にアクセスする方法がある限り、メソッドのオーバーロードをシミュレートでき、指定された引数に基づいて「if」構造を使用できます。しかし、その構文上の砂糖を検討する場合、同様に構文上の砂糖であるためには、チューリングマシンより上のものを検討する必要があります。
構文糖という用語は、通常、機能が置換によって定義される場合を指します。言語は機能が何をするかを定義するのではなく、それが何か他のものとまったく同等であると定義します。たとえば、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_int
_および_foo_double
_関数ですが、そうではありません。
名前のマングリングが機能することを考えると、それは構文糖にすぎない必要はありませんか?
呼び出し側は、呼び出していないときに同じ関数を呼び出していることを想像できます。しかし、彼は自分のすべての関数のreal名を知ることができました。型指定されていない変数を型指定された関数に渡し、その型を確立して、呼び出しが名前に従って正しいバージョンに移動できるようにして遅延多相性を実現できた場合にのみ、これが真の言語機能になります。
残念ながら、これを行う言語を見たことがありません。あいまいな場合、これらのコンパイラーはそれを解決せず、ライターがそれを解決するように要求します。
言語に応じて、それは構文糖であるかどうかです。
たとえばC++では、複雑化なしでは不可能であるオーバーロードとテンプレートを使用して物事を行うことができます(テンプレートのすべてのインスタンス化を手動で作成するか、多数のテンプレートパラメーターを追加します)。
動的ディスパッチは、オーバーロードの一種であり、一部のパラメーターで動的に解決されます(一部の言語では特別なものthisですが、すべての言語がそれほど制限されているわけではありません)。構文糖の多重定義。
現代の言語の場合、それは単なる構文上の砂糖です。完全に言語に依存しない種類の方法で、それはそれ以上です。
以前、この回答は単に構文上の糖以上のものであると述べていましたが、コメントでわかるように、ファルコは現代の言語がすべて欠けているように見えるパズルの断片があったと指摘しました。メソッドのオーバーロードと、同じステップで呼び出す関数の動的な決定を組み合わせることはありません。これは後で明らかにされます。
これが理由ですにする必要があります
メソッドのオーバーロードと型指定されていない変数の両方をサポートする言語について考えてみましょう。次のメソッドプロトタイプを作成できます。
_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.
_
それでは、これらの部屋番号の1つにsomeFunction
を適用したい場合はどうでしょうか。あなたはこれを呼ぶ:
_someFunction(roomNumber[someSortOfKey]);
_
someFunction(int)
が呼び出されますか、それともsomeFunction(string)
が呼び出されますか?これらは、特に高水準言語で、これらが完全に直交する方法ではない1つの例を示します。言語は、実行時にこれらのうちどれを呼び出すかを理解する必要があるため、これらは少なくともある程度同じメソッドであると見なす必要があります。
単にテンプレートを使用しないのはなぜですか?単に型なし引数を使用しないのはなぜですか?
柔軟性と細かい制御。テンプレート/型なし引数を使用する方が良い場合もありますが、そうでない場合もあります。
たとえば、2つのメソッドシグネチャがあり、それぞれがint
とstring
を引数として取るが、順序が各シグネチャで異なる場合について考える必要があります。各シグネチャの実装はほとんど同じことを行うかもしれませんが、少し異なるツイストで、これを行う十分な理由がある場合があります。たとえば、ロギングは異なる場合があります。または、それらがまったく同じことを行う場合でも、引数が指定された順序だけから特定の情報を自動的に収集できる場合があります。技術的には、疑似スイッチステートメントを使用して、渡された各引数のタイプを判別できますが、面倒です。
この次の例は悪いプログラミング習慣ですか?
_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;
}
}
_
はい、概して。この特定の例では、誰かがこれを特定のプリミティブ型に適用しようとして予期しない動作を返すのを防ぐことができます(これは良いことかもしれません)。しかし、上記のコードを省略して、実際にはすべてのプリミティブ型とObject
sのオーバーロードがあると仮定しましょう。次に、この次のコードが実際に適しています。
_bool stringIsTrue(untyped arg)
{
if (arg.toString() == "0")
{
return false;
}
else
{
return true;
}
}
_
しかし、これをint
sとstring
sにのみ使用する必要がある場合はどうし、それに応じてより単純またはより複雑な条件に基づいて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の種類の言語での作業に一部起因しています。
ご存知かもしれませんが、ActionScript 3はメソッドのオーバーロードをサポートしていません。 VB.NETの場合、型を明示的に割り当てなくても変数を宣言して設定できますが、これらの変数を引数としてオーバーロードされたメソッドに渡そうとする場合でも、ランタイム値を読み取って呼び出すメソッドを決定する必要はありません。代わりに、Object
型の引数を持つ、または型のない、またはそのような何かを持つメソッドを見つけたいと考えています。したがって、上記のint
とstring
の例は、その言語でも機能しません。 C++にも同様の問題があります。ボイドポインターなどのメカニズムを使用する場合でも、コンパイル時に型を明確にする必要があります。
最初のヘッダーにあるように...
現代の言語では、それは構文上の砂糖にすぎません。完全に言語にとらわれない方法で、それだけではありません。上記の例のように、メソッドのオーバーロードをより有用で関連性のあるものにすることは、(AS3で広く暗黙的に要求されているように)実際に既存の言語に追加するのに適した機能かもしれません。新しい手続き型/オブジェクト指向言語の作成。
興味深いことに、この質問に対する答えは言語によって異なります。
具体的には、overloadingとgeneric programming(*)の間に相互作用があり、一般的なプログラミングの実装方法によっては、単なる構文糖(Rust)または絶対的な必要( C++)。
つまり、一般的なプログラミングがexplicit interfacesで実装されている場合(RustまたはHaskellでは、これらは型クラスです)、オーバーロードは単なる構文です砂糖;または実際には言語の一部でさえないかもしれません。
一方、ジェネリックプログラミングがduck-typing(動的または静的)で実装されている場合、メソッドの名前は必須のコントラクトであるため、オーバーロードは必須です動作するシステム。
(*)メソッドを1回記述するという意味で使用され、さまざまなタイプを統一的に操作します。
「構文糖」は無意味で軽薄なように軽蔑的に聞こえます。質問が多くの否定的な答えを引き起こす理由です。
しかし、そうです、メソッドのオーバーロードは、異なるメソッドに同じ名前を使用する可能性を除いて、言語に機能を追加しません。パラメータタイプを明示的にしても、プログラムは同じように機能します。
同じことがパッケージ名にも当てはまります。文字列は、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);
日付の手順に特定のバージョンがあることを知っている必要はありません。 addParameter( "hello"、 "world)は最初のバージョンを呼び出し、addParameter(" now "、new Date())は2番目のバージョンを呼び出します。
もちろん、完全に異なる処理を行う別のメソッドでメソッドをオーバーロードすることは避けてください。
一部の言語では、それは間違いなく単に構文上の砂糖です。しかし、それが何であるかはあなたの視点に依存します。この回答については後で説明します。
とりあえず、一部の言語ではそれは確かに構文上の砂糖ではないことに注意したい。少なくとも、まったく異なるロジック/アルゴリズムを使用して同じものを実装する必要がないわけではありません。これは、再帰が構文上のシュガーであると主張するようなものです(これは、ループとスタックですべての再帰アルゴリズムを記述できるためです)。
非常に置き換えが難しい使用の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 ++)を備えた言語で付与され、どちらか一方を削除すると、どちらもオプションの引数の実装に使用できるため、最終的な効果はありません。
ほとんどの言語(少なくとも私が知っているすべての言語)では、これらはすべてコンパイル時に明確な関数呼び出しを必要とするため、単純な構文糖であると思います。また、コンパイラーは、関数呼び出しを、適切な実装シグニチャーへの明示的なポインターに置き換えるだけです。
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に置き換えます。argument-listは最終的な関数識別子の一部であるため、これは実際に起こることです->およびしたがって、これは構文上の砂糖にすぎません。
オブジェクトのオーバーライドされたメソッドのように、実行時に関数が実際に決定される言語がある場合、これは異なります。しかし、method.overloadingは曖昧になりがちで、コンパイラーが解決できず、明示的なキャストでプログラマーが処理しなければならないため、そのような言語はないと思います。これは実行時に行うことはできません。
Javaで型情報がコンパイルされ、どのオーバーロードが呼び出されるかはコンパイル時に決定されます。
以下は、Eclipseのクラスファイルエディターで表示されたSun.misc.Unsafe
(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動的ライブラリであり、オーバーロードされた関数を作成しようとすると、リンカがエラーを表示します。