なぜほとんどのプログラミング言語は、関数から単一の値を返すことしかサポートしないのですか?[閉まっている]


118

ほとんどのプログラミング言語の関数が、任意の数の入力パラメーターをサポートするように設計されていますが、戻り値は1つだけである理由はありますか?

ほとんどの言語では、その制限を「回避する」ことができます。たとえば、出力パラメータを使用したり、ポインタを返したり、構造体/クラスを定義/返したりすることができます。しかし、プログラミング言語が複数の戻り値をより「自然な」方法でサポートするように設計されていないことは奇妙に思えます。

これについての説明はありますか?


40
あなたは...配列を返すことができます原因
ネイサンヘイ

6
それでは、なぜ1つの引数だけを許可しないのですか?スピーチに関係しているのではないかと思います。いくつかのアイデアを取り入れて、耳を傾けるのに十分な幸運/不幸な人に戻れるように、それらを一つにしてください。リターンはほとんど意見のようです。「これ」は、「これら」で行うことです。
エリックReppen

30
pythonのアプローチは非常にシンプルでエレガントだと思います。複数の値を返す必要がある場合は、単にtuple:def f(): return (1,2,3)を返すだけで、tuple-unpackingを使用してtuple:を「分割」できますa,b,c = f() #a=1,b=2,c=3。配列を作成して要素を手動で抽出する必要はなく、新しいクラスを定義する必要もありません。
バクリウ

7
Matlabにはさまざまな数の戻り値があることをお知らせします。出力引数の数は、呼び出し元の署名([a, b] = f()vsなど[a, b, c] = f())によって決定され、内部で取得fされnargoutます。私はMatlabの大ファンではありませんが、これは実際には非常に便利です。
gerrit

5
ほとんどのプログラミング言語がそのように設計されている場合、議論の余地があると思います。プログラミング言語の歴史には、そのように作成された非常に人気のある言語(Pascal、C、C ++、Java、クラシックVBなど)がいくつかありましたが、今日では、複数のリターンを可能にする多くのファンを獲得している他の多くの言語もあります値。
Doc Brown

回答:


58

Pythonなどの一部の言語は複数の戻り値をネイティブにサポートしますが、C#などの一部の言語はベースライブラリを介してそれらをサポートします。

しかし、一般的に、それらをサポートする言語でさえ、複数の戻り値はだらしないので頻繁に使用されません:

  • 複数の値を返す関数を明確に命名するの困難です。
  • 戻り値の順序を間違えるのは簡単です

    (password, username) = GetUsernameAndPassword()  
    

    (これと同じ理由で、多くの人は関数に対してあまりにも多くのパラメーターを持つことを避けます。関数が同じ型のパラメーターを2つ持つべきではないと言う人もいます!)

  • OOP言語には、複数の戻り値に対するより良い代替手段であるクラスが既にあり ます。
    より厳密に型付けされており、戻り値を1つの論理ユニットとしてグループ化し、すべての用途で一貫した戻り値(のプロパティ)の名前を保持します。

彼らは一つの場所ですかなり便利では一つの関数から複数の戻り値は、別の複数の入力パラメータとして使用することができます(Pythonのような)言語です。しかし、これはクラスを使用するよりも優れた設計であるユースケースはかなりスリムです。


50
タプルを返すことは複数のことを返すと言うのは難しいです。1つのタプルを返します。あなたが書いたコードは、構文糖を使用してきれいに解凍します。

10
@レゴ:区別はありません-タプルは定義により複数の値です。そうでない場合、「複数の戻り値」とは何だと思いますか?
BlueRaja-ダニーPflughoeft

19
それは本当にあいまいな区別ですが、空のTupleを考えてください()。それは一つのことですか、それともゼロですか?個人的に、私はその一つのことを言うでしょう。を割り当てることができるx = ()ように、問題なく割り当てることができx = randomTuple()ます。後者では、返されたタプルが空かどうかにかかわらず、返されたタプルをに割り当てることができxます。

