代入演算子に値を返すことの利点は何ですか?


27

JavascriptとPHPの両方を置き換える言語を開発しています。(これで問題が発生することはありません。これらの言語のいずれにも大規模なインストールベースがあるわけではありません。)

変更したいことの1つは、代入演算子を代入コマンドに変え、戻り値を使用する機能を削除することでした。

x=1;          /* Assignment. */
if (x==1) {}  /* Comparison. */
x==1;         /* Error or warning, I've not decided yet. */
if (x=1) {}   /* Error. */

これは、Cの人々が愛している1行関数が機能しなくなることを意味します。私の個人的な経験を超える証拠はほとんどありませんでしたが、これが起こったのはほとんどの場合、実際に比較操作を目的としたものでした。

またはそれは?代入演算子の戻り値の実用的な用途は、簡単に書き直すことができませんか?(このような概念を持つ言語の場合。)


12
JSとPHPには大きな「インストールベース」がありませんか?
mhr

47
@mri皮肉を疑います。
アンディハント

12
私が覚えている唯一の有用なケースはwhile((x = getValue()) != null) {}です。割り当てを使用するbreakか繰り返す必要があるため、置換はいものになりますx = getValue
CodesInChaos 14

12
@mri Oohいいえ、これらの2つの言語はささいなものであり、大きな投資はまったくないと聞いています。JSの使用を主張する少数の人々が私の言語を見ると、彼らは私の言語に切り替わり、再び===を書く必要がなくなります。同様に、ブラウザのメーカーは、JSとともに私の言語を含むアップデートをすぐに展開することを確信しています。:)
billpg

4
既存の言語を強化することを意図しており、それを広く採用することを意図している場合、既存の言語との100%の下位互換性が良いことをお勧めします。例としてTypeScriptを参照してください。既存の言語のより良い代替物を提供することを目的とする場合は、はるかに難しい問題が発生します。新しい言語は、切り替えの費用を支払うために、既存の現実的な問題を既存の言語よりはるかによく解決する必要があります。言語を学ぶことは投資であり、それは報われる必要があります。
エリックリッパー

回答:


25

技術的には、一般的な操作の読みやすさを改善する場合は、わずかに置き換えることができる場合でも、一部の構文糖は保持する価値があります。しかし、式としての割り当てはそれに該当しません。比較の代わりにそれをタイプミスする危険性は、それがめったに使用されないことを意味し(スタイルガイドによって禁止されることさえあります)、使用されるたびにダブルテイクを引き起こします。言い換えれば、読みやすさの利点は数も規模も小さい。

これを行う既存の言語を調べることは価値があるかもしれません。

  • JavaとC#は式の割り当てを維持しますが、ブール値に評価する条件を要求することで、言及した落とし穴を取り除きます。これはほとんどの場合うまく機能しているようですが、人々は時折、これif (x)がの代わりに、if (x != null)またはif (x != 0)そのタイプに依存する条件を許可しないと不満を言うのxです。
  • Pythonは、式ではなく適切なステートメントを割り当てにします。これを変更する提案は、時々python-ideasメーリングリストに届きますが、私の主観的な印象は、do-whileループ、switchステートメント、複数行ラムダなどの他の「欠落」機能と比較して、これはまれにしか発生せず、毎回発生するノイズが少ないという印象です等

ただし、Pythonでは、一度に複数の名前に割り当てる特別なケースを1つ許可していますa = b = c。これはに相当するステートメントと見なされ、b = c; a = bときどき使用されるため、言語に追加する価値があるかもしれません(ただし、この追加には下位互換性があるため、気にしません)。


5
a = b = c他の回答では実際には表示されないものを表示するための+1 。
レオ

6
3番目の解決策は、割り当てに異なるシンボルを使用することです。Pascalは:=割り当てに使用します。
ブライアン

5
@ブライアン:確かに、C#もそうです。=は割り当てで==あり、比較です。
マルジャンヴェネマ14

3
C#では、次のようなものif (a = true)C4706警告(The test value in a conditional expression was the result of an assignment.)をスローします。Cを含むGCCも同様にwarning: suggest parentheses around assignment used as truth value [-Wparentheses]。これらの警告は、追加のかっこで沈黙させることができますが、割り当てが意図的なものであることを明示的に示すことを促すためにあります。
ボブ

