関数のオーバーロード?はいまたはいいえ[終了]


16

私は静的に強く型付けされたコンパイル済み言語を開発しており、言語の機能として関数のオーバーロードを含めるかどうかの考えを再検討しています。主にC[++|#]バックグラウンドから来て、私は少し偏っていることに気づきました。

最も説得力のある引数何であるため反対言語でのオーバーロード機能を含めては?


編集:反対意見を持っている人は誰もいませんか?

Bertrand Meyer(1985/1986年にエッフェルの作成者)は、これをオーバーロードするメソッドを呼び出します:(ソース)

オブジェクト指向言語のセマンティックパワーには何ももたらさないが、読みやすさを妨げ、全員のタスクを複雑にするバニティメカニズム

今、それらは抜本的な一般化ですが、彼は賢い人ですので、必要に応じてそれらをバックアップできると言っても安全だと思います。実際、ブラッドエイブラムス(CLSv1開発者の1人)は、.NETがメソッドのオーバーロードをサポートしてはならないと確信していました。(ソース)それはいくつかの強力なものです。誰もが彼の考えに光を当てることができ、彼の視点が25年後もまだ正当化されるかどうか?

回答:


24

関数のオーバーロードは、C ++スタイルのテンプレートコードにとって絶対に重要です。タイプごとに異なる関数名を使用する必要がある場合、汎用コードを書くことはできません。これにより、C ++ライブラリの大規模で頻繁に使用される部分、およびC ++の機能の多くが削除されます。

通常、メンバー関数名に存在します。 A.foo()はまったく異なる関数を呼び出すことができますB.foo()が、両方の関数に名前が付けられていfooます。これ+は、整数と浮動小数点数に適用されたときに異なることが行われるように、演算子に存在し、文字列連結演算子としてよく使用されます。通常の機能でも許可しないのは奇妙に思えます。

呼び出される正確な関数が2つのデータ型に依存するCommon Lispスタイルの「マルチメソッド」の使用を可能にします。Common Lisp Object Systemでプログラミングしていない場合は、これを役に立たないと呼ぶ前に試してください。C ++ストリームにとって不可欠です。

関数のオーバーロードを伴わないI / O(またはさらに悪い可変個の関数)には、さまざまなタイプの値を出力するか、さまざまなタイプの値を共通タイプ(Stringなど)に変換するためのさまざまな関数が必要です。

関数のオーバーロードなしで、変数または値の型を変更する場合、それを使用するすべての関数を変更する必要があります。コードのリファクタリングがはるかに難しくなります。

どのタイプの命名規則が使用されているかをユーザーが覚える必要がなく、ユーザーは標準の関数名を覚えているだけの場合、APIを使用しやすくなります。

演算子のオーバーロードがなければ、その基本操作を複数の型で使用できる場合、使用する型で各関数にラベルを付ける必要があります。これは本質的にハンガリー語の表記法であり、それを行うのが悪い方法です。

全体的に、それは言語をはるかに使いやすくします。


1
+1、すべて非常に良い点。そして、私を信じてください。マルチメソッドは役に立たないとは思いません。ビジターパターンを使用せざるを得ないたびに入力するキーボードを呪います。
自己への注意-

この男は関数のオーバーライドを記述し、オーバーロードを記述していませんか?
dragosb

8

少なくともHaskellの型クラスに注意することをお勧めします。型クラスは、演算子のオーバーロードに対する規律あるアプローチとなるように作成されましたが、他の用途を見つけており、ある程度までHaskellを作りました。

たとえば、アドホックオーバーロードの例は次のとおりです(まったく有効なHaskellではありません)。

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

そして、型クラスでオーバーロードする同じ例です:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

これの欠点は、あなたがすべてのあなたの型クラスのためのファンキーな名前を思い付くしなければならないということである(Haskellのと同様に、あなたはかなり抽象的なを持っているMonadFunctorApplicativeだけでなく、よりシンプルかつ認識EqNumOrd)。

利点は、型クラスに慣れたら、そのクラスで任意の型を使用する方法を知っていることです。さらに、次のように、必要なクラスを実装しない型から関数を簡単に保護できます。

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

編集: Haskellでは、==2つの異なる型を受け入れる演算子が必要な場合、マルチパラメータ型クラスを使用できます。

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

もちろん、リンゴとオレンジを明示的に比較できるため、これはおそらく悪い考えです。ただし、一部のコンテキストではにを+追加するのWord8Int賢明なことなので、についてこれを検討することをお勧めします。


+1、私は最初にそれを読んだ時からずっとこの概念を理解しようとしてきたが、これは助けになる。このパラダイムは、たとえば(==) :: Int -> Float -> Boolどこでも定義することを不可能にしますか?(もちろん、それが良いアイデアであるかどうかに関係なく)
自己への注意-名前を考える

(Haskellが拡張機能としてサポートしている)マルチパラメータタイプクラスを許可する場合、可能です。例で答えを更新しました。
ジョーイアダムス

うーん、面白い。基本的に、class Eq a ...擬似Cファミリに変換されるのはinterface Eq<A> {bool operator==(A x, A y);}となり、テンプレートコードを使用して任意のオブジェクトを比較する代わりに、この「インターフェイス」を使用します。そうですか?
自己への注意

正しい。Goのインターフェイスを簡単に確認することもできます。つまり、型をインターフェイスの実装として宣言する必要はなく、そのインターフェイスのすべてのメソッドを実装するだけです。
ジョーイアダムス

1
@ Notetoself-thinkofaname:追加の演算子について-はいといいえ。それはすることができます==別の名前空間にあるように、それを上書きすることはできません。単一の名前空間(があることに注意してくださいPrelude)デフォルトで含まれていますが拡張子を使用するか、明示的にインポートし、それをロード防ぐことができますが(import Prelude ()から何をインポートしないだろうPreludeimport qualified Prelude as Pしません、現在の名前空間にシンボルの挿入)。
マチェイピエチョトカ

4

関数のオーバーロードを許可します。オプションのパラメーターを使用して次の操作を実行することはできません(できれば、うまくできません)。

些細な例では メソッドを想定していませんbase.ToString()

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...

強く型付けされた言語の場合、はい。弱い型付け言語の場合、いいえ。弱い型付けの言語では、1つの関数のみで上記を実行できます。
spex

2

私は常に、関数のオーバーロードよりも既定のパラメーターを優先しています。オーバーロードされた関数は、通常、デフォルトパラメータで「デフォルト」バージョンを呼び出すだけです。書く理由

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

私ができるとき:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

そうは言っても、デフォルトのパラメーターで別のバリアントを呼び出すだけでなく、オーバーロードされた関数が異なることを行うこともありますが、そのような場合は、単にそれを与えることは悪い考えではありません(実際、おそらく良い考えです)別の名前。

(また、Pythonスタイルのキーワード引数は、デフォルトのパラメーターでも非常にうまく機能します。)


さて、もう一度ことを試してみましょう、と私は何について...この時間を感知作ってみるよArray Slice(int start, int length) {...}で過負荷Array Slice(int start) {return this.Slice(start, this.Count - start);}?デフォルトのパラメーターを使用してコーディングすることはできません。別の名前を付けるべきだと思いますか?その場合、どのように名前を付けますか?
自己への注意-

それはオーバーロードのすべての使用に対処しているわけではありません。
MetalMikester

@MetalMikester:私が見逃したと思う用途は何ですか?
mipadi

@mipadi リストのindexOf(char ch)+のようなものを見逃しindexOf(Date dt)ています。私もデフォルト値が好きですが、静的型付けと交換できません。
マーク

2

Javaについて説明しました。またはC#。

なぜ車輪を再発明したのですか?

戻り値の型がメソッドシグネチャの一部であり、心のコンテンツにオーバーロードしていることを確認してください。言う必要がない場合は、コードが本当にクリーンアップされます。

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }

7
私が知っているどの言語でも、戻り値の型がメソッドシグネチャの一部ではない(または少なくともオーバーロード解決に使用できる部分ではない)理由があります。結果を変数またはプロパティに割り当てずにプロシージャとして関数を呼び出す場合、他のすべての引数が同一である場合、コンパイラはどのバージョンを呼び出すかをどのように想定するのですか?
メイソンウィーラー

@Mason:返されることが期待されるものに基づいて、期待される返り値の型を発見的に検出できますが、そうなるとは思いません。
ジョシュK

1
えーと...戻り値が期待されていないときに、どのような結果が返されるとヒューリスティックはどのように知るのですか?
メイソンウィーラー

1
型期待値ヒューリスティックは実際に配置されています...などのことができますEnumX.Flag1 | Flag2 | Flag3。ただし、これは実装しません。使用し、戻り値の型が使用されていない場合、戻り値の型を探しますvoid
自己への注意-

1
@Mason:それはいい質問ですが、その場合は(前述のように)void関数を探します。また、理論的には、それらはすべて同じ機能を実行し、単に異なる形式でデータを返すため、いずれかを選択できます。
ジョシュK

2

Grrr ..まだコメントするには十分な権限がありません..

@Mason Wheeler:戻り型でオーバーロードするAdaに注意してください。また、私の言語Felixは、特定のコンテキスト、特に関数が別の関数を返し、次のような呼び出しがある場合にもそれを行います。