19
...タプルを他のものに使用できないと主張したことはありません。しかし、「Pythonは複数の戻り値をサポートせず、タプルをサポートする」と主張することは、非常に無意味です。これはまだ正しい答えです。
BlueRaja-ダニーPflughoeft

14
タプルもクラスも「複数の値」ではありません。
アンドレスF.

54

関数は、計算を実行して結果を返す数学的な構造であるためです。実際、いくつかのプログラミング言語の「内部」では、1つの入力と1つの出力のみに焦点が当てられています。凝集構造(またはタプル、またはMaybe)が出力になります(ただし、「単一の」戻り値は多くの値で構成されます)。

プログラマーはout、限られたシナリオのセットでのみ有用な厄介な構成要素であるパラメーターを見つけたため、これは変更されていません。他の多くのものと同様に、ニーズ/需要が存在しないため、サポートは存在しません。


5
@FrustratedWithFormsDesigner-これは最近の質問で少し出てきました。20年間で複数の出力が必要だった回数を数えることができます。
テラスティン

61
数学の関数とほとんどのプログラミング言語の関数は、2つの非常に異なる獣です。
-tdammers

17
初期の@tdammers、彼らは思考が非常に似ていました。Fortran、パスカルなど、コンピューティングアーキテクチャよりも数学の影響が大きい場所。

9
@tdammers-どうやって?ほとんどの言語では、最終的にラムダ計算に要約されます-1つの入力、1つの出力、副作用なし。それ以外はすべて、その上でのシミュレーション/ハックです。プログラミング関数は、複数の入力が同じ出力を生成するという意味で純粋ではないかもしれませんが、精神はそこにあります。
テラスティン

16
@SteveEvers:残念なことに、より適切な「手順」や「ルーチン」の代わりに、「関数」という名前が命令型プログラミングで引き継がれました。関数型プログラミングでは、関数は数学関数に非常に似ています。
tdammers

35

数学では、「明確に定義された」関数は、特定の入力に対して1つの出力しかない関数です(補足として、単一の入力関数しか持てず、カリーを使用して意味的に複数の入力を取得できます)。

複数値の関数(正の整数の平方根など)の場合、コレクションまたは値のシーケンスを返すだけで十分です。

あなたが話している関数のタイプ(すなわち、異なるタイプの複数の値を返す関数)については、あなたが思われるよりも少し異なっているように見えます:より良い設計またはより有用なデータ構造。たとえば、*.TryParse(...)メソッドがMaybe<T>outパラメーターを使用する代わりにモナドを返した場合、私は好むでしょう。F#でこのコードを考えてください:

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

コンパイラ/ IDE /分析のサポートは、これらの構成に対して非常に優れています。これにより、出力パラメータの「必要性」の多くが解決されます。完全に正直に言うと、これが解決策とならない他の方法は考えられません。

他のシナリオ(覚えていないもの)については、単純なTupleで十分です。


1
さらに、宣言されているvar (value, success) = ParseInt("foo");ためコンパイル時に型チェックされるC#:で記述できるようになりたいと思います(int, bool) ParseInt(string s) { }。私これがジェネリックでできることを知っていますが、それでも素晴らしい言語の追加になります。
絶望の顔をしかめる

10
@GrimaceofDespairは、複数の戻り値ではなく、構文を破壊することを本当に望んでいます。
ドメニック

2
@ウォーレン:はい。ここを参照し、そのような解決策は継続的ではないことに注意してください:en.wikipedia.org/wiki/Well-definition
スティーブンエバーズ

4
明確な定義の数学的概念は、関数が返す出力の数とは関係ありません。入力が同じ場合、出力は常に同じになることを意味します。厳密に言えば、数学関数は1つの値を返しますが、その値は多くの場合タプルです。数学者にとって、これと複数の値を返すこととの間に本質的な違いはありません。関数をプログラミングする引数は1つの値のみを返す必要があります。これは、数学関数が行う値があまり説得力がないためです。
マイケルサイラー

1
@MichaelSiler(私はあなたのコメントに同意する)しかし、その引数が可逆的である点に注意してください。:)「彼らは、単一のタプル値を返すことができるので、プログラムの機能は、複数の値を返すことができる引数は非常にいずれかの説得力はない」
アンドレス・F.