2
ただ、@delnanやや一般的なコメントが、それは「ブールに評価するための条件を必要とすることによって、あなたが言及落とし穴を削除する」に端を発したた- a = true ブールに評価するため、エラーではありませんが、それはまた、C#で、関連する警告を発生させます。
ボブ

11

代入演算子の戻り値の実用的な用途は、簡単に書き直すことができませんか?

一般的に言えば、いいえ。割り当て式の値を割り当てられた値にするという考えは、副作用とその値の両方に使用できる式があり、多くの人が混乱していると考えていることを意味します。

一般的な使用法は、通常、式をコンパクトにすることです。

x = y = z;

C#で「zをyの型に変換し、変換された値をyに割り当て、変換された値が式の値であり、それをxの型に変換し、xに割り当てる」というセマンティクスを持ちます。

しかし、私たちはすでに文のコンテキストで命令型の副作用の領域にいるので、それ以上に本当に魅力的な利点はほとんどありません

y = z;
x = y;

同様に

M(x = 123);

の略記であること

x = 123;
M(x);

繰り返しますが、元のコードでは、副作用と値の両方に式を使用しており、1つではなく2つの副作用を持つステートメントを作成しています。両方とも臭いです。ステートメントごとに1つの副作用を持たせ、副作用ではなく値に式を使用してください。

JavascriptとPHPの両方を置き換える言語を開発しています。

あなたが本当に大胆になり、割り当てが平等ではなくステートメントであることを強調したい場合、私のアドバイスは次のとおりです。明確に割り当てステートメントにする

let x be 1;

できました。または

x <-- 1;

またはさらに良い:

1 --> x;

またはさらに良い

1 → x;

それらのいずれかと混同されることは絶対にありませんx == 1


1
プログラミング言語の非ASCII Unicodeシンボルの世界は準備ができていますか?
billpg

私があなたの提案を気に入ってくれる限り、私の目標の1つは、ほとんどの「よく書かれた」JavaScriptをほとんどまたはまったく変更せずに移植できることです。
billpg

2
@billpg:世界は準備ができていますか?私は知りません-Unicodeが発明される数十年前の1964年に世界はAPLに対応していましたか?最初の40個から6個の数字のランダムな順列を選択するAPLのプログラムは次のとおりですx[⍋x←6?40] 。APLは独自の特別なキーボードを必要としましたが、かなり成功した言語でした。
エリックリッパー

@billpg:Macintosh Programmer's Workshopは、regexタグやstderrのリダイレクトなどに非ASCIIシンボルを使用していました。一方、MPWには、Macintoshで非ASCII文字を簡単に入力できるという利点がありました。米国のキーボードドライバーが非ASCII文字を入力する適切な手段を提供していない理由については、いくつかの困惑を告白しなければなりません。Alt番号の入力では、文字コードを検索する必要があるだけでなく、多くのアプリケーションでは機能しません。
supercat

うーん、なぜ「右に」を割り当てるのが好きa+b*c --> xでしょうか?これは私には奇妙に見えます。
ルスラン

9

Pythonを含む多くの言語は、代入を式ではなくステートメントにする方法を選択します。

foo = 42 # works
if foo = 42: print "hi" # dies
bar(foo = 42) # keyword arg

およびGolang:

var foo int
foo = 42 # works
if foo = 42 { fmt.Printn("hi") } # dies

他の言語には割り当てはありませんが、OCamlなどのスコープ付きバインディングがあります。

let foo = 42 in
  if foo = 42 then
    print_string "hi"

ただし、let式そのものです。

割り当てを許可することの利点は、たとえば次のPerlスニペットのように、条件内の関数の戻り値を直接確認できることです。

if (my $result = some_computation()) {
  say "We succeeded, and the result is $result";
}
else {
  warn "Failed with $result";
}

Perlはさらに、宣言をその条件のみにスコープするため、非常に便利です。また、新しい変数を宣言せずに条件内で割り当てるif ($foo = $bar)と警告が表示されますが、警告は表示if (my $foo = $bar)されますが、表示されません。

通常、別のステートメントで割り当てを行うだけで十分ですが、スコープの問題を引き起こす可能性があります。

my $result = some_computation()
if ($result) {
  say "We succeeded, and the result is $result";
}
else {
  warn "Failed with $result";
}
# $result is still visible here - eek!

Golangは、エラーチェックの戻り値に大きく依存しています。したがって、条件文で初期化ステートメントを取得できます。

