27 марта 2012 в 00:58

CoffeeScript: Подробное руководство по циклам tutorial

CoffeeScript: Подробное руководство по циклам

Как известно, CoffeeScript предлагает несколько иной набор управляющих конструкций, нежели JavaScript.

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







В этой статье я попытаюсь максимально подробно рассказать о принципах работы с циклами в CoffeeScript и тесно связанными с ними управляющими конструкциями.

Весь код сопровождается сравнительными примерами на JavaScript.


Инструкция for-in


Начнем с самого простого цикла for:

for (var i = 0; i < 10; i++) {
//...
}

В CoffeeScript он будет записан так:

for i in [0...10]

Для определения количества итераций используются диапазоны.
В нашем случае, диапазон от 0...10 означает: выполнить 10 итераций цикла.
Но как быть если требуется задать условие типа i <= 10?

for i in [0..10]

На первый взляд ничего не изменилось, но если присмотреться, то можно заметить, что в диапазоне одной точкой стало меньше.

В итоге, мы получим следующую запись:

for (var i = 0; i <= 10; i++) {
	//...
}

Если начальное значение диапазона больше конечного [10..0], то мы получим обратный цикл с инвертированным результатом:

for (var i = 10; i >= 0; i--) {
	//..
}

Хочу заметить, также допустимо использование отрицательных значений диапазона:

for i in [-10..0]

А так, можно заполнить массив отрицательными значениями:

[0..-3]
#[0, -1, -2, -3]

Теперь рассмотрим реальную ситуацию, на примере функции которая, вычисляет факториал числа n:

JavaScript:

var factorial = function(n) {
	var result = 1;

	for (i = 1; i <= n; i++) {
		result *= i;
	}
	return result;
};

factorial(5) //120


CoffeeScript:

factorial = (n) ->
	result = 1
	for i in [1..n]
		result *= i
	result

factorial 5 #120

Как видно из примера выше, код на CoffeeScript более компактный и читабельный по сравнению с JavaScript.

Однако и этот код можно немного упростить:

factorial = (n) ->
	result = 1
	result *= i for i in [1..n]
	result

В этой статье, это не последний пример вычисления факториала, более эффективные способы будет рассмотрены чуть позже.

[...]

Позволю себе немного отступится от темы и упомянуть еще один интересный момент связанный с применением конструкции [...] (slice).

Иногда к чужом коде можно встретить примерно такую конструкцию:

'a,b,c'[''...][0]

Что в конечно счете будет означать следующее:

'a,b,c'.slice('')[0]; //a

На первый взгляд, отличить диапазоны от слайсов довольно сложно. Основных отличий два:

Во-первых, в слайсах можно пропустить одно крайнее значение

[1...]

Здесь мне бы хотелось обратить особое внимание на то, что мы получим после трансляции этого выражения:

var __slice = Array.prototype.slice;
__slice.call(1);

Это может быть удобно для многих ситуаций, например для получения списка аргументов функции:

fn = -> [arguments...]
fn [1..3] #0,1,2,3


Хочу заметить, что в CoffeeScript для получения списка аргументов функции есть более безопасный и изящный вариант (splats):

fn = (args...) -> args
fn [1..3] #0,1,2,3

Также допустимо использование арифметических и логических операций:

[1 + 1...]


Во-вторых, перед слайсами допустимо наличие объекта

[1..10][...2] #1,2


В-третьих, в слайсах допустимо использование перечислений

[1,2,3...]

В этом примере выполняется простая операция конкатенации:

[1, 2].concat(Array.prototype.slice.call(3));

//[1,2,3]

Более полезный пример:

list1 = [1,2,3]
list2 = [4,5,6]

[list1, list2 ...] #[1,2,3,4,5,6]


List comprehension


Наиболее яркой синтаксической конструкцией для работы с объектами в CoffeeScript, являются списочные выражения (List comprehension).

Пример того, как можно получить список всех вычислений факториала от 1 до n:

factorial = (n) ->
	result = 1
	result *= i for i in [1..n]

factorial 5 #1,2,6,24,120

Теперь давайте рассмотрим более интересный пример и выведем список первых пяти членов объекта location:

(i for i of location)[0...5]
# hash, host, hostname, href, pathname

На JavaScript этот код выглядел бы так:

var list = function() {
	var result = [];

	for (var i in location) {
		result.push(i);
	}

	return result;
}().slice(0, 5);