23

関数が戻るときにアセンブリで使用されるパラダイムを見たときに既に述べたことに加えて、関数は特定のレジスターに戻りオブジェクトへのポインターを残します。変数/複数レジスタを使用する場合、呼び出し側の関数は、その関数がライブラリ内にある場合に戻り値を取得する場所を知りません。そのため、ライブラリへのリンクが難しくなり、任意の数のリターナブルポインターを設定する代わりに、1つになりました。高レベルの言語には同じ言い訳はありません。


あ!非常に興味深い点です。+1!
スティーブンエバーズ

これは受け入れられた答えでなければなりません。通常、人々はコンパイラを構築するときにターゲットマシンについて考えます。もう1つのアナロジーは、int、float、char / stringなどがターゲットマシンでサポートされているためです。ターゲットがベアメタル(jvmなど)でなくても、エミュレートしすぎないようにして、まともなパフォーマンスを得たいと思っています。
imel96

26
...複数の値を関数に渡すための呼び出し規約を定義できるのとほぼ同じ方法で、関数から複数の値を返すための呼び出し規約を簡単に定義できます。これは答えではありません。-1
BlueRaja-ダニーPflughoeft

2
スタックベースの実行エンジン(JVM、CLR)が複数の戻り値を検討/許可したことがあるかどうかを知ることは興味深いでしょう。呼び出し側は、正しい数の引数をプッシュするように、正しい数の値をポップするだけです!
ロレンツォデマテ

1
@Davidいいえ、cdeclは(理論的に)無制限の数のパラメーターを許可します(そのため、可変引数関数が使用可能です)。一部のCコンパイラでは、関数ごとに数十または100の引数に制限される場合がありますが、それはまだ合理的だと思います-_-
BlueRaja-ダニーPflughoeft

18

過去に複数の戻り値を使用していた多くのユースケースは、最新の言語機能ではもはや必要ありません。エラーコードを返したいですか?例外をスローするか、を返しますEither<T, Throwable>。オプションの結果を返したいですか?を返しますOption<T>。いくつかのタイプのいずれかを返したいですか?Either<T1, T2>またはタグ付きユニオンを返します。

また、本当に複数の値を返す必要がある場合でも、現代の言語は通常、タプルまたは何らかのデータ構造(リスト、配列、辞書)またはオブジェクトをサポートします。複数の値を単一の値にした後、それを再び複数の値に分解するのは簡単です。

以下に、複数の値を返すことをサポートしない言語の例をいくつか示します。複数の戻り値のサポートを追加することで、新しい言語機能のコストを相殺するために、どのように表現力が大幅に向上するのか、実際にはわかりません。

ルビー

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

Python

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

スカラ

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

ハスケル

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

Perl6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

1
1つの側面は、一部の言語(Matlabなど)では、関数が返す値の数を柔軟に設定できるということです。上記の私のコメントを参照しください。Matlabには私が好きではない多くの側面がありますが、これはMatlabから例えばPythonに移植するときに見逃す数少ない(おそらく唯一の)機能の1つです。
gerrit

1
しかし、PythonやRubyなどの動的言語はどうでしょうか。Matlab sort関数のようなものを書いたとします:sorted = sort(array)ソートされた配列のみを[sorted, indices] = sort(array)返し、両方を返します。Pythonで考えることができる唯一の方法はsortsort(array, nout=2)またはの行に沿ってフラグを渡すことsort(array, indices=True)です。
gerrit

2
@MikeCellini私はそうは思いません。この関数は、多くの方法で伝えることができ、出力引数が関数が呼び出される(さ[a, b, c] = func(some, thing))し、それに応じて行動します。これは、たとえば、最初の出力引数を計算するのは安価であるが、2番目の出力引数を計算するのは費用がかかる場合に便利です。Matlabsに相当するものがランタイムnargoutで利用できる他の言語には慣れていません。
gerrit

