なぜ複数の戻り値が一般的ではないのですか?


7

複数の戻り値について質問したいのですが、なぜこの構造がプログラミング言語で好まれないのですか(概念的および/または技術的な問題)。スタックフレームについて何か聞いたことがあります。また、戻り値と変数の戻り値のためにメモリを予約することで、これが問題になる可能性があります。

接触型言語(FORTHなど)では、関数からの複数の戻り値を持つことは一般的で非常に便利なものであり、java / cのような言語でこのようなものも役立つと思います(非常に基本的な例)。

x, y = multRet(5);
multret(int x) {
    return x+1;
    return x+2;
    exit;
}

明確にするために:私は複数の値を返す方法を尋ねていません(これは既知の回答を持つ既知の質問です)が、この習慣がプログラミング言語では一般的ではない理由を明確にしたいと思います。


4
1つの考えられる説明は、複数の値を返す必要がある場合、構造体またはリストを返すことでいつでもそれを行うことができるということです。言語とコンパイラを複雑にするのはなぜですか?
David Richerby 2014

4
その質問には答えられないと思います。まず、これは好みの問題です。第二に、「珍しい」という前提は誤りだと思います。返されるタプルをサポートする言語は非常に多く、1970年代から使用されています。最近設計されたすべての人気のあるシステムプログラミング言語(Swift、Go、Rust)が返されるタプルをサポートするため、返されるタプルをサポートする習慣が人気を博しているようです。。
Wandering Logic

2
@DavidRicherbyに同意します。そのような機能は冗長であり、潜在的に困難/疑わしいセマンティクスを持ちi=0; while (true) { return i; i+=1 }ます(どうなるか?)。一部の関数型言語は、より優れた代替手段であるタプルを提供します。
ラファエル

1
@WanderingLogicタプルは1つの値であり、1つのreturnステートメントによって返されます。関数/ラムダを返すときに、ポイントがあるかもしれません。
ラファエル

1
この質問は意見に基づくものではありません。これまでのコメントは、技術的な回答があることを示しています。質問の「なぜ」の部分が主観的すぎると思われる場合は、「複数の戻り値を許可することの利点と欠点は何ですか?」に簡単に編集できます。
David Richerby 2014

回答:


7

関数は伝統的に、数学では単一の値を返すと考えられています。もちろん、いくつかの値がタプルを形成しますが、多くの場合、数学では実数値の関数を扱います。この関数の起源が、複数の戻り値があまり一般的ではない理由だと思います。とはいえ、いくつかの方法でそれらをエミュレートするのは簡単です(タプルを返す、またはいくつかの出力パラメーター(つまり、参照で渡されるパラメーター)を使用する)と、いくつかの一般的な最近の言語(Pythonなど)は、この構成をネイティブでサポートしています。


4

複数の戻り値を持つこと望ましいです。それらが欠けていると、デザインが悪く、プレーンでシンプルになります。しかし、そういうものはあなたが考えている意味でも存在しているという誤解を解消する必要があります。

型付きラムダ計算(祖先プログラミング言語)に基づく言語、たとえばMLファミリHaskellでは、関数(ラムダ抽象化)は単一の引数のみを受け入れ、単一の結果を「返す」(より正式には、評価する)。では、複数の値を取得または返すように見える関数を作成するにはどうすればよいでしょうか。

その答えは、商品タイプを使用して値を「接着」できるということです。場合ABが両方のタイプであり、その後、製品タイプ×B フォームのすべてのタプルが含まれています ab、 どこ a そして bB

したがって、multiply2つの整数を乗算する関数を作成するには、それに型を指定しmultiply: int * int -> intます。たまたまタプルである単一の引数を受け入れます。同様に、splitリストを2つに分割する関数はtypeになりsplit: a list -> a list * a listます。

たぶん30年前、実装はそのような機能を含めることの正当な関心事でした。Cの呼び出し規約cedl(そしてそれは単なる規約 -神が定めたものではありません)によると、戻り値は通常、である特殊レジスターに格納されEAXます。確かに、これは読み書きが非常に速いため、このアイデアにはいくつかのメリットがあります。