if result, err := some_computation(); err != nil {
  fmt.Printf("Failed with %d", result)
}
fmt.Printf("We succeeded, and the result is %d\n", result)

他の言語では、型システムを使用して、条件内で非ブール式を禁止しています。

int foo;
if (foo = bar()) // Java does not like this

もちろん、ブール値を返す関数を使用すると失敗します。

偶発的な割り当てを防ぐためのさまざまなメカニズムを見てきました。

  • 式としての割り当てを許可しない
  • 静的型チェックを使用する
  • 割り当ては存在せず、letバインディングしかありません
  • 初期化ステートメントを許可し、そうでない場合は割り当てを許可しない
  • 宣言なしの条件内での割り当てを許可しない

昇順の優先順位でそれらをランク付けしました-式内の割り当ては便利です(そして、明示的な宣言構文と別の名前付き引数構文を持つことでPythonの問題を簡単に回避できます)。しかし、同じ効果に対する他の多くのオプションがあるので、それらを禁止することは問題ありません。

バグのないコードは簡潔なコードよりも重要です。


「式としての割り当てを許可しない」ための+1。式としての割り当てのユースケースは、バグや可読性の問題の可能性を正当化するものではありません。
突く

7

あなたは「私は(私の個人的な経験を超える証拠はほとんどありませんが)これが起こったのはほとんどの場合、それが本当に比較操作であることを意図していた」と言いました。

問題を解決してみませんか?

割り当てに=を、同等性テストに==を使用する代わりに、割り当てに=を、同等に=(または==)を使用しないのはなぜですか?

観察する:

if (a=foo(bar)) {}  // obviously equality
if (a := foo(bar)) { do something with a } // obviously assignment

プログラマーが割り当てを平等と間違えにくくする場合は、難しくします。

同時に、本当に問題を解決したい場合は、ブール値が定義済みのシンボリックシュガー名を持つ単なる整数であると主張するCクロックを削除します。それらをすべて異なるタイプにします。そして、言う代わりに

int a = some_value();
if (a) {}

プログラマに次のように書かせる:

int a = some_value();
if (a /= 0) {} // Note that /= means 'not equal'.  This is your Ada lesson for today.

実際のところ、演算子としての割り当ては非常に便利な構成です。一部の人々が自分自身を切断したため、カミソリの刃を排除しませんでした。代わりに、キングジレットが安全剃刀を発明しました。


2
(1):=割り当てと=平等のためにこの問題は解決するかもしれませんが、主流ではない言語の小さなセットを使用して成長しなかったすべてのプログラマーを疎外するという犠牲を払います。(2)bools以外の型が条件で許可されるのは、必ずしもboolsと整数が混ざっているためではなく、他の型に真/偽の解釈を与えるだけで十分です。Cから逸脱することを恐れない新しい言語は、整数以外の多くのタイプでそうしています(たとえば、Pythonは空のコレクションを偽と見なします)。

1
そして、カミソリの刃に関して:それらは、鋭さを必要とするユースケースに役立ちます。一方で、プログラミングでは、式の評価の途中で変数に割り当てる必要があるとは確信していません。鋭利なエッジなしで体毛を消すためのシンプルでローテクで安全で費用効率の高い方法があれば、カミソリの刃はずらされるか、少なくとももっとまれになったでしょう。

1
@delnan:賢明な男はかつて「できるだけシンプルにしよう、しかしもっとシンプルにしよう」と言った。a = bエラーとa == bエラーの大部分を排除することが目的の場合、条件付きテストのドメインをブール値に制限し、<other>-> booleanのデフォルトの型変換ルールを排除することで、ほぼすべての方法が得られますそこ。その時点で、if(a = b){}は、aとbが両方ともブール値であり、aが有効な左辺値である場合にのみ構文的に有効です。
ジョンR.ストローム

代入をステートメントにすることは、少なくともあなたが提案する変更とほぼ同じくらい簡単です-少なくとも同じくらい達成します-おそらくさらにもっと(if (a = b)左辺値a、ブール値a、bを許可しません)。静的型付けのない言語では、(解析時と実行時の)はるかに優れたエラーメッセージも提供します。さらに、「a = b vs. a == bエラー」を防ぐことが唯一の関連する目的ではない場合があります。たとえば、if items:意味するようなコードを許可しif len(items) != 0たいので、条件をブール値に制限するためにgiveめなければなりません。