1
Pythonでの正しい解決策は、次のように書くことですsorted, _ = sort(array)
マイルルーティング14

1
@MilesRout:そして、sort関数はインデックスを計算する必要がないことを伝えることができますか?それはクールです、私はそれを知りませんでした。
ヨルグWミットタグ14

12

単一の戻り値が非常に人気がある本当の理由は、非常に多くの言語で使用される式です。x + 1すでに単一の戻り値の観点から考えているような式を持つことができる言語では、式を断片に分割し、各断片の値を決定することで頭の式を評価するためです。見てx、その値が3(たとえば)であると判断し、1を見てから、x + 1そして、全体の値が4であることを決定するためにすべてをまとめる それは誰もが期待する表現の自然な意味です。関数が値のペアを返す場合でも、実際には2つの値の仕事をしている1つの値を返します。どういうわけか、単一のコレクションにラップされない2つの値を返す関数の考え方はあまりにも奇妙です。

人々は、関数が複数の値を返すために必要な代替のセマンティクスを扱いたくありません。たとえば、Forthのようなスタックベースの言語では、各関数が単にスタックの最上部を変更し、入力をポップし、出力を自由にプッシュするため、任意の数の戻り値を使用できます。そのため、Forthには通常の言語が持つ種類の表現がありません。

Perlは、通常はリストを返すと考えられている場合でも、関数が複数の値を返すように動作することがある別の言語です。Perlのリスト「補間」の方法は、Perlを(1, foo(), 3)知らないほとんどの人が期待するように3つの要素を持っているかもしれませんが、2つの要素、4つの要素、またはfoo()。Perlのリストは平坦化されているため、構文リストは常にリストのセマンティクスを持つとは限りません。それは単なる大きなリストの一部にすぎません。

関数が複数の値を返す別の方法は、任意の式が複数の値を持つことができ、各値が可能性を表す代替式セマンティクスを持つことです。取るx + 1再び、今度はそれが想像x二つの値は{3,4}、その後の値はx + 1{4,5}であろう、との値x + xになり、{6,8}、または多分{6,7,8} 、1つの評価でに複数の値を使用できるかどうかによって異なりますx。そのような言語は、Prologがクエリに複数の回答を与えるのと同じように、バックトラッキングを使用して実装できます。

要するに、関数呼び出しは単一の構文単位であり、単一の構文単位は、誰もが知っていて愛している式のセマンティクスにおいて単一の値を持っています。他のセマンティクスは、Perl、Prolog、Forthなどの奇妙な方法を強制します。


9

この回答で示唆されているように、これはハードウェアサポートの問題ですが、言語設計の伝統も役割を果たします。

関数が戻るとき、それは特定のレジスターに戻るオブジェクトへのポインターを残します

Fortran、Lisp、およびCOBOLの最初の3つの言語のうち、最初の言語では数学でモデル化された単一の戻り値が使用されました。2番目は、受信したのと同じ方法で任意の数のパラメーターをリストとして返しました(リストのアドレスである単一のパラメーターのみを渡して返したと主張することもできます)。3番目はゼロまたは1つの値を返します。

これらの最初の言語は、その後に続く言語の設計に大きな影響を与えましたが、複数の値を返すLispだけはあまり人気がありませんでした。

Cが登場したとき、Cはそれ以前の言語の影響を受けながら、ハードウェアリソースの効率的な使用に重点を置き、C言語が行ったこととそれを実装したマシンコードとの密接な関連を維持しました。「auto」変数と「register」変数などの最も古い機能の一部は、その設計哲学の結果です。

また、アセンブリ言語は80年代まで広く普及し、ついに主流の開発から段階的に廃止され始めたことも指摘する必要があります。コンパイラーを作成し、言語を作成した人々はアセンブリーに精通しており、ほとんどの場合、そこで最もうまく機能するものを維持していました。

この標準から逸脱した言語のほとんどは、あまり人気がなかったため、言語デザイナー(もちろん、彼らが知っていたことに触発された)の決定に影響を与える強力な役割を果たしたことはありません。