Для того чтобы вывести список элементов (не индексов) массива нужно задать еще один параметр:

foo = (value for i, value of ['a', 'b', 'c'][0...2]) # [ a, b ]

C одной стороны, списочные выражения представляет собой очень эффективный и компактный способ для работы с объектами. С другой стороны, нужно четко представлять какой код будет получен после трансляции в JavaScript.

К примеру, код выше, который выводит список элементов от 0 до 2, более эффективно можно переписать так:

foo = (value for value in ['a', 'b', 'c'][0...2])

Или так:

['a', 'b', 'c'].filter (value, i) -> i < 2

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

Если пропустить пробел, то мы получим следующее:

['a', 'b', 'c'].filter(value, i)(function() {
  return i < 2;
});

//ReferenceError: value is not defined!

Теперь, вам наверное интересно узнать почему вариант с методом .filter() оказался наиболее предпочтителен?
Дело в том, что когда мы используем инструкцию for-of, транслятор подставляет более медленный вариант цикла чем требуется, а именно for-in:

Результат трансляции:


var i, value;

[
	(function() {
		var _ref, _results;
		_ref = ['a', 'b', 'c'].slice(0, 2);
		_results = [];
		for (i in _ref) {
			value = _ref[i];
			_results.push(value);
		}
		return _results;
	})()
];

Скажем прямо, итоговый код ужасен.
Теперь давайте посмотрим на код полученный при использовании метода filter:

['a', 'b', 'c'].filter(function(value, i) {
	return i < 2;
});

Как видите, мы получили идеальный и эффективный код!

Если вы используете CoffeeScript на сервере, то вам не о чем беспокоится, ели нет, то стоит помнить, что IE9- не поддерживает метод filter. Поэтому вы сами должны позаботиться о его наличии!

Оператор then


Как известно, для интерпретации выражений, парсер CoffeeScript анализирует отступы, переводы строк, символы возврата каретки и пр.

Ниже представлен типичный цикл для возведения чисел от 1 до n в степень двойки:

for i in [1...10]
	i * i

Для наглядности, мы использовали перевод строки и отступ.
Однако в реальной ситуации, большинство разработчиков предпочтут записать это выражение в одну строчку:

for i in [1...10] then i * i

В инструкциях while, if/else, и switch/when оператор then указывает анализатору на разделение выражений.

Оператор by


До этого момента мы рассматривали только "простые" циклы, сейчас пора поговорить о циклах с пропусками значений в определенный шаг.

Выведем только четные числа от 2 до 10:

alert i for i in [0..10] by 2 #0,2,4,6,8,10

На JavaScript этот код выглядел бы так:

for (var i = 2; i <= 10; i += 2) {
	alert(i);
}

Опрератор by применяется к диапазону элементов, в которых можно установить шаг итерации.

Также мы можем работать не только с числами или элементами массива, но и со строками:

[i for i in 'Hello World' by 3] #H,l,W,l

Операторы by и then могут примеятся совместно:

[for i in 'hello world' by 1 then i.toUpperCase()] # H,E,L,L,O, ,W,O,R,L,D

Хотя этот пример немного надуман и в реальной ситуации шаг "в один" слудует упостить, тем не менее совместная работа операторов by-then позволяет писать очень компактный и эффективный код.

Оператор own


В JavaScript довольно часто используется метод .hasOwnProperty(), который в отличии от оператора in не проверяет свойства в цепочке прототипов объекта:

var object = {
    foo: 1
};

object.constructor.prototype.bar = 1;

console.log('bar' in object); // true
console.log(object.hasOwnProperty('bar')); // false

Рассмотрим пример использования метод .hasOwnProperty() в теле цикла for-in:

var object = {
    foo: 1
};

object.constructor.prototype.toString = function() {
      return this.foo;
};
 
for (i in object) {
      if (object.hasOwnProperty(i)) {
            console.log(i); //foo
      }
}

Несмотря на то, что мы добавили метод .toString() в прототип объекта object, в теле цикла он перечислен не будет. Хотя к нему можно обратиться напрямую:

object.toString() //1

В CoffeeScript для этих целей предусмотрен специальный оператор own:

object = foo: 1
object.constructor::toString = -> @foo

for own i of object
  console.log i #foo

Если нужно использовать второй ключ инструкции for-of , то достаточно его указать через запятую, при этом добавлять еще раз оператор own не нужно:

for own key, value of object
  console.log '#{key}, #{value}' #foo, 1


Условные операторы if/else


Сейчас мне бы хотелось обратить внимаение на один очень важный момент, который связан с совместным использованием циклов с операторами if/else.

Иногда в JavaScript приложениях мы можем встретить подобный код:

for (var i = 0; i < 10; i++) if (i === 1) break;

Не будем обсуждать и тем более осуждать разработчиков, которые так пишут.
Для нас представляет интерес только как корректно записать выражение в CoffeeScript.

Первое что приходит в голову, сделать так:

for i in [0..10] if i is 1 break # Parse error on line 1: Unexpected 'TERMINATOR'

Прекрасно..., однако согласно правилам лексического анализа CoffeeScriptперед инструкцией if будет обнаружено неожидаемое значение терминала, что приведет к ошибке парсинга!

Из предыдущего материала мы помним, что записать выражение в одну строчку мы можем реализовать с помощью оператора then:

for i in [0..10] then if i is 1 break #Parse error on line 1: Unexpected 'POST_IF'

Однако и это не помогло, мы снова видим ошибку парсинга.

Давайте попробуем разобраться...
Дело в том, что инструкция if подчиняется тем же правилам, что и другие инструкции, для которых возможно применение оператора then. А именно, для того чтобы наше выражение правильно распарсилось нужно после выражения с if добавить еще раз оператор then:

for i in [0..10] then if i is 1 then break

Таким образом мы получим следующий код:

for (i = 0; i <= 10; i++) {
	if (i === 1) {
		break;
	}
}

Иногда бывают ситуации, когда перед циклом нужно проверить выполнение к.л. условия:

if (foo === true) {
	for (i = 0; i <= 10; i++) {
		if (i === 1) {
			break;
		}
	}
}

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

(if i is 1 then break) for i in [0..10] if foo is on

Обратитие внимание, что то в этом случае мы не стали использовать опрератор then, при этом никаких ошибок парсинга не произошло!

Условный оператор when


Мы уже рассмотрели операторы by и then, настало время поговорить о следующем операторе в нашем списке, а именно об условном операторе when.

И начнем мы пожалуй с коррекции предыдущего примера:

if foo is on then for i in [0..10] when i is 1 then break

В этом случае, кода получился немного больше в чем предыдущем варианте, однако он приобрел куда больше выразительности и смысла.

Давайте рассмотрим еще один пример, как можно вывести порядок чисел от 1 до 10 по модулю натурального числа n:

alert i for i in [1..10] when i % 2 is 0

После трансляции в JavaScript код:

for (i = 1; i <= 10; i++) {
	if (i % 2 === 0) {
		alert(i);
	}
}

Как видите использование оператора when дает нам еще больше возможностей для работы с массивами.

Инструкция for-of


Вы уже видели примеры использования инструкции for-of, когда рассматривали списочные выражения. Теперь давайте более подробно познакомимся с инструкцией for-of, которая наряду с for-in позволяет перебирать свойства объекта.

Давайте сразу проведем сравнительную аналогию с инструкцией for-in в JavaScript:

var object = {
	foo: 0,
	bar: 1
};

for (var i in object) {
	alert(key + " : " + object[i]); //0 : foo, 1 : bar
}

Как видите для получения значения свойств объекта мы использовали следующий синтаксис: object[i].
В CoffeeScript же, все проще, во-первых мы можем получить значение объекта используя списочные выражения:

value for key, value of {foo: 1, bar: 2}

Во-вторых, для более сложных выражений мы можем применить более разверную нотацию с применением уже знакомых нам операторов:

for key, value of {foo: 1, bar: 2}
	if key is 'foo' and value is 1 then break

В JavaScript тот же результат можно получить так:

var object = {
	foo: 1,
	bar: 2
};

for (key in object) {
	if (key === 'foo' && object[i] === 1) {
		break;
	}
}

Еще один пример эффективного использования for-in:

(if value is 1 then alert "#{key} : #{value}") for key, value of document

#ELEMENT_NODE : 1,
#DOCUMENT_POSITION_DISCONNECTED : 1

Напомню, что самым эффективным способом получения списка свойств объекта, явлется метод keys():

Object.keys obj {foo: 1, bar: 2} # foo, bar

Для того чтобы получить значения свойств, метод keys() нужно использовать совместно с методом map():

object =
	foo: 1
	bar: 2

