16

しばらくの間Coffeescriptに切り替えたかったのですが、昨日はようやく売れたと思っていましたが、Coffeescriptでのシャドウイングに関するArminRonachersの記事に出くわしました。

Coffeescriptは実際にシャドウイングを放棄しました。その問題の例は、ネストされたループに同じイテレータを使用する場合です。

var arr, hab, i;

arr = [[1, 2], [1, 2, 3], [1, 2, 3]];

for(var i = 0; i < arr.length; i++){
  var subArr = arr[i];
  (function(){
      for(var i = 0; i < subArr.length; i++){
        console.log(subArr[i]);
      }
  })();
}

csは変数を宣言するのは一度だけなので、coffeescript内でこれを行うことはできません。

シャドウイングは意図的に削除されました。cs-authorsがそのような機能を削除したい理由を理解したいのですが。

更新:これは、GitHubでのこの問題に関する問題から導き出された、シャドウイングが重要である理由のより良い例です。

PS:バッククォートを使用してプレーンなJavascriptを挿入できるという答えを探していません。

4

3 に答える 3

27

このチケットのディスカッションを読むと、CoffeeScriptの作成者であるJeremy Ashkenasが、明示的なシャドウイングを禁止する理由のいくつかを説明していることがわかります。

変数の値について推論するのが難しいため、動的スコープは字句スコープと比較して悪いことは誰もが知っています。動的スコープでは、関数が呼び出されたときの値は環境に完全に依存するため、周囲のソースコードを読み取って変数の値を決定することはできません。変数のシャドウイングが許可および推奨されている場合、ローカル変数のまったく同じ識別子が隣接するスコープで完全に異なる値を持つ可能性があるため、ソース内で最も近い変数変数まで逆方向に追跡せずに変数の値を決定することはできません。いずれの場合も、変数をシャドウイングする場合は、より適切な名前を選択するだけで同じことを実行できます。それ'

したがって、CoffeeScriptが1つの石で2羽の鳥を殺すのは非常に慎重な選択です。「var」の概念を削除して言語を簡素化し、自然な結果としてシャドウ変数を禁止します。

CoffeeScriptの問題で「スコープ」または「シャドウイング」を検索すると、これが常に表示されることがわかります。ここでは意見を述べませんが、要点は、CoffeeScript Creatorsは、エラーが発生しにくい、より単純なコードにつながると信じているということです。

さて、私は少し意見を述べます:シャドウイングは重要ではありません。どちらのアプローチが優れているのかを示す不自然な例を思いつくことができます。実際のところ、シャドウイングの有無にかかわらず、変数の有効期間を理解するには、スコープチェーンの「上」を検索する必要があります。JavaScriptで変数を明示的に宣言すると、より早く短絡できる可能性があります。しかし、それは問題ではありません。特定の関数のスコープ内にある変数がわからない場合は、間違っています。

シャドウイング、JavaScriptを含めずにCoffeeScriptで可能です。ローカルスコープであることがわかっている変数が実際に必要な場合は、次のように取得できます。

x = 15
do (x = 10) ->
  console.log x
console.log x

したがって、これが実際に発生する可能性が低い場合は、かなり簡単な回避策があります。

個人的に、私は明示的にすべての変数を宣言するアプローチを好み、私の「議論」として以下を提供します。

doSomething = ->
  ...
  someCallback = ->
    ...
      whatever = ->
        ...
        x = 10
        ...

これはうまくいきます。その後、突然インターンがやって来て、次の行を追加します。

x = 20
doSomething = ->
  ...
  someCallback = ->
    ...
      whatever = ->
        ...
        x = 10
        ...

そして、バム、コードは壊れていますが、壊れたものはずっと後になるまで現れません。おっと!でvar、それは起こらなかっただろう。しかし、「特に指定しない限り、通常は暗黙のスコープ」を使用すると、そうなります。それで。ともかく。

私はクライアントとサーバーでCoffeeScriptを使用している会社で働いていますが、これが実際に起こっていることは聞いたことがありません。どこにでも単語を入力する必要がないことで節約されたvar時間は、バグのスコーピングに費やされた時間(決して発生しない)よりも大きいと思います。

編集:

この答えを書いて以来、私はこのバグが実際のコードで2回発生するのを見てきました。それが起こるたびに、それは非常に迷惑でデバッグが困難でした。私の気持ちは、CoffeeScriptの選択はいたるところで悪い時期だと思うようになりました。

LiveScriptやcocoなどの一部のCoffeeScriptのようなJSの代替手段は、これに2つの異なる代入演算子を使用します。=変数を宣言する:=ためと外部スコープで変数を変更するためです。これは、単にキーワードを保存するよりも複雑な解決策のように思えます。varまた、一度はうまくいかないものもlet広く使用できます。

于 2013-03-05T16:08:50.447 に答える
7