それでは、アセンブリ言語を調べてみましょう。最初に、Apple IIおよびVIC-20マイコンで有名な1975マイクロプロセッサである6502を見てみましょう。プログラミング言語のd明期の20、30年前の最初のコンピューターと比較すると強力でしたが、当時のメインフレームやミニコンピューターで使用されていたものと比較すると非常に弱かったです。

技術的な説明を見ると、5つのレジスタといくつかの1ビットフラグがあります。唯一の「フル」レジスタは、プログラムカウンタ(PC)でした。このレジスタは、実行される次の命令を指します。アキュムレータ(A)、2つの「インデックス」レジスタ(XおよびY)、およびスタックポインタ(SP)が存在するその他のレジスタ。

サブルーチンを呼び出すと、SPが指すメモリにPCが配置され、SPがデクリメントされます。サブルーチンから戻ることは逆に機能します。スタック上の他の値をプッシュおよびプルすることはできますが、SPに関連してメモリを参照することは困難であるため、再入可能なサブルーチンを記述することは困難でした。当然のことながら、サブルーチンの呼び出しはいつでも実行できると思いますが、このアーキテクチャではそれほど一般的ではありません。多くの場合、個別の「スタック」が作成され、パラメータとサブルーチンの戻りアドレスは別々に保たれます。

あなたは6502、インスピレーションを得たプロセッサを見れば6800を、それがSPから値を受け取ることができ、追加のレジスタ、インデックスレジスタ(IX)、など幅広いとしてSPを、持っていました。

マシン上で、リエントラントサブルーチンの呼び出しは、スタックにパラメーターをプッシュし、PCをプッシュし、PCを新しいアドレスに変更してから、サブルーチンがスタックにローカル変数をプッシュします。ローカル変数とパラメーターの数はわかっているため、それらのアドレス指定はスタックに関連して行うことができます。たとえば、2つのパラメーターを受け取り、2つのローカル変数を持つ関数は次のようになります。

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

すべての一時スペースがスタック上にあるため、何度でも呼び出すことができます。

8080、TRS-80に使用し、CP / Mベースのマイコンのホストは、スタック上にSPをプッシュして、そのレジスタ間接、HLにそれをポップし、6800に似た何かをすることができます。

これは物事を実装する非常に一般的な方法であり、簡単に戻る前にすべてのローカル変数をダンプするベースポインターを使用して、より新しいプロセッサでさらにサポートされました。

問題は、どのように何かを返すのですか?プロセッサのレジスタは初期の段階ではそれほど多くありませんでした。多くの場合、アドレス指定するメモリを見つけるために、それらのいくつかを使用する必要がありました。スタック上にあるものを返すのは複雑です。すべてをポップし、PCを保存し、返されたパラメーターをプッシュし(一方、どこに保存されますか?)、PCを再度プッシュして戻る必要があります。

それで、通常行われたのは 、戻り値用に1つのレジスタ予約する。呼び出しコードは、戻り値が特定のレジスターにあることを知っていたため、保存または使用できるようになるまで保存する必要があります。

複数の戻り値を許可する言語を見てみましょう:Forth。Forthが行うことは、個別のリターンスタック(RP)とデータスタック(SP)を保持することです。そのため、関数はすべてのパラメーターをポップし、戻り値をスタックに残します。戻りスタックは独立しているため、邪魔になりませんでした。

コンピューターでの最初の6か月の間にアセンブリ言語とForthを学んだ人として、複数の戻り値は完全に正常に見えます。/mod整数除算を返すForth'sなどの演算子残りは明らかです。一方で、初期の経験がCの心であった人がその概念を奇妙に感じる方法を簡単に見ることができます。

数学については...まあ、私は数学のクラスの関数に到達する前にコンピューターをプログラミングしていました。数学の影響を受けるCSおよびプログラミング言語のセクション全体があります、再度、そうでないセクション全体があります。

したがって、数学が初期の言語設計に影響を及ぼし、ハードウェアの制約が容易に実装されるものを決定し、一般的な言語がハードウェアの進化に影響を与えた要因の合流点があります(LispマシンとForthマシンプロセッサは、このプロセスのロードキルでした)。