Object.keys(object).map (key) -> object[key]; # 1, 2


Инструкция while


По мимо инструкций for-of/in в CoffeeScript также реализована инструкция while.

Когда мы рассматривали инструкцию for-in, я обещал показать еще более эффективный способ вычисления фактриала числа n, время как раз подходящее:

factorial = (n) ->
	result = 1
	while n then result *= n--
	result

На вскидку хочу добавить, что самое элегантное решение вычисления факториала следующее:

factorial = (n) -> !n and 1 or n * factorial n - 1


Инструкция loop


На этой инструкции мы не будем долго останавливаться, потому что единственное ее назначение это создание бесконечного цикла:

loop then break if foo is bar


Рельтат трансляции:

while (true) {
	if (foo === bar) {
		break;
	}
}


Инструкция until


Инструкция until аналогична инструкуции while, за одиним исключением, что в выражение добавляется отрицание.
Это может быть полезно например для нахождения следующей позиции набора символов в строке:

expr = /foo/g;
alert "#{array[0]} : #{expr.lastIndex}" until (array = expr.exec('foofoo')) is null

Рельтат трансляции:

var array, expr;
expr = /foo*/g;

while ((array = expr.exec('foofoo')) !== null) {
	alert("" + array[0] + " : " + expr.lastIndex);
}

//foo : 3, foo : 6

Как видно из примера, цикл выполняется до тех пока результат выражения не станет равен нулю.

Инструкция do-while


Скажу сразу, что в CoffeeScript отсутствует реализация инструкции do-while. Однко с помощью нехитрых манипуляций эмитировать ее частичное поведение можно с помощью инструкции loop:

loop
	#...
	break if foo()


Методы массивов (filter, forEach, map и пр.)


Как известно в CoffeeScript доступны абсолютно все те же методы, что и в JavaSctipt.
Разбирать всю эту группу методов нет смысла, рассмотрим лишь общий принцип работы на примере метода map().

Создадим массив из трех элементов и возведем каждый из них в квадрат:

[1..3].map (i) -> i * i

Рельтат трансляции:

[1, 2, 3].map(function(i) {
	return i * i;
});

Рассмотрим еще один пример:

['foo', 'bar'].map (value, i) -> "#{value} : #{i}"
#foo : 0, bar : 1

Вторым аргументом, метод map принимает контекст вызова:

var object = new function() {
	return [0].map(function() {
		return this
	});
};

// [Window map]

Как видите this внутри map указывает на Window, чтобы сменить контекст вызова, сделать это нужно явно:

var object = new function() {
	return [0].map(function() {
		return this;
	}, this);
};
// [Object {}]

В CoffeeScript для этой цели предназначен специальный оператор =>:

object = new -> [0].map (i) => @

Рельтат трансляции:

var object = new function() {
	var _this = this;

	return [0].map(function() {
		return _this;
		}, this);
};

Иными словами используйте эти методы массивов максимально, где это только возможно.

Кроссбраузерную реализация этих методов я разместил на github'e
Реальный пример использования методов map и filter в CoffeeScript, также можно посмотреть в одном из моих проектов на github'e

Инструкция do / Замыкания


Как известно, в JavaScript активно используются замыкания, при этом CoffeeScript тоже не лишает нас этого удовольствия.

Для создания анонимной самозавязывающейся функции в CoffeeScript используется инструкция do, котороая принимает произвольное число аргументов.

Рассмотрим пример:

array = [];
i = 2
while i-- then array[i] = do (i) -> -> i

array[0]() #0
array[1]() #1

Суть этого кода сводится к заполнению массива. При этом, элементы массива содержат не элементарные значения, а функции, каждая из которых возвращает индекс своего элемента.

На JavaScript код выглядел бы так:

var array = [],
i = 2;

while (i--) {
	array[i] = function(i) {
		return function() {
			return i;
		};
	}(i);
}

array[0]() //0
array[1]() //1


Вложенные инструкции


Вложенные инструкции особо ничем не отличаются от других инструкций и подчиняются тем же правилам:

Для примера, заполним массив парными элементами от 1 до 3:

list = []

for i in [0..2] 
   for j in [1..2] 
     list.push i

list # [0,0,1,1,2,2]

Как видите нет ничего сложного!

Возможно у вас появится желание записать это в одну строчку. Что же, давайте теперь попробуем упростить запись:

list = []
for i in [0..2] then for j in [1..2] then list.push i

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

А что если нужно добавить перед вторым циклом какое-то выражение?

В качестве примера выведем три пары элементов от 0-3:

list = []

for i in [0..2]
  list.push i
  for j in [1..1]
     list.push i

list #[0,0,1,1,2,2]


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

list = []

for i in [0..2]
  list.push i
  list.push i for j in [1..1]
    
list #[0,1,2,3]


В третьей строке можно использовать как префиксную так и постфиксную форму записи.

jQuery и пр.


Скажу сразу, для CoffeeScript не важно какая JavaScript библиотека у вас используется.

Начнем с самой главной функции jQuery.ready()

.ready():

$ -> @

Результат трансляции:
$(function() {
	return this;
});

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

Следующий метод jQuery в нашем списке — .each(), который почти эквивалентен стандартному методу .forEach()

$.each:

$.each [1..3], (i) -> i

Результат трансляции:

$.each([1, 2, 3], function(i) {
	return i;
});


ECMASctipt 6


Если вас не интересует будущее развитие стандарта ECMASctipt 6, можете смело пропустить этот раздел

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

К чему это я?

Дело в том, что будущий синтаксис ES6 практически более чем полностью не совместим с сегодняшним CoffeeScript.

К примеру инструкция for...of, сейчас носит более общий характер нежели это нужно:

[value for key, value of [1,2,3]]

На выходе мы получим следующее извращенство :

var key, value;

[
  (function() {
    var _ref, _results;
    _ref = [1, 2, 3];
    _results = [];
    for (key in _ref) {
      value = _ref[key];
      _results.push(value);
    }
    return _results;
  })()
];

//[1, 2, 3]

Будущий стандарт дает возможность использовать итерацию через объекты, куда проще:

[for i of [1,2,3]]

Здорово, не правда ли?

Также будет доступны генераторы выражений:

[i * 2 for each (i in [1, 2, 3])];
//2,4,6

Возможным станет и такая запись:

[i * 2 for each (i in [1, 2, 3]) if (i % 2 == 0)];
//2

Станут доступными и итераторы:

var object = {
	a: 1,
	b: 2
};

var it = Iterator(lang);

var pair = it.next(); //[a, 1]
	pair = it.next(); //[b, 2]

Итераторы также можно применять совместно с генераторами выражений:

var it = Iterator([1,2,3]);
[i * 2 for (i in it)]; //1, 4, 6


С выходом нового стандарта многие фишки из CoffeScript перестанут быть таковыми, а разработчикам ядра очевидно предстоит очень много работы, чтобы чтобы удержать «сахарные» позиции. Пожелаем им удачи.
Alexander Abashkin @monolithed
карма
27,0
рейтинг 0,0
Пользователь
Самое читаемое Разработка