ここでの主な問題はシャドウイングではなく、CoffeeScriptが変数の初期化と変数の再割り当てを混同し、プログラマーが意図を正確に指定できないようにすることです。

コーヒースクリプトコンパイラがを見るときx = 1、それはあなたが意味したかどうかわかりません

新しい変数が欲しいのですが、すでにその名前を上位スコープで使用していることを忘れました

また

ファイルの先頭に最初に作成した変数に値を再割り当てしたい

これは、言語でのシャドウイングを禁止する方法ではありません。これは、微妙で検出が難しい変数名を誤って再利用したユーザーを罰する言語を作成する方法です。

CoffeeScriptは、シャドウイングを禁止するように設計できますが、を保持することで宣言と割り当てを分離しvarます。コンパイラは単にこのコードについて文句を言うでしょう:

var x = blah()

var test = -> 
  var x = 0

「変数xはすでに存在します(4行目)」

しかし、それはこのコードについても文句を言うでしょう:

x = blah()

test = ->
  x = 0;

「変数xは存在しません(1行目)」

ただし、var削除されたため、コンパイラーは、ユーザーが「宣言」を意味するのか「再割り当て」を意味するのかわからず、役に立ちません。

2つの異なるものに同じ構文を使用することは、見た目は「単純」ではありません。私はリッチ・ヒッキーの話をお勧めします。シンプルは、彼がなぜそうなのかを深く掘り下げたところを簡単にしました。

于 2013-12-11T00:23:15.027 に答える
2

csは、ループが意図したとおりに機能しなくなった場合にのみ変数を宣言するためです。

それらのループが機能するための意図された方法は何ですか?が空でない場合、の条件while i = 0 < arr.lengthは常に真にarrなるため、無限ループになります。意図したとおりに機能しないループが1つしかない場合でもwhile(無限ループが探しているものではないと想定):

# This is an infinite loop; don't run it.
while i = 0 < arr.length
  console.log arr[i]
  i++

配列を順番に繰り返す正しい方法は、次のfor ... in構成を使用することです。

arr = [[1,2], [1,2,3], [1,2,3]]

for hab in arr
  # IDK what "hab" means.
  for habElement in hab
    console.log habElement

私は、この答えが正接していくように聞こえることを知っています。その主なポイントは、CSが可変シャドウイングを推奨しない理由です。しかし、例が何かに賛成または反対の議論に使用される場合、例は良いはずです。この例は、可変シャドウイングを推奨する必要があるという考えには役立ちません。

更新(実際の回答)

変数シャドウイングの問題について、明確にする価値のあることの1つは、ブロックではなく、異なる関数スコープ間で変数シャドウイングを許可する必要があるかどうかについての議論であるということです。同じ関数スコープ内で、変数は最初に割り当てられた場所に関係なく、スコープ全体を持ち上げます。このセマンティクスはJSから継承されます。

->
  console.log a # No ReferenceError is thrown, as "a" exists in this scope.
  a = 5

->
  if someCondition()
    a = something()
  console.log a # "a" will refer to the same variable as above, as the if 
                # statement does not introduce a new scope.

時々尋ねられる質問は、letキーワードのように変数のスコープを明示的に宣言する方法を追加しないのはなぜですか(したがって、スコープを囲む際に同じ名前の他の変数をシャドウイングする)、または=常にそのスコープに新しい変数を導入するようにします、そして:=現在のスコープで変数を宣言せずに、スコープを囲む変数を割り当てるようなものがあります。この動機は、この種のエラーを回避することです。

user = ... # The current user of the application; very important!

# ...
# Many lines after that...
# ...

notifyUsers = (users) ->
  for user in users # HO NO! The current user gets overridden by this loop that has nothing to do with it!
    user.notify something

変数をシャドウイングするための特別な構文がないというCoffeeScriptの主張は、この種のことを行うべきではないということです。変数に明確な名前を付けます。シャドウイングが許可されたとしても、同じ名前の2つの異なる意味を持つ2つの変数(1つは内部スコープに、もう1つは囲みスコープにある)を持つことは非常に混乱するためです。

コンテキストの量に応じて適切な変数名を使用します。たとえば、トップレベルの変数など、コンテキストがほとんどない場合は、それを説明するために非常に具体的な名前が必要になる可能性がありますcurrentGameState特に、定数ではなく、その値が時間とともに変化する); より多くのコンテキストがある場合は、ループ変数のように、(コンテキストがすでに存在するため)わかりにくい名前を使用する必要がありますkilledEnemies.forEach (e) -> e.die()

この設計上の決定についてもっと知りたい場合は、これらのHackerNewsスレッドでJeremy Ashkenasの意見を読むことに興味があるかもしれません:linklink ; または、このトピックが議論されている多くのCoffeeScriptの問題:#1121#2697など。

于 2013-03-05T14:02:37.417 に答える