@gnat「本質的な品質を提供する」などの「通知する」の使用は意図的なものでした。
ダニエルC.ソブラル

これについて強く感じたら、気軽にロールバックしてください。私の読書のあたりに影響がここに少し良いフィット:「影響...重要な方法で」
ブヨ

1
+1指摘されているように、現代のCPUの豊富なレジスタセット(x64 abiなどの多くのABIでも使用されている)と比較した初期CPUのまばらなレジスタカウントは、ゲームチェンジャーであり、以前は1つの値のみを返す説得力のある理由であった可能性があります今日は単なる歴史的な理由かもしれません。
-BitTickler

初期の8ビットマイクロCPUが言語設計に大きく影響し、どのアーキテクチャでもCまたはFortranの呼び出し規約で何が必要であると想定されているかを確信していません。Fortranは、配列引数(基本的にポインター)を渡すことができると想定しています。あなたの答えや「CからZ80コンパイラなぜ貧弱なコードを生成するのですか?retrocomputing.SEで。
ピーター

Fortranは、Cと同様に、任意の数の引数を渡すことができ、それらと任意の量のローカルにランダムにアクセスできると想定していますよね?先ほど説明したように、再入可能性を放棄しない限り、スタック相対アドレス指定は問題ではないため、6502では簡単に実行できません。それらを静的ストレージにポップできます。任意の引数リストを渡すことができる場合、レジスタに収まらない戻り値(たとえば、最初のもの以外)の追加の非表示パラメーターを追加できます。
ピーター

7

私が知っている関数型言語は、タプルを使用して複数の値を簡単に返すことができます(動的に型付けされた言語では、リストを使用することもできます)。タプルは他の言語でもサポートされています:

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

上記の例でfは、2つのintを返す関数です。

同様に、ML、Haskell、F#などもデータ構造を返すことができます(ほとんどの言語ではポインターが低レベルです)。このような制限がある現代のGP言語は聞いたことがありません。

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

最後に、out関数型言語でもパラメータをエミュレートできますIORef。ほとんどの言語でout変数のネイティブサポートがない理由はいくつかあります。

  • 不明なセマンティクス:次の関数は0または1を出力しますか?私は0を出力する言語と1を出力する言語を知っています。両方に利点があります(パフォーマンスの面でも、プログラマーのメンタルモデルに一致する点でも)。

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • ローカライズされていない効果:上記の例のように、長いチェーンを持つことができ、最も内側の関数がグローバル状態に影響することがわかります。一般に、関数の要件が何であるか、および変更が合法であるかどうかを推論するのが難しくなります。現代のほとんどのパラダイムは、効果のローカライズ(OOPでのカプセル化)または副作用の排除(関数型プログラミング)を試みるため、これらのパラダイムと矛盾します。

  • 冗長であること:タプルがある場合、outパラメーターの機能の99 %と慣用的な使用の100%があります。ミックスにポインターを追加すると、残りの1%をカバーします。

タプル、クラス、またはoutパラメーターを使用して複数の値を返すことができない1つの言語の名前付けに問題があります(ほとんどの場合、2つ以上のこれらのメソッドが許可されています)。


+1関数型言語がこれをエレガントで痛みのない方法で処理する方法について言及するため。
アンドレスF.

1
技術的には、まだ単一の値を返しています:D(この単一の値が複数の値に分解するのは簡単なことです)。
トーマスエディング

1
実際の「出力」セマンティクスを持つパラメーターは、メソッドが正常に終了したときに宛先にコピーされる一時的なコンパイラーとして動作するはずです。「inout」セマンティクス変数を持つものは、入り口で渡された変数からロードされ、出口で書き戻される一時的なコンパイラーとして動作する必要があります。「ref」セマンティクスを持つものはエイリアスとして動作する必要があります。C#のいわゆる「out」パラメーターは、実際には「ref」パラメーターであり、そのように動作します。
supercat