Комментарии (72)

  • –1
    ДиапАзон.
    • –4
      Спасибо!
  • +12
    Имхо, такое малозаметное различие между [0..10] и [0...10] не очень хорошо для «языка», который делался «для упрощения и разработки более качественного кода».
    • +6
      Когда-то из-за точек в фортране падали космические корабли, а сейчас из-за точек в кофе просто не будут работать сайты. Прогресс налицо!

      Ничему люди не учатся.
    • +1
      Есть такие тонкости, но это не принципиальная вещь, которая бы делала coffeescript непригодным для реальных проектов.
      Можно: использовать .slice 0, 10 вместо [0...10],
      использовать разную подсветку синтаксиса в редакторе для [..] и [...],
      написать issue на трекер проекта или сделать pull request,
      сделать свой fork на github

      Эффект «упрощения и разработки более качественного кода» можно почувствовать только после начала самостоятельного использования coffeescript.
    • 0
      Когда руби учил, тоже смущался. Но после 10 минут привыкаешь.
      • 0
        Чёрт, везде оно :)
  • +9
    Вы меня простите, но считать точки — это как-то печально. Как по мне, ни разу не читабельней стандартной конструкции.
  • –1
    я в этом мире чего-то не понимаю, видимо. Для меня код на CoffeeScript не будет читабелен никогда.
    • +1
      Если не писать на CoffeeScript, то очевидно он останется не читабельным, но нужно выходить из зоны комфорта, пробовать другие языки. Я не говорю, что нужно бежать и использовать CoffeeScript, но говорить, что он не читабелен, когда его многие используют и он для них удобен, — не профессионально.
      • 0
        я пробую другие языки и платформы, но на этом я не смогу разрабатывать никогда. При чем тут профессионализм — я не понимаю.
        Пусть люди пишут, а мои мозги под это не заточены.
  • 0
    Первое впечатление: язык идеально подходит для того, чтобы кроме разработчика его не смог прочитать никто. Хотя насчёт того, что сам разработчик сможет это читать спустя эдак месяц, у меня сомнения.

    Это новомодная защита от копирования или язык всё-таки? :)
    • +3
      А вы попробуйте на нем писать. Уверен, что уже через пару часов вы измените свое мнение!
  • 0
    Странно. Вот такой вот код генерится, если в цикле for используется переменная:
    Andrey-Cherkashins-MacBook-Pro:~ andoriyu$ nano test.coffee
    Andrey-Cherkashins-MacBook-Pro:~ andoriyu$ coffee -p test.coffee
    (function() {
    var i, n;

    n = 2;

    for (i = 0; 0
    • 0
      Вероятно, вы забыли разделитель между выражением и телом цикла. Покажите как вы его написали.
      • 0
        не вставилось: gist.github.com/2212226
        • 0
          Этот случай относиться к так называемой «перестраховке» и генерации более безопасного выражения. Для того чтобы код получился более компактным нужно явно задать значение (без использование переменной).
          • +1
            Я понимю, я просто не слабо удивился когда вместо красивого цикла for, я увидел for с вложенными тернарными операторами,
            • 0
              Интересно, что какой-нибудь компилятор/минифаер сделает с этим кодом. Вполне возможно, что доведёт его до вида без тернарных выражений вообще.
  • +1
    Вообще честно сказать посмотрев в сторону CoffeeScript понял одно, что пока не готов его использовать. Пусть он упрощает кодирование и меньше кода получается, но вот то, что он выдает в файл .js мягко говоря, конфузит. Пока буду использовать синтаксис старого доброго JavaScript.
    • 0
      Приведите пожалуйста пример того, что вас вводит в этот конфуз.
      • 0
        Далеко ходить не буду приведу простой примерчик

        Содержимое CoffeeScript файла

            alert j for j in [1..5] 
                alert i for i in [0..2]
        


        Выходной javascript скрипт

        (function() {
          var i, j, _i, _len, _ref;
        
          for (i = 0; i <= 2; i++) {
            _ref = [1, 2, 3, 4, 5](alert(i));
            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
              j = _ref[_i];
              alert(j);
            }
          }
        
        }).call(this);
        


        А я всего лишь хотел чтобы было вот это:

        for(j=1; j<=5; j++){
             alert(j);
             for(i=0; i<=2; i++){
                   alert(i);
             }
        }
        

        • 0
          Не знаю версию вашего компилятора, но интерактивная версия на сайте этот пример не откомпилила:
          > PARSE ERROR ON LINE 2: UNEXPECTED 'INDENT'
          • 0
            CoffeeScript 1.2.0
          • +1
            У меня на этой версии все прекрасно компилируется. Я бы не стал выкладывать фейк. Не в моем вкусе.
        • 0
          Оно то хоть работает?
          • 0
            Если Вы имеете ввиду пример, то у меня интересовал результат преобразования из coffee-script в javascript. Результаты не вдохновили. В конце концов я указал лишь свое мнение и не более.
  • +3
    for i in [1..5]
      alert i
      for i in [0..2]
        alert i
    


    На выходе:
    var i;
    
    for (i = 1; i <= 5; i++) {
      alert(i);
      for (i = 0; i <= 2; i++) {
        alert(i);
      }
    }
    


    • –1
      Во-первых Вы ошибку допустили в скрипте. У Вас два раза i используется переменная.
      Во-вторых я не понимаю почему в офф документации используют именно этот вариант вызова alert

       alert j for j in [1..5] 
      


      и почему он не работает кода вдруг ниже появляется подобная инструкция со сдвигом?
      • +2
        Получается что я не могу доверять полностью CoffeeScript. Ибо мне нужно подразумевать, что некие инструкции могут восприниматься не так, как ты того ожидаешь.
        • +2
          Постфиксная запись операторов пришла из Perl, по крайней мере он ее популяризовал. Coffee в этом плане ничего от себя не добавляет.

          То, что вы пытаетесь одновременно использовать постфиксную и префиксную запись, не говорит о том, что язык плох, это говорит о том, что вы плохо изучили его возможности. Хотя, официальная документация по Coffeescript этот момент обходит и никаких замечаний не делает, наверное это не хорошо.

          Возможно, при знакомстве с этими возможностями, следует обращать внимание на такие правила:

          Если нужно писать код в одну строчку, используем либо постфиксную форму:

          return if smth < 0
          


          или префиксную с then

          if smth < 0 then return
          


          Заметьте, что код хорошо читается и похож на обычное предложение.

          Если вам нужно написать несколько строк в «теле» оператора, либо одну, но длинную или тяжело читаемую, лучше воспользоваться префиксной формой с идентацией:
          if smth < 0
             smth = 0
             foo = bar + 100500 
             return
          


      • 0
        Потому что в этом случае используется списочное выражение, и вся логика находится в левой части. Поэтому нужен явный перевод строки. В статье есть пример:
        • –1
          тогда почему не учитывается перевод строки после первого выражения for, ведь он явный. Странно как-то получается.
      • 0
        Потому что в этом случае используется списочное выражение. Поэтому нужно явно использовать перевод строки.
  • +2
    Во первых coffeescript это почти такой же javascript, только без {/}/; т.е. вы можете писать на javascript синтаксом, напоминающим питон. Во вторых то, за что я люблю coffeescript — это классы, и наследование. В третьих обращаю ваше внимание что любое действие в coffeescript возвращает последний шаг, к примеру если вы напишете mydiv.onclick = -> alert(123) то в итоге этот код скомпилируется в mydiv.onclick = function() {return alert(123);}; Ну и в четвертых — онлайн компилятор (кнопка try coffeescript на coffeescript.org/ ) иногда помогает анализировать выходной код.

    Написал в своё время на нём шахматы с использованием canvas. использовал coffee-script.js и type=«text/coffeescript». Так вот пока идёт трансляция и eval, проходит около минуты и в это время браузер просто виснет намертво. Отсюда следует что большой код лучше компилировать в js, а не пользоваться инлайн-ретранслятором
    • 0
      Во-вторых то, за что я люблю coffeescript — это классы, и наследование
      Вы так говорите, как будто в JS нет классов и наследования.
      • 0
        Наоборот, в жаваскрипте очень много классов и наследования. Штук пять несовместимых реализаций.
        • –1
          Реализаций чего? Там ровно одна реализация — прототипно-объектная модель. То что вы считаете «реализацией классов и наследования» — всего лишь попытка сделать ООП «как в Си++».
          • 0
            Буквально в субботу был на мастер-классе (у нас в Казани), было продемонстрировано минимум три реализации.
            • 0
              Три реализации «объектов как в Си++».
      • НЛО прилетело и опубликовало эту надпись здесь
        • –3
          Я как-то не заметил, что их там нет.
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Упаси боже, не путаю, конечно.
            • 0
              Скажите, в «Пайтоне» есть ООП? Там всё класс-объекты.
              • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      К сожалению с обратной совместимость не все так гладко, особенно когда речь идет о классическом способе создания объектов.
      На эту тему в ближайшее время постараюсь написать отдельную статью.
  • +1
    Спасибо! Хоть уже и использую пару месяцев coffeescript в одном своем проекте, но операция do до сих пор из-под моего внимаения ускользала. А вот сейчас взял и поменял пару мест у себя в коде, где эта операция пришлась ну очень кстати!

    Эх, если бы был еще вариент этой операции, который посволял такие вот случаи обрабатывать:

    (function(x) {

    })(arr.x)


    было бы вообще клево
    • 0
      ((x) ->)(arr.x)
      
      • 0
        У меня сейчас так и делается.
        Просто do — весьма изящный способ избавиться от лишних скобок
        • 0
          Можно сделать так:
          do([arr.x]) ->
          

          • 0
            Нельзя

            (function(_arg) {
            arr.x = _arg[0];
            })([arr.x]);


            это не совсем то, что хочется…
            В общем, придется дальше скобками обходиться…
            • 0
              Безусловно это извращений способ.

              arr = x:1
              do ([arr.x]) -> alert _arg #1
              

              • 0
                извращенный*
    • 0
      do (x = arr.x) ->
      • 0
        На момент написания статьи этой возможности в языке еще не было)
      • 0
        Ага, в 1.3.1. добавили.
  • +1
    CoffeScript по отношению к JS — это как Visual Basic по отношению к C#. Вроде больше слов, проще конструкции, весь язык такой… литературный. Но от обилия этого сахара видеть логику кода становится только сложнее.
    • 0
      Просто к ЖСу уже замыелн глаз и читая код ты не читаешь каждое слово или символ. Мозг уже сходу видит знакомые конструкции и куски кода и хватает тело цикло, а не его описание и т.д. А смотря новый код ты пыатешь прочитать абсолютно все и осмыслить, что затрудняет восприятие его няшности. В 1 проекте сейчас пишу на кофе, смысла использовать чистый жс в новых проектах больше не вижу :)
  • +1
    Для меня очень полезный пост, так как сам недавно начал использовать CoffeeScript, но примеры в одну строчку вроде:

    (if value is 1 then alert "#{key}: #{value}") for key, value of document

    мне кажется, могут отбить желание у новичков использовать прекрасный синтаксис CoffeeScript
  • 0
    К примеру инструкция for...of, сейчас носит более общий характер нежели это нужно:
    [value for key, value of [1,2,3]]

    На выходе мы получим следующее извращенство:
    var key, value;
    alert([
    


    Вот тут немного непонятно, откуда alert спарсился.
    • 0
      Спасибо, что заметили, alert лишний.
  • 0
    Добавил раздел про вложенные инструкции!
  • 0
    Эм, за напоминание о слайсах — спасибо, в отместку — там опечатка есть
    [list1, list2 ...]  #[1,2,3,4,5,6] 
    

    Не, чтобы получить плоский список надо сказать
    [list1..., list2...]
    
  • 0
    Алсо — с места
    [value for i, value of ['a', 'b', 'c'][0...2]] # a, b
    

    и до конца раздела — это вы о чем?
    Во-первых не нужно писать of [arr], нужно in [arr]
    Во вторых — смысла конструкции вообще не понял — может проще сразу было
     ['a', 'b', 'c'][0...2]
    

    не?
    • 0
      Спасибо что обратили на это внимание, должно быть так:

      foo = (value for i, value of ['a', 'b', 'c'][0...2])
      

      Во-первых не нужно писать of [arr], нужно in [arr]

      В данном случае, для получение значений массива использовать нужно именно of, а не in.
      Во вторых — смысла конструкции вообще не понял — может проще сразу было

      Суть примеров — показать возможности синтаксических конструкций, а не их эффективности
      • 0
        В данном случае, для получение значений массива использовать нужно именно of, а не in.

        ой ли?
        вот две записи, дающие одинаковый результат
        result_in = ( val for val in ['a', 'b', 'c'][0...2])
        result_of = ( val for key, val of ['a', 'b', 'c'][0...2])
        

        Но почему первая — корректна, а вторая — путь на темную сторону?
        Потому что:
        var key, result_in, result_of, val;
        
        result_in = (function() {
          var _i, _len, _ref, _results;
          _ref = ['a', 'b', 'c'].slice(0, 2);
          _results = [];
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            val = _ref[_i];
            _results.push(val);
          }
          return _results;
        })();
        
        result_of = (function() {
          var _ref, _results;
          _ref = ['a', 'b', 'c'].slice(0, 2);
          _results = [];
          for (key in _ref) {
            val = _ref[key];
            _results.push(val);
          }
          return _results;
        })();
        

        Мы же не будем спорить о том, насколько неэффективно обращаться с массивом как объектом?
        • 0
          Хм. действительно я пропустил пример с in.
          Спасибо за замечание!

          Мы же не будем спорить о том, насколько неэффективно обращаться с массивом как объектом?

          Вы должны были заметить что я об этом писал ниже под примером!
  • 0
    Алсо —
    alert i for i in [1..10] when i % 2 is 0
    

    ИМХО удачнее будет
    for i in [1..10] when not( i % 2) then console.log i
    
  • 0
    Алсо —
    expr = /foo*/g;
    alert "#{array[0]} : #{expr.lastIndex}" until (array = expr.exec('foofoo')) is null
    

    пример явно не удачный, потому как это же явно
    expr = /foo/g
    console.log "#{array[0]} : #{expr.lastIndex}" while array = expr.exec 'foofoo'
    
    • 0
      Нет, не должно, хотя бы по тому что раздел называется: инструкция until
      • 0
        Пример от этого удачнее не становится.

        counter = 5
        console.log counter-- until counter is 2
        

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.