Почему Coffeescript считает, что затенение - плохая идея

Я хотел переключиться на Coffeescript какое-то время, а вчера я думал, что я наконец-то продал, но затем я наткнулся на статью Armin Ronachers о теневом копировании в Coffeescript.

В настоящее время 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-авторы захотят избавиться от такой функции?

Обновление. Вот пример лучшего примера о том, почему Shadowing важен, вытекает из проблемы, связанной с этой проблемой на github

PS: Я не ищу ответа, который говорит мне, что я могу просто вставить простой Javascript с обратными окнами.

Ответ 1

Если вы читаете обсуждение этого билета, вы можете увидеть Джереми Ашкенаса, создателя CoffeeScript, объясняющего некоторые рассуждения между запретом явного затенение:

Мы все знаем, что динамический масштаб плох, по сравнению с лексической областью, потому что это затрудняет рассуждение о ценности ваших переменных. С динамической областью вы не можете определить значение переменной, читая окружающий исходный код, потому что это значение полностью зависит от среды во время вызова функции. Если разрешено и поощряется переменное затенение, вы не можете определить значение переменной без отслеживания назад в источнике до ближайшей переменной var, потому что тот же самый идентификатор для локальной переменной может иметь совершенно разные значения в смежных областях. Во всех случаях, когда вы хотите затенять переменную, вы можете сделать то же самое, просто выбрав более подходящее имя. Гораздо проще рассуждать о вашем коде, если имя локальной переменной имеет одно значение во всей лексической области, а теневое запрещение запрещено.

Итак, это очень преднамеренный выбор для CoffeeScript, чтобы убить двух птиц одним камнем - упростить язык, удалив концепцию "var" и запретив темные переменные как естественные последствия.

Если вы просматриваете "сферу" или "затенение" в проблемах CoffeeScript, вы можете видеть, что это происходит постоянно. Я не буду здесь останавливаться, но суть в том, что создатели CoffeeScript считают, что это приводит к более простому коду, который менее подвержен ошибкам.

Хорошо, я опишу немного: затенение не имеет значения. Вы можете придумать надуманные примеры, которые показывают, почему любой подход лучше. Дело в том, что с затенением или нет, вам нужно найти "вверх" цепочку областей видимости, чтобы понять жизнь переменной. Если вы явно объявите свои переменные ala JavaScript, возможно, вы сможете быстрее закоротиться. Но это не имеет значения. Если вы никогда не знаете, какие переменные находятся в области видимости в заданной функции, вы делаете это неправильно.

Затенение возможно в CoffeeScript без включения JavaScript. Если вам действительно нужна переменная, которую вы знаете, локально локализована, вы можете ее получить:

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

Таким образом, вне возможности, что это происходит на практике, существует довольно простой способ обхода.

Лично я предпочитаю подход явным образом-declare-every-variable и предлагаю в качестве своего "аргумента" следующее:

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

Это отлично работает. Затем неожиданно появляется стажер и добавляет эту строку:

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

И bam, код сломан, но разрыв не появляется до конца позже. Упс! С var этого не произошло бы. Но с "обычно неявным охватом, если вы не укажете иное", это было бы. Так. Так или иначе.

Я работаю в компании, которая использует CoffeeScript на клиенте и сервере, и я никогда не слышал об этом на практике. Я думаю, что количество времени, сохраненное при отсутствии необходимости вводить слово var всюду, больше, чем количество времени, затраченного на выявление ошибок (которые никогда не появляются).

Изменить:

После написания этого ответа, я видел, что эта ошибка повторяется два раза в реальном коде. Каждый раз, когда это случалось, это было крайне неприятно и трудно отлаживать. Мои чувства изменились, и они подумали, что выбор CoffeeScript - это плохие времена.

Некоторые альтернативы JS, похожие на CoffeeScript, такие как LiveScript и coco, используют для этого два разных оператора присваивания: = объявлять переменные и := изменять переменные во внешних областях. Это похоже на более сложное решение, чем просто сохранение ключевого слова var и что-то, что также не будет хорошо поддерживать, как только let будет широко использоваться.

Ответ 2

Основная проблема здесь заключается не в том, чтобы затенять, его инициализацию инициализации переменной CoffeeScript и переменную переназначения и не дать программисту точно указать их намерение

Когда компилятор coffee- script видит x = 1, он понятия не имеет, означает ли вы

Я хочу новую переменную, но я забыл, что уже использую это имя в верхней области

или

Я хочу переназначить значение переменной, которую я первоначально создал в верхней части моего файла.

Это не то, как вы запрещаете затенение на языке. Это то, как вы создаете язык, который наказывает пользователей, которые случайно используют имя переменной с тонким и трудным для обнаружения ошибок.

CoffeeScript мог быть разработан для запрещения теневого копирования, но сохраняйте декларацию и назначение отдельно, сохраняя var. Компилятор просто пожаловался бы на этот код:

var x = blah()

var test = -> 
  var x = 0

с "Переменная x уже существует (строка 4)"

но он также будет жаловаться на этот код:

x = blah()

test = ->
  x = 0;

с "Переменная x не существует (строка 1)"

Однако, поскольку var был удален, компилятор понятия не имеет, означает ли вы означать "объявить" или "переназначить" и не может помочь.

Использование того же синтаксиса для двух разных вещей не является "более простым", хотя может показаться, что это так. Я рекомендую "Rich Hickey talk" , просто сделанный легко, где он глубже объясняет, почему это так.

Ответ 3

Потому что cs объявляет переменные только после того, как цикл не будет работать так, как предполагалось.

Каков предназначенный для этих циклов способ? Условие в while i = 0 < arr.length всегда будет истинным, если arr не пусто, поэтому он будет бесконечным циклом. Даже если только один цикл 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 препятствует переменному затенению. Но если примеры должны использоваться для аргументации в пользу или против чего-либо, примеры должны быть хорошими. Этот пример не помогает идее о том, что переменное затенение следует поощрять.

Обновление (фактический ответ)

Из-за проблемы с изменением тени одна вещь, которую стоит прояснить, заключается в том, что обсуждение заключается в том, следует ли допускать переменное затенение между различными областями функций, а не с блоками. В пределах одной и той же области функций переменные будут поднимать всю область действия, независимо от того, где они сначала назначены; эта семантика унаследована от 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 за отсутствие специального синтаксиса для переменных shadowing заключается в том, что вы просто не должны делать такого рода вещи. Назовите свои переменные четко. Потому что, даже если затенение будет разрешено, было бы очень запутанно иметь две переменные с двумя разными значениями с одним и тем же именем, один во внутренней области и один в охватывающей области.

Используйте соответствующие имена переменных в зависимости от того, какой у вас контекст: если у вас мало контекста, например. переменная верхнего уровня, вам, вероятно, понадобится очень специфическое имя для ее описания, например currentGameState (особенно если это не константа, и ее значение будет меняться со временем); если у вас больше контекста, вы можете избежать использования менее описательных имен (потому что контекст уже существует), например, переменные цикла: killedEnemies.forEach (e) -> e.die().

Если вы хотите узнать больше об этом дизайнерском решении, вам может быть интересно прочитать мнения Джереми Ашкенаса в этих потоках HackerNews: ссылка, ссылка; или во многих проблемах CoffeeScript, где обсуждается этот вопрос: # 1121, # 2697 и др.