1
タプルの「回避策」も無料では提供されません。最適化の機会をブロックします。N個の戻り値をCPUレジスターに返すことができるABIが存在する場合、コンパイラーは、タプルインスタンスを作成して構築する代わりに、実際に最適化できます。
BitTickler

1
@BitTicklerは、ABIを制御する場合、レジスターによって渡される構造体の最初のnフィールドを返すことを妨げるものはありません。
マチェイピエチョトカ16

6

のような表現が原因だと思います(a + b[i]) * c

式は「特異な」値で構成されます。したがって、特異値を返す関数は、上記の4つの変数のいずれかの代わりに、式で直接使用できます。マルチ出力関数は、式では少なくとも多少不器用です。

私は個人的にはこれがあると感じ特異戻り値に関する特別だもの。式で使用する複数の戻り値のいずれかを指定するための構文を追加することでこれを回避できますが、簡潔で誰にとっても馴染みのある古き良き数学表記よりも不器用です。


4

構文が少し複雑になりますが、実装レベルで許可しない正当な理由はありません。他のいくつかの応答とは異なり、複数の値が返される場合(可能な場合)、より明確で効率的なコードになります。X Y、または「成功」ブール値有用な値を返すことを望む頻度を数えられません。


3
複数のリターンがより明確なおよび/またはより効率的なコードを提供する場所の例を提供できますか?
スティーブンエバーズ

3
たとえば、C ++ COMプログラミングでは、多くの関数に1つの[out]パラメーターがありますが、実質的にすべてがHRESULT(エラーコード)を返します。ペアを取得するだけで十分に実用的です。Pythonなどのタプルを適切にサポートする言語では、これは私が見た多くのコードで使用されます。
フェリックスドンベック

一部の言語では、X座標とY座標を持つベクターを返します。有用な値を返すと、例外として「成功」としてカウントされ、その有用な値を伝えて、失敗に使用されます。
-doppelgreener

3
多くの場合、非自明な方法で情報を戻り値にエンコードすることになります。負の値はエラーコード、正の値は結果です。ユク。ハッシュテーブルにアクセスすると、アイテムが見つかったかどうかを示し、アイテムを返すことも常に面倒です。
ddyer