しかし、あなたが指摘するように、それはあまりにも制限的であり、今日このルールに従う必要がある理由はありません。たとえば、代わりに次のポリシーを使用できます。戻り値の数が少ない場合は、指定されたレジスターのセットを使用します(80年代よりも多くのレジスターを現在使用しています)。より大きなデータの場合、メモリ内の場所へのポインタを返すことができます。何が一番いいのかわかりません。私はコンパイラーのライターではありません。しかし、古風なCの呼び出し規約は、現代の言語のセマンティクスに影響を与えません。


1

単純なパターンがうまく機能し、提案された構文が歴史的に多くの人々を混乱させる場合、構文は必要ありません。これは、あなたの構文が習得するのが難しく、人々があなたの言語と他の言語を切り替えるときに問題を引き起こすことを意味します。

この問題を解決する単純なパターンは、メソッドの最初に「returnValue」という名前の変数を定義し、それに値を割り当てることです。複数の値が必要な場合は、並べ替えのコレクションを使用して、代わりに値を追加します。次に、この変数を返します。問題が解決しました。

構文は、デフォルトでコレクションを処理する必要があることを意味します。これは、値を1つしか返さない関数にはイライラします。

そして、誰かが何か間違ったことをしたらどうなりますか?例えば。

x, y = multRet(5);
multret(int x) {
    return x+1;
    exit;
}

または

x = multRet(5);
multret(int x) {
    return x+1;
    return x+1;
    exit;
}

コンパイラはそれをキャッチするはずですか?ループがあるとどうなりますか?

せいぜいこれは構文上の砂糖です。新しい機能を追加せずに、現在のパターンをより快適に、コードを短くするだけのもの。

最悪の場合、これはコードに恐ろしい、恐ろしいエラーを引き起こします。大量のヌルポインター例外。


1
コンパイラーは複数の戻り値を問題なくキャッチできます。これはGoプログラミング言語が行うことです。
slebetman

1

これは主に構文上の理由によるものです。あなた自身の例では、最初のreturnステートメントは1つの値を返して戻り、2番目のreturnステートメントには決して到達しません。

今日、タプル、つまりゼロ、任意のタイプの1つ以上の値を返し、単一の値にパッケージ化できる言語がいくつかあります。それでも、それらは1つの値を返していますが、それはもう少し複雑な値です。通常、そのような言語には、タプルの一部の要素を無視する簡単な方法で、タプルをゼロ、1つ以上の一致する変数に割り当てる方法もあります。これは次のようになります。

タプルを別のタプルに割り当てます。

x = multRet(5); // x is a variable of type tuple (int, int)
multret(int x) {
    return (x+1, x+2);
}

(x, y) = multRet(5); // x, y are variables of type int
multret(int x) {
    return (x+1, x+2);
}

(x, _) = multRet(5); // x is a variable of type int, second component ignored. 
multret(int x) {
    return (x+1, x+2);
}

その構文はCと互換性がありません。(x、y)は括弧内のコンマ式です。一方、2つの値を受け取ることは、ポインターを使用して2つの値を渡すか、この目的のために構造体を宣言し、それを入力して返すCの場合よりもはるかに簡単です。ポインターを使用すると、呼び出し元が1つの値のみに関心を持っている3番目の例は面倒なので、タプルを返すこのメソッドは非常に便利です。


Goは、戻り値がタプルではなく個別の値である1つの例です。Goでは2番目と3番目の例を実行できますが、最初の例ではコンパイル時エラーが発生します。コンパイラは2つの変数を想定していますが、1つしか取得しません。Goが選択した構文は、単純でreturn x, yあり、実行する戻り値を受け入れるためのものですx,y = multiRet()
slebetman

Goにはタプル型の変数がないということですか?
gnasher729 2016年

他のCのような言語と同様に、Goには構造体があり、Goにはほとんどのマップ言語(ハッシュ/連想配列)があるため、タプルを構造体またはマップとして実装できます。Goがよく見ないことの1つは、名前のない構造体である匿名構造体です。
slebetman 2016年

Swiftのような新しい言語にはタプルがあります。自分でタプルを実装する必要があると、ほとんどの利点がなくなります。
gnasher729 2016年

LISPでは一般的です。また、multiple-value-bindを使用して、各戻り値を変数に割り当てることもできます
Talk is Cheap Show Me Code
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.