コンピュータ プログラミングでは、代入文は変数名で示される記憶場所に格納されている値を設定またはリセットします。つまり、値を変数にコピーします。ほとんどの命令型プログラミング言語では、代入文 (または式) は基本的な構成要素です。
現在、この操作で最も一般的に使用されている表記法は(元々はSuperplan 1949–51で、Fortran 1957とCで普及した)です。2番目によく使用される表記法は[1](元々はALGOL 1958で、Pascalで普及した)です。[2]他にも多くの表記法が使用されています。一部の言語では、使用されるシンボルは演算子と見なされます(つまり、代入文全体が値を返します)。他の言語では、代入は文として定義されます(つまり、式では使用できません)。
x = expr x := expr
代入により、変数は、その有効期間と有効範囲の異なる時点で異なる値を保持できます。ただし、一部の言語 (主に厳密に関数型言語) では、非ローカル状態の変更を意味する可能性があるため、そのような「破壊的な」再代入は許可されません。目的は、参照の透明性、つまり、一部の変数の状態には依存せず、任意の時点で特定のパラメーター入力セットに対して同じ結果を生成する関数を強化することです。他の言語の最新のプログラムでも、複雑さを軽減するために、通常はデータ構造化、構造化プログラミング、オブジェクト指向などの補完的な方法論と組み合わせて、同様の戦略が使用されることがよくありますが、厳密さは低く、特定の部分のみです。
セマンティクス
代入演算は、命令型プログラミングにおける処理であり、時間の経過とともに特定の変数名に異なる値が関連付けられる。[1]このようなモデルでは、プログラムは連続した代入文を使用して状態を変更することで動作する。[2] [3]命令型プログラミング言語のプリミティブは、反復処理を実行するために代入に依存している。[4]最も低いレベルでは、代入はやなどの機械演算を使用して実装される。[2] [4]MOVESTORE
変数は値を入れる容器です。変数に値を入れて、後で新しい値に置き換えることができます。代入操作は実行中のプログラムの現在の状態を変更します。[3]したがって、代入は変数の概念に依存します。代入では、
- は
expressionプログラムの現在の状態で評価されます。 - 計算された値
variableが割り当てられ、その変数の以前の値が置き換えられます。
例: がa数値変数であると仮定すると、代入は、ステートメントの実行後に
a := 2*a変数の内容が 2 倍になることを意味します。a
Cコードの例:
int x = 10 ; float y ; x = 23 ; y = 32.4f ;
このサンプルでは、変数はx最初に int として宣言され、次に 10 という値が割り当てられます。宣言と割り当てが同じステートメントで行われることに注意してください。2 行目では、y割り当てなしで宣言されています。3 行目では、x23 という値が再割り当てされています。最後に、y32.4 という値が割り当てられています。
代入操作では、 の値がexpression明確に定義されていること (有効なrvalueであること)、および がvariable変更可能なエンティティを表していること (有効な変更可能な (非const ) lvalueであること) が必要です。一部の言語 (通常は動的言語) では、変数に値を割り当てる前に宣言する必要はありません。このような言語では、変数は最初に割り当てられたときに自動的に宣言され、宣言されたスコープは言語によって異なります。
単一割り当て
既存の値を変更する代入(例x := x + 1:)は、純粋に関数型の言語では許可されていません。[4]関数型プログラミングでは、代入よりも単一の代入(より一般的には初期化)が推奨されています。単一の代入は名前バインディングの例であり、この記事で説明する代入とは異なり、通常は変数が作成されるときに一度しか実行できず、その後の再代入は許可されません。
式の評価は、結果を生成すること以外にマシンの観測可能な状態を変更せず、同じ入力に対して常に同じ値を生成する場合、 [5]副作用はありません。 [4]命令型代入は、古い値を破壊して使用できなくしながら新しい値に置き換えるときに副作用を引き起こす可能性があり、[6] LISPや関数型プログラミングでは破壊的更新と同様に破壊的代入と呼ばれています。
単一代入は、 Haskellなどの純粋関数型言語で使用できる唯一の代入形式です。Haskellには、命令型プログラミング言語[4]の意味での変数はありませんが、遅延評価言語では、要素がオンデマンドで段階的に定義される、複合的な性質を持つ可能性のある名前付き定数値があります。純粋関数型言語では、値が互いに独立しているため、一度に1ステップずつ順次実行するフォン・ノイマン・ボトルネックを回避し、並列で計算を実行する機会を提供できます。 [7]
不純な関数型言語では、単一代入と真の代入の両方が提供されています (ただし、真の代入は通常、命令型プログラミング言語よりも使用頻度が低くなります)。たとえば、Scheme では、単一代入 ( を使用let) と真の代入 ( を使用set!) の両方をすべての変数で使用でき、リスト、ベクター、文字列などの内部での破壊的な更新用に特殊なプリミティブが提供されています。OCaml では、構文を介して変数に単一代入のみが許可されています。ただし、破壊的な更新は、配列と文字列の要素に対しては別の演算子を使用して使用できます。また、プログラマによって明示的に変更可能 (つまり、最初の宣言後に変更できる) と宣言されたレコードとオブジェクトのフィールドに対しても使用できます。
let name = value<-
単一代入を使用する関数型プログラミング言語には、Clojure (データ構造用、変数用ではない)、Erlang (Haskell とは異なり、値が等しい場合は複数代入を受け入れる)、F#、Haskell、JavaScript (定数用)、 Lava、OCaml、Oz (データフロー変数用、セル用ではない)、Racket (リストなどの一部のデータ構造用、シンボル用ではない)、SASL、Scala (変数用)、SISAL、Standard ML などがあります。バックトラックのない Prologコードは明示的な単一代入と見なすことができます。明示的とは、その (名前付き) 変数が明示的に未代入の状態にあるか、正確に 1 回設定できるという意味です。 これに対して Haskell では、未代入の変数は存在できず、すべての変数は、作成時にその値 (または、要求に応じて値を生成する計算オブジェクト) に暗黙的に設定されると考えることができます。
割り当ての価値
一部のプログラミング言語では代入文は値を返しますが、他の言語では値は返しません。
ほとんどの式指向プログラミング言語(たとえば、C ) では、代入文は割り当てられた値を返すため、 などの慣用句が可能になります。x = y = aこの場合、代入文はy = aの値を返しa、その後、その値が に割り当てられますx。 などの文では、関数の戻り値を使用して、同じ値を変数に代入しながらループを制御します。
while ((ch = getchar()) != EOF) {…}
他のプログラミング言語、たとえばSchemeでは、代入の戻り値は未定義であり、このような慣用句は無効です。
Haskell [8]では変数の代入はありませんが、代入に似た操作(配列のフィールドや可変データ構造のフィールドへの代入など)は通常、ユニット型 と評価され、と表されます()。この型は可能な値が1つしかないため、情報は含まれていません。これは通常、副作用のみについて評価される式の型です。
譲渡のさまざまな形式
特定の使用パターンは非常に一般的であるため、多くの場合、それらをサポートする特別な構文があります。これらは主に、ソース コードの冗長性を削減するための構文糖ですが、コードを読む人がプログラマーの意図を理解するのに役立ち、コンパイラーに最適化のヒントを提供します。
拡張割り当て
代入された値が以前の値に依存するケースは非常に一般的であるため、多くの命令型言語、特にC言語とその子孫の大部分では、のような拡張代入*=と呼ばれる特殊な演算子が提供されており、a = 2*a代わりに と記述できますa *= 2。[3]これは、構文上の糖衣を超えて、変数のインプレース変更が可能であることを明確にすることで、コンパイラのタスクを支援しますa。
連鎖割り当て
のような文は連鎖代入w = x = y = zと呼ばれ、 の値が複数の変数とに割り当てられます。連鎖代入は、次のように複数の変数を初期化するためによく使用されます。
zw, x,y
a = b = c = d = f = 0
すべてのプログラミング言語が連鎖代入をサポートしているわけではありません。連鎖代入は代入のシーケンスに相当しますが、評価戦略は言語によって異なります。複数の変数を初期化するなどの単純な連鎖代入の場合、評価戦略は重要ではありませんが、代入内のターゲット (左辺値) が何らかの方法で接続されている場合は、評価戦略が結果に影響します。
一部のプログラミング言語 (たとえばC ) では、代入が式であり、値を持つため、連鎖代入がサポートされています。この場合、連鎖代入は右結合代入によって実装でき、代入は右から左に行われます。たとえば、i = arr[i] = f()は と同等です。C ++arr[i] = f(); i = arr[i]では、代入演算子に適切な戻り値の型を宣言することで、クラス型の値にも使用できます。
Pythonでは、代入文は式ではないため、値を持ちません。代わりに、連鎖代入は、単一の式に対して複数のターゲットを持つ一連の文です。代入は左から右に実行されるため、i = arr[i] = f()式 を評価しf()、その結果を最も左のターゲット に代入しi、同じ結果を のarr[i]新しい値を使用して次のターゲット に代入しますi。[9]これは本質的に と同等ですtmp = f(); i = tmp; arr[i] = tmpが、一時的な値に対して実際の変数は生成されません。
並列割り当て
APL、Common Lisp、[10] Go、[11] JavaScript(1.7以降)、PHP、Maple、Lua、occam 2、[12] Perl、[13] Python、[14] REBOL、Ruby、[15] PowerShellなどのプログラミング言語では、次のような構文を使用して複数の変数を並列に割り当てることができます。
a, b := 0, 1
これは に 0 をa、 に 1 を同時に代入します。これは並列代入bとして最もよく知られています。これは 1963 年にCPLで同時代入という名前で導入されました[16] 。多重代入と呼ばれることもありますが、「単一代入」と一緒に使用すると混乱を招きます。これらは反対語ではないからです。代入の右側が単一の変数 (配列や構造体など) である場合、この機能はアンパック[17]または構造化代入と呼ばれます。[18]
変数リスト:={0,1}
a, b := リスト
リストは展開され、 に 0 がa、 に 1 が割り当てられますb。さらに、
a, b := b, a
aとの値を入れ替えますb。並列割り当てのない言語では、これは一時変数を使用するように記述する必要があります。
var t := a a := b b := t
との両方が の元の値のa := b; b := aままになるからです。
abb
Go、F#、Pythonなどの言語では、並列割り当て、タプル、自動タプルアンパックを組み合わせて、1つの関数から複数の戻り値を返すことができます。次のPythonの例をご覧ください。
def f ():
1 , 2 a , b = f ()を返す
一方、ここに示すC#やRustなどの他の言語では、括弧を使用した明示的なタプルの構築と分解が必要です。
// 有効な C# または Rust 構文
( a , b ) = ( b , a );
// C# タプル return
( string , int ) f () => ( "foo" , 1 ); var ( a , b ) = f ();
// Rust タプル return
let f = || ( "foo" , 1 ); let ( a , b ) = f ();
これは、関数から複数の値を返すために出力パラメータを使用する代わりになるものです。これはCLU (1974) に遡り、CLU は並列割り当ての一般的な普及に貢献しました。
C#では、さらに、右辺の式で定義された実装による一般化された分解代入が可能であり、コンパイラは式上で適切なインスタンスまたは拡張 Deconstructメソッドを検索し、代入先の変数の出力パラメータを持っている必要があります。[19]たとえば、上記の戻り値と同じ動作をクラスに与えるメソッドの1つは、次のf()ようになります。
void分解( out string a 、out int b ) { a = "foo" ; b = 1 ; }
C および C++ では、カンマ演算子は並列代入に似ており、1 つのステートメント内で複数の代入を実行できるため、a = 1, b = 2の代わりに と記述します。これは主にfor ループa, b = 1, 2で使用され、Go などの他の言語では並列代入に置き換えられています。[20]
ただし、上記の C++ コードでは、次のコードの右側が左側の後に評価されるため、完全な同時性は保証されません。Python などの言語では、2 つの変数を同時に割り当て、a の初期値を使用して新しい b を計算します。
a = b, b = a+1a, b = b, a+1
割り当てと等価性
等号を=代入演算子として使用することは、等号の比較演算子としての等号と競合するため、頻繁に批判されてきました。その結果、初心者がコードを書くときに混乱が生じ、経験豊富なプログラマでさえコードを読むときに混乱が生じます。等号を代入に使用することは、 1949 年から 1951 年にかけて設計されたHeinz Rutishauserの言語Superplanにまで遡り、特に Fortran によって普及しました。
悪いアイデアの悪名高い例は、代入を表すために等号を選んだことです。これは 1957 年の Fortran にまで遡ります[a]。そして、多くの言語設計者によって盲目的にコピーされてきました。なぜ悪いアイデアなのでしょうか。それは、等号の比較、つまり真か偽かの述語を表すために「=」を使用するという 100 年にわたる伝統を覆すからです。しかし、Fortran では、等号を代入、つまり等号の強制を意味するようにしました。この場合、オペランドは不平等な立場にあります。つまり、左オペランド (変数) は右オペランド (式) と等しくなります。x = y は y = x と同じ意味ではありません。[21]
— ニクラウス・ヴィルト、『良いアイデア』『鏡の国のアリス』
初心者のプログラマーは、代入と等価性の関係演算子を混同することがあります。これは、「=」が数学では等価性を意味し、多くの言語で代入に使用されるためです。ただし、代入は変数の値を変更するのに対し、等価性テストは 2 つの式が同じ値を持つかどうかをテストします。
BASICなどの一部の言語では、単一の等号("=")が代入演算子と等価関係演算子の両方に使用され、文脈によってどちらを意味するかが決まります。他の言語では、2つの演算子に異なる記号を使用します。[22]例:
- ALGOLとPascalでは、代入演算子はコロンと等号 (
":=") であり、等号演算子は単一の等号 ("=") です。 - Cでは、代入演算子は単一の等号 (
"=") ですが、等号演算子は等号のペア ("==") です。 - Rでは、代入演算子は基本的に
<-、 と同じですx <- valueが、特定のコンテキストでは単一の等号を使用することもできます。
2 つのシンボルの類似性は、プログラマがどの形式 (" ="、" =="、" :=") が適切かを忘れたり、=" " を意図していたのに " "を誤って入力したりすると、エラーにつながる==可能性があります。これは、C などの言語 (Linux カーネルのバックドアを狙った有名な試みを含む) でよく見られるプログラミングの問題です。[23] C では、代入演算子も割り当てられた値を返します (関数が値を返すのと同じ方法)。また、式の中に有効にネストできます。ifたとえば、ステートメント内で 2 つの値を比較することが意図されていた場合、代入はブール値の true として解釈できる値を返す可能性が非常に高く、その場合、節が実行され、プログラムが予期しない動作をします。一部の言語プロセッサ ( gccthenなど) は、このような状況を検出し、潜在的なエラーについてプログラマに警告します。[24] [25]
表記
コピー代入の最も一般的な 2 つの表現は、等号( =) とコロン付き等号 ( ) です。どちらの形式も、言語や使用法に応じて、意味的には代入文または代入演算子(値も持つ)
:=のいずれかを表します。
他の可能性としては左矢印やキーワードなどがありますが、よりまれなバリエーションも存在します。
数学的な擬似コードの割り当ては、通常、左矢印で表されます。
一部のプラットフォームでは、式を左側に、変数を右側に配置します。
Lisp [34] [35]やTclなどの式指向言語では、代入を含むすべての文に前置構文(または後置構文)を一様に使用します。
参照
注記
- ^ は Fortran より前から使われていました
=が、Fortran によって普及しました。
参考文献
- ^ ab "2cs24 Declarative". www.csc.liv.ac.uk . 2006年4月24日時点のオリジナルよりアーカイブ。2018年4月20日閲覧。
- ^ abc 「命令型プログラミング」。uah.edu。2016年3月4日時点のオリジナルよりアーカイブ。2018年4月20日閲覧。
- ^ abc Ruediger-Marcus Flaig (2008). Python によるバイオインフォマティクスプログラミング: 初心者のための実践コース. Wiley-VCH. pp. 98–99. ISBN 978-3-527-32094-3. 2010年12月25日閲覧。
- ^ abcde 国境を越えて: Haskell で関数型プログラミングを探る 2010 年 11 月 19 日アーカイブ、Wayback Machine、Bruce Tate 著
- ^ ミッチェル、ジョン C. (2003)。プログラミング言語の概念。ケンブリッジ大学出版局。p. 23。ISBN 978-0-521-78098-8. 2011年1月3日閲覧。
- ^ 「命令型プログラミング言語 (IPL)」(PDF)。gwu.edu 。 2011年7月16日時点のオリジナル(PDF)からアーカイブ。2018年4月20日閲覧。
- ^ John C. Mitchell (2003). プログラミング言語の概念。ケンブリッジ大学出版局。pp. 81–82。ISBN 978-0-521-78098-8. 2011年1月3日閲覧。
- ^ Hudak, Paul (2000). Haskell School of Expression: マルチメディアによる関数型プログラミングの学習ケンブリッジ: ケンブリッジ大学出版局. ISBN 0-521-64408-9。
- ^ 「7. 単純なステートメント — Python 3.6.5 ドキュメント」。docs.python.org 。 2018 年4 月 20 日閲覧。
- ^ 「CLHS: マクロ SETF、PSETF」。Common Lisp Hyperspec。LispWorks。2019年4月23日閲覧。
- ^ Goプログラミング言語仕様: 割り当て
- ^ INMOS Limited 編 (1988)。Occam 2リファレンスマニュアル。ニュージャージー州: Prentice Hall。ISBN 0-13-629312-3。
- ^ Wall, Larry ; Christiansen, Tom; Schwartz, Randal C. (1996). Perlプログラミング言語(第2版). Cambridge: O´Reilly. ISBN 1-56592-149-6。
- ^ Lutz, Mark (2001). Pythonプログラミング言語(第2版). セバストポル: O´Reilly. ISBN 0-596-00085-5。
- ^ Thomas, David; Hunt, Andrew (2001). 『プログラミング Ruby: 実用的なプログラマーのためのガイド』 Upper Saddle River: Addison Wesley. ISBN 0-201-71089-7。
- ^ DW Barron他、「CPL の主な特徴」、Computer Journal 6 :2:140 (1963)。全文 (購読)
- ^ 「PEP 3132 -- 拡張反復可能アンパック」。legacy.python.org 。2018年4月20日閲覧。
- ^ 「構造化代入」。MDN Web Docs 。 2018年4月20日閲覧。
- ^ 「タプルとその他の型の分解」。Microsoft Docs。Microsoft。2019年8月 29 日閲覧。
- ^ Effective Go: for、「最後に、Go にはカンマ演算子がなく、++ と -- は式ではなくステートメントです。したがって、for で複数の変数を実行する場合は、並列割り当てを使用する必要があります (ただし、++ と -- は使用できません)。」
- ^ ニクラウス・ヴィルト。「鏡を通して見る良いアイデア」。CiteSeerX 10.1.1.88.8309。
- ^ 「C++プログラミング言語。基礎」。ntu.edu.sg。2013年6月1日。 2024年6月21日閲覧。
- ^ Corbet (2003年11月6日). 「カーネルへのバックドア攻撃の試み」lwn.net . 2024年6月21日閲覧。
- ^ 「静的アナライザーオプション(GNU コンパイラコレクション(GCC)の使用)」。gcc.gnu.org 。 2024年6月21日閲覧。
- ^ Deitel, Paul; Deitel, Harvey (2022-10-25). 「C++ 制御ステートメント、パート 2」。Domyassignments 。2024年 6 月 21 日閲覧。
- ^ ムーア、ローリー (1980)。『Pascal によるプログラミングの基礎』。ニューヨーク: John Wiley & Sons。ISBN 0-470-26939-1。
- ^ マイヤー、バートランド(1992)。『エッフェルという言語』ヘメル・ヘムステッド:プレンティス・ホール・インターナショナル(英国)。ISBN 0-13-247925-7。
- ^ Wiener, Richard (1996). Eiffel を使用したオブジェクト指向コンピュータサイエンス入門。アッパーサドルリバー、ニュージャージー: Prentice Hall。ISBN 0-13-183872-5。
- ^ Feinberg, Neal; Keene, Sonya E.; Mathews, Robert O.; Withington, P. Tucker (1997). Dylan Programming . マサチューセッツ州: Addison Wesley. ISBN 0-201-47976-1。
- ^ 「PEP 572 – 代入式」. python.org . 2018年2月28日. 2020年3月4日閲覧。
- ^ 「Goプログラミング言語仕様 - Goプログラミング言語」golang.org . 2018年4月20日閲覧。
- ^ Ullman, Jeffrey D. (1998). Elements of ML Programming: ML97 Edition . ニュージャージー州エングルウッドクリフス: Prentice Hall. ISBN 0-13-790387-1。
- ^ アイバーソン、ケネス E. (1962)。プログラミング言語。ジョン ワイリー アンド サンズ。ISBN 0-471-43014-5. 2009年6月4日時点のオリジナルよりアーカイブ。2010年5月9日閲覧。
- ^ グレアム、ポール(1996)。ANSI Common Lisp。ニュージャージー州: プレンティスホール。ISBN 0-13-370875-6。
- ^ Steele, Guy L. (1990). Common Lisp: 言語. レキシントン: Digital Press. ISBN 1-55558-041-6。
- ^ Dybvig, R. Kent (1996). Scheme プログラミング言語: ANSI Scheme . ニュージャージー: Prentice Hall. ISBN 0-13-454646-6。
- ^ スミス、ジェリー D. (1988) 。Scheme 入門。ニュージャージー州: プレンティス ホール。ISBN 0-13-496712-7。
- ^ アベルソン、ハロルド、サスマン、ジェラルド・ジェイ、サスマン、ジュリー(1996)。コンピュータプログラムの構造と解釈。ニュージャージー:マグロウヒル。ISBN 0-07-000484-6。