1
@delnan Pascalは非主流の言語ですか?何百万人もの人々がPascal(および/またはPascalから派生したModula)を使用してプログラミングを学びました。そして、Delphiは今でも多くの国で一般的に使用されています(おそらくあなたの国ではあまりないでしょう)。
ジュウェンティング

5

実際に質問に答えるために、はい、それらはわずかにニッチですが、これの多くの用途があります。

たとえば、Javaの場合:

while ((Object ob = x.next()) != null) {
    // This will loop through calling next() until it returns null
    // The value of the returned object is available as ob within the loop
}

埋め込み代入を使用しない代替方法でobは、ループのスコープ外で定義する必要があり、x.next()を呼び出す2つの別個のコード位置が必要です。

1つのステップで複数の変数を割り当てることができることは既に述べました。

x = y = z = 3;

この種のことは最も一般的な使用法ですが、創造的なプログラマーは常にもっと多くのことを考え出します。


whileループ条件は、ループobごとに割り当てを解除し、新しいオブジェクトを作成しますか?
user3932000

@ user3932000その場合、おそらくそうではありませんが、通常x.next()は何かを繰り返し処理しています。確かに可能性はあります。
ティムB

1

すべてのルールを作成できるので、なぜ割り当てを許可して値を有効にし、単に条件付きステップ内での割り当てを許可しないのですか?これにより、一般的なコーディングの間違いを防ぎながら、初期化を簡単にする構文糖衣が提供されます。

言い換えれば、これを合法にします:

a=b=c=0;

しかし、これを違法にする:

if (a=b) ...

2
それはかなりアドホックなルールのようです。割り当てをステートメントにし、それを許可するa = b = cように拡張することは、より直交的であり、実装も簡単です。これらの2つのアプローチは、式(a + (b = c))の割り当てについては意見が異なりますが、それらに賛成していないので、それらは重要ではないと思います。

「実装が簡単」はあまり考慮すべきではありません。ユーザーインターフェースを定義しています。ユーザーのニーズを最初に置きます。この動作がユーザーを助けるか妨げるかを自問するだけです。
ブライアンオークリー

boolへの暗黙的な変換を許可しない場合、条件での割り当てを心配する必要はありません
ラチェットフリーク

実装しやすいというのは、私の議論の1つにすぎませんでした。残りはどうですか?UIの観点から言えば、IMHOの一貫性のないデザインとアドホックな例外は、一般的にユーザーがルールを理解し、内部化するのを妨げると付け加えます。

@ratchetfreakでは、実際のboolの割り当てにまだ問題がある可能性があります
jk。

0

それの音によって、あなたはかなり厳しい言語を作成する道にいます。

それを念頭に置いて、人々に次のように書かせる:

a=c;
b=c;

の代わりに:

a=b=c;

人々がすることを防ぐための改善に見えるかもしれません:

if (a=b) {

彼らがするつもりだったとき:

if (a==b) {

しかし、最終的に、この種のエラーは簡単に検出され、合法コードであるかどうかについて警告されます。

ただし、次のような状況があります。

a=c;
b=c;

という意味ではありません

if (a==b) {

本当でしょう。

cが実際に関数c()である場合、呼び出されるたびに異なる結果を返す可能性があります。(計算的にも高価になる可能性があります...)

同様に、cがメモリマップドハードウェアへのポインタである場合、

a=*c;
b=*c;

両方とも異なる可能性が高く、また、読み取りごとにハードウェアに電子的な影響を与える可能性があります。

特定のタイミング制約からどのメモリアドレスが読み取られ、書き込まれ、どのメモリアドレスが書き込まれるかを正確に把握する必要があるハードウェアには、タイミングリスクを伴わずに同じ行で複数の割り当てをすばやく簡単に行うことができる多くの順列があります一時変数を導入


4
と同等のものa = b = ca = c; b = c、そうではありませんb = c; a = b。これは、副作用の重複を避け、またの修正保ちab同じ順序でを。また、これらのハードウェア関連の引数はすべて一種の愚かです。ほとんどの言語はシステム言語ではなく、これらの問題を解決するように設計されておらず、これらの問題が発生する状況で使用されていません。これは、JavaScriptやPHPに取って代わろうとする言語では二重になります。

デルナン、問題はこれらの不自然な例ではありませんでした。ポイントは、a = b = cを記述するのが一般的な場所の種類を示し、ハードウェアの場合、OPが要求したように、良い慣行と考えられる場所を示しているということです。私は彼らの期待環境への関連性を検討することができます確信している
マイケル・ショー

振り返ってみると、この答えの私の問題は、主にシステムプログラミングのユースケースに焦点を合わせていることではありませんが(書き方は十分ではありませんが)、誤った書き換えを前提としています。例はa=b=c、一般的/有用な場所の例ではなく、副作用の順序と数に注意しなければならない場所の例です。それは完全に独立しています。チェーンされた割り当てを正しく書き換えると、両方のバリアントが同様に正しくなります。

@delnan:右辺値はb1つのtempの型に変換され、a別のtempの型に変換されます。これらの値が実際に保存される相対的なタイミングは指定されていません。言語設計の観点からは、複数代入文のすべての左辺値が一致する型を持つことを要求し、場合によってはそれらのいずれも揮発性がないことを要求することは合理的だと思います。
supercat 14

0

割り当てを表現にすることの最大の利点は、「すべてが表現である」という目標(特にLISPの目標)である場合に、文法を単純化できることです。

Pythonにはこれがありません。式とステートメントがあり、割り当てはステートメントです。しかし、Pythonはlambdaフォームを単一のパラメーター化されたであると定義しているため、ラムダ内で変数を割り当てることはできません。これは時には不便ですが、重大な問題ではありません。Pythonで代入をステートメントにすることが私の経験の唯一の欠点です。

可能性を導入することなく、式をする、割り当てを許可、または割り当てのかなり影響する一つの方法if(x=1)Cが持っている事故が使用するLISPのようなletのような構築物(let ((x 2) (y 3)) (+ x y))として評価する言語でいるかもしれません5。字句スコープの作成としてlet定義する場合、この方法を使用することは、言語で技術的にまったく割り当てる必要はありませんlet。このように定義letすると、引数を使用してネストされたクロージャー関数を作成して呼び出すのと同じ方法で、構文をコンパイルできます。

一方、単にif(x=1)大文字と小文字を区別するだけで、Cのように代入を式にしたい場合は、異なるトークンを選択するだけで十分です。割り当て:x := 1またはx <- 1。比較:x == 1。構文エラー:x = 1


1
let新しいスコープに新しい変数を技術的に導入するよりも多くの点で割り当てとは異なります。まず第一に、それはletの本体の外側のコードに影響を与えないので、すべてのコード(変数を使用すべきもの)をさらにネストする必要があります。その経路をたどるとset!すれば、より良いLispアナログになります-比較とはまったく異なりますが、ネストや新しいスコープは必要ありません。

@delnan:再割り当ては禁止するが再宣言は許可する、宣言と割り当ての組み合わせの構文を見たい)再宣言は、すべての囲みスコープで変数を「宣言解除」します。したがって、有効な識別子の値は、その名前の以前の宣言で割り当てられたものになります。これは、数行だけに使用される変数のスコープブロックを追加したり、各一時変数の新しい名前を定式化したりするよりも、少しいいように思えます。
supercat

0

これは、Cの人々が愛している1行関数が機能しなくなることを意味します。私の個人的な経験を超える証拠はほとんどありませんでしたが、これが起こったのはほとんどの場合、実際に比較操作を目的としたものでした。

確かに。これは新しいことではなく、C言語のすべての安全なサブセットがすでにこの結論を出しています。

MISRA-C、CERT-Cなど、条件内でのすべての禁止割り当ては、単に危険だからです。

条件内の割り当てに依存するコードを書き換えることができない場合はありません。


さらに、このような標準は、評価の順序に依存するコードの記述に対しても警告しています。単一の行に複数の割り当てx=y=z;があるような場合です。複数の割り当てがある行に副作用(関数の呼び出し、揮発性変数へのアクセスなど)が含まれている場合、どの副作用が最初に発生するかを知ることはできません。

オペランドの評価の間にシーケンスポイントはありません。したがって、部分式yzCで指定されていない動作であるかどうかの前に評価されるかどうかを知ることはできません。

解決策は、コードをに置き換えることy=z; x=y;でした。これにより、シーケンスポイントが追加され、評価の順序が保証されます。


したがって、これがCで引き起こしたすべての問題に基づいて、現代の言語は、条件内での割り当ての禁止と、単一の行での複数の割り当ての両方に適しています。

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