f a b  // application is left assoc: (f a) b

bのタイプは、オーバーロード解決に使用できます。また、状況によってはC ++が戻り値の型でオーバーロードします。

int (*f)(int) = g; // choses g based on type, not just signature

実際、型推論を使用して、戻り値の型にオーバーロードするアルゴリズムがあります。実際、機械を使うのはそれほど難しくありません。問題は人間がそれを難し​​く感じることです。(アウトラインはDragon Bookに記載されていると思います。正しく覚えていれば、アルゴリズムはシーソーアルゴリズムと呼ばれています)


2

関数のオーバーロードの実装に対するユースケース:さまざまなパターンでまったく異なる引数のセットを使用して、同じことを行う25の同じ名前のメソッド。

関数のオーバーロードを実装しない場合のユースケース:まったく同じパターンで非常に類似した型のセットを持つ5つの類似した名前のメソッド。

結局のところ、どちらの場合でも作成されたAPIのドキュメントを読むのを楽しみにしていません。

しかし、ある場合には、ユーザーが何をするかについてです。それ以外の場合は、言語の制限のためにユーザーがしなければならないことです。IMO、少なくともプログラムの作者があいまいさを作らずに賢明にオーバーロードするのに十分賢いという可能性を考慮した方が良いです。あなたが彼らの手を平手打ちし、オプションを奪うとき、あなたは基本的に曖昧さが起こることを保証しています。私は、ユーザーが常に間違った行動をとると想定するよりも、正しい行動をとることをユーザーに信頼することを重視しています。私の経験では、保護主義は言語のコミュニティの一部でさらに悪い行動につながる傾向があります。


1

私は、言語Felixで通常のオーバーロードとマルチタイプのタイプクラスを提供することにしました。

特に、多くの数値型(FelixにはすべてCの数値型がある)の言語では、(オープンな)オーバーロードが不可欠であると考えています。ただし、テンプレートを依存させることでオーバーロードを悪用するC ++とは異なり、Felixポリモーフィズムはパラメトリックです。C++のテンプレートの設計が不適切であるため、C ++のテンプレートのオーバーロードが必要です。

タイプクラスはFelixでも提供されます。C ++を知っているがHaskellを理解していない人のために、それをオーバーロードと記述している人は無視してください。リモートでのオーバーロードのようなものではなく、テンプレートの専門化のようなものです。実装しないテンプレートを宣言し、必要に応じて特定のケースの実装を提供します。型付けはパラメトリックに多態的であり、実装はアドホックなインスタンス化によるものですが、制約を受けることを意図したものではなく、意図したセマンティクスを実装する必要があります。

Haskell(およびC ++)では、セマンティクスを述べることはできません。C ++では、「概念」という考え方は、おおよそセマンティクスを概算する試みです。Felixでは、公理、簡約、補題、定理で意図を近似できます。

Felixのような原則に基づいた言語での(オープンな)オーバーロードの主な唯一の利点は、プログラム作成者とコードレビューアの両方にとってライブラリ関数名を覚えやすくすることです。

オーバーロードの主な欠点は、実装に必要な複雑なアルゴリズムです。また、型推論とは相性がよくありません。2つは完全に排他的ではありませんが、両方を実行するアルゴリズムは、プログラマがおそらく結果を予測できないほど複雑です。

C ++では、これは、ずさんなマッチングアルゴリズムを持ち、自動型変換もサポートするため、問題です。Felixでは、完全な一致を要求し、自動型変換を必要としないことでこの問題を「修正」しました。

だから、あなたは私が思うに選択がある:オーバーロードまたは型推論。推論はかわいいですが、競合を適切に診断する方法で実装することも非常に困難です。たとえば、Ocamlは、競合を検出した場所を示しますが、予想されるタイプを推測した場所は示しません。

オーバーロードはあまり良いものではありません。たとえすべての候補を伝えようとする高品質のコンパイラを持っているとしても、候補が多態性であると読みにくくなり、C ++テンプレートハッカーであるとさらに悪くなります。


面白そう。もっと読みたいのですが、Felix Webページのドキュメントへのリンクが壊れています。
自己への注意-

はい、サイト全体が現在(再び)建設中です、申し訳ありません。
イットリル

0

コンテキストに行き着きますが、他の誰かが書いたクラスを使用している場合、オーバーロードはクラスをより便利にするものだと思います。多くの場合、冗長性が低くなります。


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