@SteveEvers Matlab sort関数は通常、配列をソートしますsorted_array = sort(array)。時には対応するインデックスも必要です:[sorted_array, indices] = sort(array)。時にはインデックスだけが必要な場合があります:[~, indices]= sort(array). The function sort`は実際に必要な出力引数の数を知ることができるため、1と比較して2つの出力に追加の作業が必要な場合、必要な場合にのみそれらの出力を計算できます。
gerrit

2

関数がサポートされているほとんどの言語では、そのタイプの変数を使用できる場所であればどこでも関数呼び出しを使用できます。

x = n + sqrt(y);

関数が複数の値を返す場合、これは機能しません。Pythonなどの動的に型付けされた言語を使用すると、これを行うことができますが、ほとんどの場合、方程式の途中でタプルを使用して適切な処理を実行できない限り、ランタイムエラーがスローされます。


5
不適切な機能を使用しないでください。これは、値を返さない、または数値以外の値を返す関数によって引き起こされる「問題」と違いはありません。
ddyer

3
複数の戻り値を提供している言語(たとえば、SciLab)では、最初の戻り値に特権があり、1つの値のみが必要な場合に使用されます。そこに本当の問題はありません。
光子

そして、彼らがいないときでも、Pythonのタプル開梱と同じように、あなたが望むどのいずれかを選択することができますfoo()[0]
Izkata

正確には、関数が2つの値を返す場合、戻り値の型は1つの値ではなく2つの値になります。プログラミング言語はあなたの心を読むべきではありません。
マークE.ハーゼ

1

Harveyの答えを基にしたいだけです。私はもともとニューステックサイト(arstechnica)でこの質問を見つけ、この質問の核心に本当に答えていると感じ、他のすべての答えには欠けているという素晴らしい説明を見つけました(Harveyを除く)

関数からの単一リターンの起源は、マシンコードにあります。マシンコードレベルでは、関数はA(アキュムレータ)レジスタに値を返すことができます。他の戻り値はすべてスタック上にあります。

2つの戻り値をサポートする言語は、1つを返すマシンコードとしてコンパイルし、2番目の値をスタックに配置します。言い換えると、2番目の戻り値は、いずれにしてもoutパラメーターとして使用されます。

割り当てが一度に1つの変数である理由を尋ねるようなものです。たとえば、a、b = 1、2を許可する言語を使用できます。しかし、マシンコードレベルでは、a = 1に続いてb = 2になります。

プログラミング言語の構造に、コードがコンパイルされて実行されたときに実際に何が起こるかについて、ある程度の類似性を持たせることには、いくつかの理論的根拠があります。


Cのような低レベル言語がファーストクラス機能として複数の戻り値をサポートしている場合、C呼び出し規約には複数の戻り値レジスタが含まれます。x86で整数/ポインタ関数引数を渡すために最大6つのレジスタが使用されるのと同じです64 System V ABI。(実際、x86-64 SysVは、RDX:RAXレジスタペアにパックされた最大16バイトの構造体を返します。これは、構造体がロードされて格納される場合に適していますが、別のreg 64ビットより狭い場合。)
ピーター

明らかな慣例はRAXで、それから引数を渡すregsです。(RDI、RSI、RDX、RCX、R8、R9)。または、Windows x64規則では、RCX、RDX、R8、R9。ただし、Cにはネイティブに複数の戻り値がないため、C ABI /呼び出し規約では、ワイド整数と一部の構造体に対して複数の戻りレジスタのみを指定します。ARM®アーキテクチャーのプロシージャー呼び出し標準: ARMで2つの戻り値を受け取るための効率的なasmをコンパイラーに取得させるためのwide intの使用例については、2つの別個の関連する戻り値を参照してください。
ピーター

-1

それは数学から始まりました。「式変換」にちなんで命名されたFORTRANは、最初のコンパイラでした。FORTRANは、物理学/数学/工学を指向していた。

COBOLは、ほぼ古いものですが、明示的な戻り値がありませんでした。サブルーチンはほとんどありませんでした。それ以来、ほとんどがinertia性でした。

たとえば、Goには複数の戻り値があり、結果は "out"パラメーターを使用するよりも簡潔であいまいではありません。少し使用した後、非常に自然で効率的です。すべての新しい言語について、複数の戻り値を考慮することをお勧めします。たぶん古い言語にも。


4
これは聞かれる質問に答えていません
ブヨ

@gnatは私としては答えます。大藤一つはそれを理解するためにいくつかの背景を必要とし、既に持っている、そしてその人はおそらく...質問をしないであろう
Netch

@Netch は、「FORTRAN ...が最初のコンパイラだった」などのステートメントが完全に混乱していることを理解するのに、ほとんど背景を必要としません。それは間違っていません。ただ、この「答え」の残りの部分のような
ブヨ

リンクは、コンパイラの初期の試みがあったが、「IBMのジョンバックスが率いるFORTRANチームは、1957年に最初の完全なコンパイラを導入したと一般に信じられている」と述べています。尋ねられた質問は、なぜたった1つだけだったのですか?私の言ったように。それはほとんど数学と慣性でした。「関数」という用語の数学的な定義には、正確に1つの結果値が必要です。それでおなじみの形でした。
RickyS

-2

おそらく、プロセッサマシン命令で関数呼び出しが行われるというレガシーと、すべてのプログラミング言語がマシンコードから派生しているという事実(たとえば、C->アセンブリ->マシン)に関係があると思われます。

プロセッサが関数呼び出しを実行する方法

最初のプログラムはマシンコードで記述され、その後アセンブリされました。プロセッサは、現在のすべてのレジスタのコピーをスタックにプッシュすることにより、関数呼び出しをサポートしました。関数から戻ると、保存されたレジスタのセットがスタックからポップされます。通常、返される関数が値を返すことができるように、1つのレジスタは変更されません。

さて、プロセッサがこのように設計された理由については...リソースの制約の問題である可能性があります。

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