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行の関数が機能しなくなることを意味します。私が経験したことのほとんどの証拠はありませんが、これが発生した大部分の時間は、実際には比較操作であることが意図されていました。
またはそれは?簡単に書き換えることができない代入演算子の戻り値の実用的な使い方はありますか? (そのような概念を持つ言語の場合。)
技術的には、一部の一般的な操作の可読性を向上させる場合、一部の構文糖はそれを簡単に置き換えることができても保持する価値があります。しかし、割り当てとしての式は、その対象にはなりません。比較の代わりにそれをタイプする危険性は、それがめったに使用されないことを意味し(時にはスタイルガイドによってさえ禁止されます)、それが使用されるときはいつでもダブルテイクを引き起こします。言い換えると、読みやすさの利点は数と規模が小さいということです。
これを行う既存の言語を検討することには価値があります。
x
のタイプに応じて、if (x)
またはif (x != null)
の代わりにif (x != 0)
のような条件を許可しないと不満を言う人もいます。ただし、Pythonは、1つの特殊なケースを許可し、一度に複数の名前に割り当てます:a = b = c
。これはb = c; a = b
と同等のステートメントと見なされ、たまに使用されるため、あなたの言語にも追加する価値があるかもしれません(ただし、この追加は下位互換性があるはずなので、私は気にしないでしょう)。
簡単に書き換えることができない代入演算子の戻り値の実用的な使い方はありますか?
一般的に言えば、いいえ。割り当て式の値を割り当てられた値にするという考えは、その副作用とそのvalue、そしてそれは混乱するものであると多くの人が考えています。
一般的な使用法は通常、式をコンパクトにすることです。
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
と混同されることは絶対にありません。
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の問題を回避するのは簡単です)。しかし、同じ効果には他にも多くのオプションがあるため、それらを許可しないことは問題ありません。
バグのないコードは簡潔なコードよりも重要です。
あなたは、「私が経験したことのほとんどない証拠で、これが起こった大多数の場合、それが本当に比較操作であることを意図していたと考えました」と述べました。
問題を修正しないのはなぜですか?
=の代入と==の等価テストの代わりに、なぜ:=の代入と=(または==)の等価テストを使用しないのですか?
観察する:
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.
実際のところ、オペレーターとしての割り当ては非常に便利な構成です。一部の人々は自分自身をカットしたため、かみそりの刃を排除しなかった。代わりに、ジレット王が安全かみそりを発明しました。
質問に実際に答えるために、はい、これはわずかにニッチですが、これには多くの用途があります。
たとえば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;
この種のものは最も一般的な使用法ですが、創造的なプログラマーは常にもっと多くのことを考え出します。
すべてのルールを構成できるようになったので、なぜ割り当てで値を変更できるようにし、単純にnot条件付きステップ内の割り当てを許可しますか?これにより、一般的なコーディングミスを防ぎながら、初期化を容易にする構文上の砂糖が得られます。
つまり、これを合法にします。
a=b=c=0;
しかし、これを違法にしてください:
if (a=b) ...
割り当てを表現にすることの私の心にとっての最大の利点は、目標の1つが「すべてが表現である」、特にLISPの目標である場合、文法がより単純になることです。
Pythonにはこれがありません。式とステートメントがあり、代入はステートメントです。ただし、Pythonはlambda
フォームを単一のパラメーター化されたexpressionとして定義しているため、ラムダ内に変数を割り当てることはできません。これは不便です。時々、しかし重大な問題ではありません、そしてそれは私の経験の中で唯一の欠点がPythonでステートメントになることです。
Cが持っているif(x=1)
事故の可能性を導入せずに代入、または代入の効果を式にすることを可能にする1つの方法は、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
_。
これは、Cの人々が大好きな1行の関数が機能しなくなることを意味します。私が経験したことのほとんどの証拠はありませんが、これが発生した大部分の時間は、実際には比較操作であることが意図されていました。
確かに。これは新しいことではなく、C言語のすべての安全なサブセットがすでにこの結論を出しています。
MISRA-C、 CERT-C など、条件内のすべての禁止割り当ては、単に危険であるためです。
条件内の代入に依存するコードを書き換えられないケースはありません。
さらに、そのような標準は、評価の順序に依存するコードの作成に対しても警告します。 1つの行に対する複数の割り当てx=y=z;
はそのような場合です。複数の割り当てを持つ行に副作用(関数の呼び出し、揮発性変数へのアクセスなど)が含まれている場合、どの副作用が最初に発生するかはわかりません。
オペランドの評価の間にシーケンスポイントはありません。したがって、部分式y
がz
の前または後に評価されるかどうかを知ることはできません。これは、Cでは不特定の動作です。したがって、このようなコードは、信頼性が低く、移植性がなく、前述の金庫に準拠していない可能性がありますCのサブセット.
解決策は、コードをy=z; x=y;
に置き換えることでした。これにより、シーケンスポイントが追加され、評価の順序が保証されます。
したがって、これがCで発生したすべての問題に基づいて、すべての現代の言語は、条件内での割り当ての禁止と、1つの行での複数の割り当ての両方に適しています。
その音で、あなたはかなり厳密な言語を作成する道にいます。
それを念頭に置いて、人々に書くように強制します:
a=c;
b=c;
の代わりに:
a=b=c;
人々が行うことを防ぐための改善のように見えるかもしれません:
if (a=b) {
彼らがするつもりだったとき:
if (a==b) {
しかし結局のところ、この種のエラーは簡単に検出でき、それらが正当なコードであるかどうかについて警告します。
ただし、次のような状況があります。
a=c;
b=c;
という意味ではありません
if (a==b) {
本当でしょう。
Cが実際に関数の場合c()の場合、cを呼び出すたびに異なる結果が返される可能性があります(計算コストもかかる可能性があります...)。
同様に、cがメモリマップハードウェアへのポインタである場合、
a=*c;
b=*c;
両方が異なる可能性が高く、読み取りごとにハードウェアに電子的影響を与える可能性もあります。
ハードウェアには他にも多くの組み合わせがあり、特定のタイミング制約に基づいて、メモリアドレスの読み取り、書き込み、および特定のタイミング制約の下で正確に行う必要があります。同じラインで複数の割り当てを行うと、迅速でシンプルで明白です。一時変数が導入します