
Цикл
for
хорошо послужил нам, но он устарел и должен уступить место более новым, функциональным техникам программирования.К счастью, этот факт не требует от вас быть мастером функционального программирования, более того, это то, что вы можете использовать в своих текущих проектах прямо сегодня!
Так в чем проблема цикла for в JavaScript?
Дизайн цикла
for
подталкивает к мутациям состояния (англ. mutation of state — изменение состояния — прим. переводчика) и применению сайд эффектов (англ. side effects — побочные эффекты — прим. переводчика), которые являются потенциальными источниками багов и непредсказуемого поведения кода.Все мы слышали, что глобальное состояние — это плохо, и что мы должны избегать его. Однако, локальное состояние разделяет те же проблемы что и глобальное, мы просто не сталкиваемся с ними так часто, ведь они проявляются в меньших масштабах. Фактически, мы никогда не решали проблему, а просто сводили ее к минимуму.
Однажды, используя мутабельное состояние (англ. mutable state — изменяемое состояние — прим. переводчика), значение случайной переменной изменится по неизвестной причине, и вы потратите часы на отладку и поиск причины изменения. У меня волосы на голове встают дыбом, только от одной мысли об этом.
Я бы хотел немного поговорить о сайд эффектах. Эти слова даже звучат ужасно, сайд эффекты. Дрянь. Вы хотите чтобы в ваших программах были сайд эффекты? Нет, я не хочу сайд эффектов в моих программах!
Но что такое сайд эффекты?
Считается, что у функции есть сайд эффекты если она модифицирует что-то за пределами своей области видимости. Это может быть изменение переменной, пользовательский ввод, запрос к api, запись данных на диск, лог в консоль и т.д.
Сайд эффекты — действительно мощный инструмент, но с большой силой приходит большая ответственность.
Сайд эффекты, по возможности, должны быть устранены или хотя бы инкапсулированы так, чтобы мы могли их контролировать. Функции с сайд эффектами тяжелее читать и тестировать, избегайте их всегда, когда это возможно. К счастью, в рамках данной статьи, мы не будем беспокоиться о побочных эффектах.
Меньше слов, больше кода. Давайте посмотрим на типичный цикл
for
, который вы вероятно видели сотни раз.const cats = [
{ name: 'Mojo', months: 84 },
{ name: 'Mao-Mao', months: 34 },
{ name: 'Waffles', months: 4 },
{ name: 'Pickles', months: 6 }
]
var kittens = []
// Типичный, плохо написанный цикл for
for (var i = 0; i < cats.length; i++) {
if (cats[i].months < 7) {
kittens.push(cats[i].name)
}
}
console.log(kittens)
Я собираюсь отрефакторить этот код шаг за шагом, чтобы вы смогли пронаблюдать как легко превратить ваш собственный код в нечто более прекрасное.
Во-первых, я извлеку условное выражение в отдельную функцию:
const isKitten = cat => cat.months < 7
var kittens = []
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
kittens.push(cats[i].name)
}
}
Выносить условия — это в целом хорошая практика. Изменение фильтрации с “меньше чем 7 месяцев” на “является ли котенком” — большой шаг вперед. Теперь код передает наши намерения гораздо лучше. Почему мы берем котов до 7 месяцев? Не совсем понятно. Мы хотим найти котят, так пусть код говорит об этом!
Другая польза в том, что
isKitten
теперь можно переиспользовать, а все мы знаем, переиспользование кода всегда должно быть нашей целью.Следующее изменение — извлечь преобразование (или отображение) кота его в имя. Это изменение будет понятнее позднее, а сейчас просто доверьтесь мне.
const isKitten = cat => cat.months < 7
const getName = cat => cat.name
var kittens = []
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
kittens.push(getName(cats[i]))
}
}
Я собирался написать несколько абзацев для описания механики работы
filter
и map
, но вместо этого, я покажу вам как легко читать и понимать этот код, даже увидев их (filter
и map
— прим. переводчика) впервые. Это лучшая демонстрация того, насколько читабельным может стать ваш код.const isKitten = cat => cat.months < 7
const getName = cat => cat.name
const kittens =
cats.filter(isKitten)
.map(getName)
Обратите внимание, мы избавились от
kittens.push(...)
. Никаких больше мутаций состояния и никаких var
.Код отдающий предпочтение const
перед var
и let
выглядит чертовски привлекательно
Конечно, мы могли использовать
const
с самого начала, так как он не делает сам объект иммутабельным (об этом больше в другой раз), но это придуманный пример, не наседайте!И последнее изменение, я бы предложил также извлечь фильтрацию и отображение в функцию (для полного переиспользования кода).
И все вместе:
const isKitten = cat => cat.months < 7
const getName = cat => cat.name
const getKittenNames = cats =>
cats.filter(isKitten)
.map(getName)
const cats = [
{ name: 'Mojo', months: 84 },
{ name: 'Mao-Mao', months: 34 },
{ name: 'Waffles', months: 4 },
{ name: 'Pickles', months: 6 }
]
const kittens = getKittenNames(cats)
console.log(kittens)
Домашнее задание
Изучить извлечение методов filter и map из их объектов. Задание со звездочкой: исследовать композицию функций.
- Functional JavaScript: Decoupling methods from their objects
- Functional JavaScript: Function Composition For Every Day Use.
Что насчет break
Многие из вас спрашивали, “Что насчет
break
”, посмотрите часть вторую серии “Переосмысление JavaScript: break это GOTO циклов”.Заключение
Пишите что вы думаете по этому поводу в комментариях. Умер ли для вас цикл
for
?Для вас это мелочь, но меня очень радует когда кто-то подписывается на меня на медиуме или в твиттере (@joelnet), а если вы считаете мой рассказ бесполезным, то расскажите об этом в комментариях.
Спасибо!
Комментарии (157)
k12th
18.05.2017 16:59+14Я сомневаюсь, что внутри несомненно любимого автором функциональненького React найдется так много map/reduce вместо циклов. Проблема в том, что безблагодатные императивные циклы существенно быстрее красивых трансдьюсеров, а, значит, в узких местах будут применяться они.
break это не goto, он не отправляет тебя в неизвестное произвольное место.
GlebkaF
18.05.2017 17:08-2В узких местах — возможно, но мы каждый день пишем огромное количество кода, который не является узким местом. Интерфейс, скорее всего, не будет лагать если мы напишем map вместо for.
Конечно, глубоко внутри все сплошная императивщина, но ее уже написали для нас, давайте этим пользоваться и писать красивый, декларативный код :)
Politura
19.05.2017 02:16Вот интересно, почему map/reduce медленнее циклов? Ведь любой map можно разложить в эквивалентный ему цикл for, так почему его реализация столь медленна?
Теоретически можно транслировать map в for на лету, при загрузке модуля, не затратив практически ничего, если вдруг нет возможности изменить реализацию map.k12th
19.05.2017 02:32+1Вот интересно, почему map/reduce медленнее циклов?
Да просто вызов функции это относительно дорого.
А еще все эти map/reduce/forEach делают кучу дополнительных проверок на каждом шагу, потому что должны работать с разреженными массивами, с массивами, в которых кто-то напихал кастомных свойства и прочими граничными случаями. Я не помню, как называлась либа, но там были на порядок более быстрые реализации всего этого дела — но которые работали только с «нормальными» массивами.
Ведь любой map можно разложить в эквивалентный ему цикл for
Теоретически можно транслировать map в for на летуМне кажется, это возможно только если итератор — чистая функция...
Ну ладно, map мы разложили в for автоматом, а если гений типа автора написал
filter().map()
? В общем, по хорошему, нужна поддержка частых ФП-паттернов, всяких там трансдьюсеров, и, возможно, у авторов движков дойдут до этого руки, если это станет достаточно частым сценарием.GlebkaF
19.05.2017 04:33-2В Ramda map имеет одинаковую с for производительность.
raveclassic
19.05.2017 10:27+1А замеряли как? jsperf?
lekzd
19.05.2017 10:14эти функции в движке написаны на JS, все дело в этом, цикл for, к слову, тоже можно затормозить неожиданными вещами, например в v8 недавно пофиксили let в цикле
Aingis
19.05.2017 13:18+2Вот интересно, почему map/reduce медленнее циклов? Ведь любой map можно разложить в эквивалентный ему цикл for, так почему его реализация столь медленна?
Всё дело в спецификации, которая не идентична циклу
for
. Если сделать именно через цикл, как, например, в fast.js или lodash.js, то будет примерно так же быстро (остаются ещё накладные расходы на вызовы функции).
А спецификация говорит, что map/foreach и аналоги должны пропускать так называемые «дырки». «Дырки» — это такие ключи, которые не инициализированы.? Например,
new Array(10)
не содержит ни одного ключа, и поэтому не вызовет обработчик ни разу. Из-за этого реализация методов в браузерах должна на каждом шаге проверять, а есть ли такой ключ.
? Если к ним обратиться, то будет
undefined
, но не стоит путать с тем случаем, когда значение установлено вundefined
.
SirEdvin
18.05.2017 17:06+11- Плохо не глобальное состояние, а глобальные переменные. Глобальным состоянием можно управлять, держать в одном месте и оно в целом может быть неизменяемым или мало изменяемым. С переменными так не получится.
- Причем тут ФП к циклу for? Side эффект от цикла for — это только переменная цикла, которая окажется снаружи.
- Первый пример — чем он лучше?
const isKitten = cat => cat.months < 7 const getName = cat => cat.name const kittens = cats.filter(isKitten) .map(getName)
У вас тут:
- Две лишних lambda функции
- Вместо одного цикла, у вас их тут два (в зависимости от реализации, но скорее всего).
И в чем выгода?
Пропаганда ФП это круто, но ФП далеко не везде и не всегда влазит.
Also, goto так не плох, плохо его неконтроллирумое использование в коде. Просто он позволяет слишком много.
Ну а break плох только в запутанных внутренних циклах с кучей логики, но такие циклы всегда выглядят или плохо, или не оптимизировано.GlebkaF
18.05.2017 17:18-1В ФП for либо не используется, либо используется крайне редко, ведь что угодно можно сделать через reduce.
Лишние лямбды кушать не просят, а если вас волнуют лишние циклы используйте трансдьюсеры.
ФП — инструмент, выбирайте инструмент исходя из задачи. Вопрос применимости конкретных инструментов обсуждался уже тысячу раз :)
SirEdvin
18.05.2017 17:26+2Лишние лямбды кушать не просят, а если вас волнуют лишние циклы используйте трансдьюсеры.
Очень зависит от. Но в целом лишние лямбды всегда едят больше, чем просто условия и циклы. И они не поддаются оптимизации.
В Android появилась эта странная мода на retrolambda, которые еще очень как тормозят. Они проигрывают где-то в 20 раз по скорости обработки вызовов.
GlebkaF
18.05.2017 17:36-2Конечно зависит, сильно зависит. Но я, в своем фронтенде, в 99% случаев готов идти на такие компромиссы. Я пока не сталкивался с вещами, где несколько лишних функций сыграли бы хоть какую-то роль, хотя не отрицаю того что такие вещи где-то есть.
SirEdvin
18.05.2017 17:39+4А потом мы получаем сайты, которые поедают все CPU и грузятся по пять минут.
Дело не в нескольких лишних функций, дело в подходе.
"Несколько" лишних функций в большом проекте превращаются в 500-1000 и это уже заметно везде.GlebkaF
18.05.2017 17:50-4Остановитесь, сайты жрут все cpu и грузятся долго не потому что ФП, map, filter или reduce, а потому что разработчик раздолбай. Говно код можно писать в любой парадигме и на любом языке.
А вот что бы на количестве функций экономить, это я от вас первый раз услышал рекомендацию, спасибо :)SirEdvin
18.05.2017 18:17+3Остановитесь, сайты жрут все cpu и грузятся долго не потому что ФП, map, filter или reduce, а потому что разработчик раздолбай. Говно код можно писать в любой парадигме и на любом языке.
Говнокод обычно заметно, и с ним можно боротся. А вот боротся с выбранным уровнем абстракции нельзя.
Каждый дополнительный уровень сверху — это оверхед. ФП такой же оверхед, который может привести к увеличению времени работы от 2 до 100 раз, если вычисления не ленивые, что вполне возможно.justboris
18.05.2017 18:27Иногда бывает и наоборот. Допустим, есть какая-то операция со списком. Через некоторое время нужно сделать еще одну почти такую же, но с некоторыми изменениями.
В случае цепочки map/filter переиспользовать часть коллбеков намного проще, чем в императивном коде с for, где все написано единым куском кода.
SirEdvin
18.05.2017 20:05+1В императивном коде такое можно легко поправить, завернув цикл for в функцию и просто в одном месте добавить флаг, который будет отвечать за это изменение.
Другое дело, что ФП штуки работают гораздо лучше и проще в случае работы с данными, но, в языках, в которых они для этого нужны.
Если у вас работа с данными не основная часть (а для сайта это врядли так), то я сомневаюсь, что ФП покажет себя лучше.di-strange
19.05.2017 10:43Пример в статье слишком надуманный, чтобы отразить необходимость использования ФП. И пока никто не привел реального примера ФП где он бы подошел. И странно как то выглядит, что сначала обругали jQuery с его $(selector).each(...).otherfunc(...), а потом пришли к .filter(...).map(...)
vanxant
18.05.2017 19:43+4ФП сам по себе может быть быстр, очень быстр. Но вот ФП в браузерном JS, особенно когда куча функционала реализована в либах, а не в браузере…
Представьте, что у вас на входе 10 000 котят. Миллион котят. Для цикла for нет вообще никакой проблемы вывести не всех, а только текущих видимых, в позициях с 12345-ого по 12435-го.
Что там у вас внутри filter/map и как это оптимизировать — а крен его знает.Mycolaos
19.05.2017 02:16Array.slice?
http://stackoverflow.com/questions/3978492/javascript-fastest-way-to-duplicate-an-array-slice-vs-for-loop
Кстати, специально пробовал заменить while на for — в 3 раза медленней.
raveclassic
19.05.2017 10:48А если попробовать за-map/reduce-ить котят в вебворкеры? Штук по 10к-100к для миллиона? For-то все-равно быстрей будет, но так для интереса? Хотя, наверное большую часть времени займет доставка котят до воркера и обратно.
JSmitty
20.05.2017 07:19Классически замена циклов — не reduce, а рекурсия. И отсутствие TCO в JS этому приему не дает возможности эффективно применяться.
vasIvas
18.05.2017 18:10+1Я на днях статью увидел где на картинке фп стоит выше чем ооп и так же автор уверял что фп это новая парадигма 90 года. То есть фп пропаганда идет ну очень сильно и если раньше казалось что это откидывает людей назад, то сейчас мне кажется что это даже лучше, ведь как ещё научить людей писать правильный ооп, который по факту впитал фп и сделал его ещё лучше. Любой грамотный ооп-шник скажет что в ооп все тоже самое, но вот люди начали это осваивать только тогда, когда им начали подавать это на чистом подносе-фп. Возможно так им легче.
Только мне не понятно почему до сих пор не начали говорить что код в статье это не фп, ведь в фп вообще нет переменных, а то фп где есть переменные, это ооп. В фп не может быть объявлений const, var, или let.GlebkaF
18.05.2017 18:15-1Код статьи написан в функциональном стиле, т.е. не мутирует переменных и использует чистые функции.
Действительно, основополагающие принципы у обеих парадигм одинаковые, но их реализация имхо ортогональна, поэтому говорить что ООП впитал ФП не совсем корректно
p.s. А как бы вы переписали код из статьи на JS?
SirEdvin
18.05.2017 18:19Переменная объявленная как неизменяемая вряд ли переменной остается.
Так же лисп функциональный, но переменные в нем все равно есть
Ciiol
19.05.2017 02:17Не назвал бы лисп прямо таким функциональным (мы же о Common Lisp?). Да, присваиваний в нормальном коде не увидишь, но и функциями типа map/reduce пользоваться там не слишком удобно.
Зато, возвращаясь к теме статьи, в нём самый шикарный цикл for из всех языков, что я видел (точнее конструкция loop и библиотека iter как развитие этой идеи). Там решение задачи типа «найти такой элемент массива A, что функция F от следующего за ним элемента примет максимальное значение» будет выглядеть примерно как сам текст этой задачи. В чистом ФП же тут будет жонглирование zip, map, filter, etc. А в условном C или императивном js будет куча вспомогательных переменных и манипуляций с ними. Оба варианта как-то так себе.
wheercool
18.05.2017 18:24Можно поинтересоваться почему в ФП не может быть const и let?
Const в них используются неявно, а let есть ничто иное как псевдоним, для некоторого выражения, наподобии того как в математики вводят дополнительные переменныеraveclassic
18.05.2017 20:10а то фп где есть переменные, это ооп
в математики вводят дополнительные переменные
Видимо, математика — это ооп. =/
Akon32
18.05.2017 17:10+3const isKitten = cat => cat.months < 7 const getName = cat => cat.name const kittens = cats.filter(isKitten).map(getName)
Красиво, но, вероятно, раза в 2 медленнее. Критичные части приходится переписывать в императивщину.
TheShock
18.05.2017 17:16+2На самом деле медленнее значительно больше, чем в два раза, но все-равно линейно, потому в большинстве программ, которые пишут на ФП (а на нем все-равно ничего сложного написать нельзя) — это не критично.
saluev
18.05.2017 18:25В написанном хорошо коде переписать узкие места более эффективно можно в любой момент (особенно заменить мапы и фильтры на циклы). Преждевременная оптимизация — корень всех зол.
Regis
18.05.2017 19:08+6Преждевременная оптимизация — корень всех зол.
Эх, как же надоела эта вырванная из контекста цитата.
YourDesire
23.05.2017 14:12+2Прошу прошения. Не подскажете, из какого контекста эта фраза?
staticlab
23.05.2017 16:07+1There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified. It is often a mistake to make a priori judgments about what parts of a program are really critical, since the universal experience of programmers who have been using measurement tools has been that their intuitive guesses fail. After working with such tools for seven years, I've become convinced that all compilers written from now on should be designed to provide all programmers with feedback indicating what parts of their programs are costing the most; indeed, this feedback should be supplied automatically unless it has been specifically turned off.https://www.cs.sjsu.edu/~mak/CS185C/KnuthStructuredProgrammingGoTo.pdf
Regis
26.05.2017 04:08Подчеркну, что здесь Кнут говорит не "забейте на заботу о производительности до тех пор, пока не окажется, что у вас всё тормозит" (как это обычно подразумевают цитирующие), а "заботьтесь о производительности там, где это важно и даст наибольший эффект".
SirEdvin
18.05.2017 20:06Поэтому, вместо вызова у списка метода sort вы каждый раз пишите два цикла сортировки вставкой?
saluev
19.05.2017 09:44Нет, я пишу вызов метода sort(), а при необходимости анализирую, нужно ли заменить встроенную сортировку на какой-то конкретный алгоритм, больше подходящий для задачи.
SirEdvin
19.05.2017 10:46+1Но ведь вы не пишите сначала свою сортировку пузырьком, что бы потом менять ее в хорошо написанном коде, правильно?
Если мы знаем, что у нас каждая такая цепочка порождает еще один цикл и мы получаем переизбыток циклов и не особо то и выигрываем в читаемости — зачем?
saluev
19.05.2017 11:22Ну, на мой вкус, в читаемости мы выигрываем весьма ощутимо.
SirEdvin
19.05.2017 11:25Уже приводили контрпример немного ниже.
Как только названия фильтров начинают превращатся в "isValid" или что-то такое, то все превращается в кашу. Далеко не всегда по фильтру можно понять, что конкретно он делает, а в случае с циклом for + if практически всегда.
А если так не делать, то получается гиганская цепочка фильтров, которую тоже сложно читать.
Akon32
19.05.2017 14:22Константирую тот факт, что в иных проектах узкие места встречаются столь часто, что даже написание сразу циклов не будет преждевременной оптимизацией.
Но, естественно, если котят в списке значительно меньше десятков миллионов, пара лишних созданных в map-filter массивов не сильно повлияют на скорость.
TheShock
18.05.2017 17:14+3Хоронили if, порвали три редакса.
cats.filter(isKitten).map(getName)
Ну сколько можно то? Еще примитивней пример нельзя привести? Или только для таких основ и годятся все эти тренды? Да, на однострочниках ФП выигрывает. Ну вот на однострочниках его и можно применять. А чем длинеее — тем более трудноподдерживаемым код становится
Drag13
18.05.2017 17:25+4Вообще проблема .map и прочей функциональности (как по мне конечно) что код исполняется «где то там». И вместо того что бы сходу посмотреть что происходит в цикле, нам нужно прыгать по анонимным методам и искать там… Для однозначных вещей это ок, но когда задачи идут менее тривиальные все становится банально не удобным.
wheercool
18.05.2017 17:47А я вот противоположной точки зрения
Глядя на композицию map, filter, reduce можно сразу выделить структуру даже не вникая в детали (банально если заканчивается reduce — значит результат одиночное значение, иначе — массив). С циклом же нужно полностью изучить весь код, чтобы выявить какую-то структуру.Drag13
18.05.2017 18:06А зачем вам структура? Структура описана или классом или интерфейсом. И посмотреть ее можно и так. А вот что там происходит это нужно дебажить… А дебажить чистые циклы проще, лично мне. Но тут разговор не об этом. .filter((cat)=>cat.isBlack ) мне гораздо больше нравится чем for(){}. Но при этом я не рискну утверждать что for умер.
Aingis
18.05.2017 17:55+10Я рад, что автор открыл для себя
.map()
из ES5, он действительно удобнееfor
, но это 2011 год. Если он изучил ещё и.reduce()
, то смог бы обойтись одним проходом вместо двух.
Но зачем публиковать это сейчас, в 2017, на Хабре? Да ещё с таким ужасным переводом. Есть русский термин «побочный эффект». Он не «применяется», а появляется.
Тем временем в ES2015 появилась разновидность цикла
for-of
, которая гораздо элегантнее.foreach()
со вложенной функцией (когда отдельная функция не нужна). С учётом этого говорить о смертиfor
может только невежда.
апофеоз переводамутабельное состояние (англ. mutable state — изменяемое состояние — прим. переводчика)
Что мешало сразу написать «изменяемое состояние»?
AlexZaharow
18.05.2017 17:57+1Очень интересно посмотреть на выход из map по break?
wheercool
18.05.2017 18:05+2Смысл map в том что производится трансформация над каждым элементом (можете считать что это групповая операция) и кол-во элементов на входе и на выходе обязано быть одинаковым
SirEdvin
18.05.2017 20:07По идее, нужно просто фильтр перед map сделать для такого.
TheShock
18.05.2017 20:10+2А если брейк по результатам вычисления, а не до результатов?
SirEdvin
18.05.2017 20:12Сформулируйте задачу детальнее, пожалуйста.
В случае, если ФП сделано правильно, через ленивые вычисления, то можно сделать фильтр после. Что-то в духе:
map(тут ваши вычисления).filter(тут ваше условие выхода).any()
Должно помочь получить нужные данные.
Это если брать задачу "нужно найти первый такой элемент в цикле". Если задача другая, то надо будет выкручиватся по другому.
TheShock
18.05.2017 20:14Хорошо, давайте обсудим ваше условие — «нужно найти первый такой элемент в цикле». Ну или «найти максимум 5 элементов, удовлетворяющих результатам поиска».
В случае, если ФП сделано правильно
А если ФП у нас JS?gearbox
18.05.2017 20:21Да и в случае правильного fp есть ряд задач плохо решаемых редьюсерами. 21 очко на колоде карт?
TheShock
18.05.2017 20:2421 очко на колоде карт?
Объяснитеgearbox
18.05.2017 20:45+1колода карт, перетасована, надо выбирать карты пока не выпадет 21 очко или больше. Редьюсер пойдет по всему массиву а нужен выход. Сделать фильтр не получится — такой же полный перебор.
Large
23.05.2017 02:36-2для этого есть трансдьюсер
gearbox
23.05.2017 15:11-3вы просто новое слово выучили или реально понимаете о чем говорите? Трансдьюсер — это грубо говоря генератор редьюсеров. Какой редьюсер он должен дать что бы решить эту задачу ОПТИМАЛЬНО без выхода? С учетом того что редьюсер не должен выходить, если он выходит — это уже обычный цикл в функциональной обертке. То есть изначально вы не решите ОПТИМАЛЬНО задачу редьюсером если она ОПТИМАЛЬНО решается неполным проходом. Редьюсер по своей природе подразумевает полный проход. Если для решения задачи полный проход не нужен — значит для решения этой задачи не нужен редьюсер.
Large
23.05.2017 15:17-2фу как грубо! а не грубо трансдьюсер это мидлвар который может выходить когда нужно и никакого полного прохода не требуется.
gearbox
23.05.2017 15:35-3>это мидлвар который может выходить когда нужно и никакого полного прохода не требуется.
В этом момент мы прекращаем назвать его или результат его работы редьюсером и не нарываемся на грубости на хабре. Если вам нужен фолдинг — делаем редьюсер и делаем полный проход. Если полный проход не нужен — делаем рекурсию или итерацию но не называем это фолдингом. Либо называем фолдингом но тогда меняем структуру и берем не массив а список, в котором после получения результата не применяем редьюсер к остатку. На массиве редьюсер подразумевает полный проход, изначально мы говорили за массив. Для преобразования его в нужную структуру нужно применить логику в которую и перекочует логика выхода (трансформированная но тем не менее) которую вы пытаетесь запихнуть в редьюсер.Large
23.05.2017 15:41-3Грубости они не от хабра, а от воспитания. Вас так учили, нас по другому. Я про редьюсер вам вообще ничего не говорил.
gearbox
23.05.2017 16:58-3>Я про редьюсер вам вообще ничего не говорил.
Вы говорили про трансдьюсер. Трудно упомянуть трансдьюсер и не ввести при этом в контекст обсуждения редьюсеры.
Беда и хейтеров и хипстеров как правило просто в непонимании предназначения инструмента. Конкретно редьюсер нужен для того что бы не хвостовую рекурсию сделать хвостовой, это его предназначение, это то куда он ложится красиво.
То есть имеем:
f(a) {
return a == 0? 0: a + f(a-1);
}
это нехвостовая рекурсия, не соптимизируешь. Выносим вычисления в передачу аргумента:
f(a, prev){
return a == 0? prev: f(a-1, a+prev);
}
внезапно рекурсия стала хвостовой а функция — редьюсером. Для этого финта надо иметь нулевой элемент определенный на последней операции, для сложения это ноль. Значит первый вызов будет
func(a, 0);
Все, теперь можно делать фолдинг на ком угодно, это нормально оптимизится.
Large
23.05.2017 15:23-3вот пример из рамды http://ramdajs.com/docs/#transduce take(2) выходит после 2х элементов.
TheShock
23.05.2017 15:34-3А пример из Рамбды, когда выходит после N элементов, ума которых больше 20?
Large
23.05.2017 15:42-2Паша, зачем тебе пример из Рамды? Я думаю ты сам можешь написать функцию, которая вместо проверки длинны массива результатов выходит при другом условии.
TheShock
23.05.2017 15:52-2Та я ж тут чисто достаю всех, а сам даже FizzBuzz не напишу)
Ты предлагаешь такую функцию написать в императивном стиле с умершим for?Large
23.05.2017 16:00-2Да, ты тот еще… кодер ))
Да нет, ее можно в функциональном написать, просто нужно еще состояние добавить аргументом (текущую суму), а реализацию трансдьюсера через фор делать, так быстрее.
Статьи эти смешные конечно, они на самом деле не о функ программировании а как-то вообще не о чем. Функ программирование по моему это скорее про структуры данных, тут ни слова о функторах и фолдебл, зато много пафоса про фор мертв.
Из реальных примеров все что генерируется компилятором удобнее писать в функ стиле так как оно тупо менее многословно и подчиняется простым правилам. А вот реализация этого функ стиля уже делается этими самыми мертвыми фор =)
GlebkaF
19.05.2017 04:38В правильном fp функция reduce умеет проверять у себя флаг reduced и выходить когда ей потребуется.
raveclassic
19.05.2017 10:27+1Покажите, как вы из reduce выходите. И объясните, что значит выйти из reduce, если идеологически reduce подразумевает проход по всем элементам?
faiwer
19.05.2017 11:36-2Да вариантов много, зависит от взглядов автора библиотеки. К примеру в
lodash
естьtransform
. Это такой мутабельныйreduce
, которые помимо мутабельности итогового значения умеет выходить из цикла за счётreturn false
.
GlebkaF
19.05.2017 12:23Откуда информация что reduce должен проходить обязательно по всем элементам коллекции?
Вот map — да, подразумевает обход всех элементов.
reduced в clojureScript, по большому счету — дополнительный внутренний флаг isReduced по которому происходит выход из reduce.raveclassic
19.05.2017 13:01
SirEdvin
18.05.2017 20:21+2А если ФП у нас JS?
Я думаю, вам сюда. Как я понял, ленивые вычисления в фп называются трансдьюсеры и точно что-то такое там есть. К сожалению, я знаком с такой штукой для Java, которую они добавили в восьмой версии.
В случае, если использовать их синтаксис, задачи будут выглядит так:
stream.filter(x => x.state == "ok").findAny()
Второе будет выглядить так:
stream.filter(x => x.state == "ok").limit(5)
AlexZaharow
18.05.2017 23:06т.е. в for фильтр был не нужен, теперь в статье предлагается похоронить for и использовать вместо него map, но вот загвоздка — как жить без фильтра, которого в for не было?
Думаю, что вполне можно было бы ограничиться сведениями, в которых map хорош и удобнее, чем for, но в задачах определённого класса, в которых конструкция for достаточно громоздка.
Мне вот не очень понятно, зачем вводить map в стандарт, когда эту функцию можно написать в прототип массива и без всяких стандартов и выглядеть она будет точно так же?
JSmitty
20.05.2017 06:50У автора оригинальной статьи есть продолжение — Rethinking JavaScript: Replace break by going functional
TheShock
20.05.2017 07:03+3У автора талант из нормального кода писать неподдерживаемое говнище. Кто еще не заходил — вам загадка. Что делает эта функция и как она могла бы быть названа? Только засеките время на разгадывание этого ребуса.
const FUNC_NAME = (limit, predicate, list, i = 0, newList = []) => { const isDone = limit <= 0 || i >= list.length const isMatch = isDone ? undefined : predicate(list[i]) return isDone ? newList : isMatch ? FUNC_NAME(limit - 1, predicate, list, i + 1, [...newList, list[i]]) : FUNC_NAME(limit, predicate, list, i + 1, newList) }
А потом сравните с промышленным, а не хипстерским кодом:
Скрытый текстconst FUNC_NAME = (limit, predicate, list) => { const newList = [] for (var i = 0; i < list.length; i++) { if (predicate(list[i])) { newList.push(list[i]) if (newList.length >= limit) { break } } } return newList }
faiwer
20.05.2017 08:51+2Боже ж ты мой. Главное чтобы это потом не приснилось. Второй кусок кода предельно очевиден: отфильтровать из list только то, что проходит predicate, но не более limit элементов. А первое?
- Рекурсия… Представим что limit около 10'000… и всё это в стек. Кошмар
[...newList, list[i]]
в рамках рекурсии для аккумулируемого значения. Какая там асимптотика будет у такого решения?O(n^2)
?- Даёшь тернарный оператор внутри тернарного оператора, чтобы ты мог написать терна...
Я вот не могу понять. Это издержки фанатизма? Или человек действительно не понимаешь, что за дичь он пишет? А ведь потом это ещё и переводят, т.е. это "популярно" и "востребовано".
MikailBag
25.05.2017 22:03Именно рекурсия не проблема, т.к. соптимизируется.
Но это, конечно, не отменяет превышение нормы СЭС по показателю hipsterity/line)faiwer
26.05.2017 06:49-2const a = new Array(10000); for(let q = 0; q < 10000; ++ q) a[q] = Math.random() const predicate = a => a > 0.5; FUNC_NAME(10000, predicate, a) // Uncaught RangeError: Maximum call stack size exceeded
MikailBag
26.05.2017 20:38+1В ES2015 ввели правильную хвостовую рекурсию.
Только что проверил на массиве из 10000 элементов.
node 7.7.2
AlexZaharow
22.05.2017 14:55Но в этом продолжении нет выхода из map в любой момент времени. Я легко выдумываю условие выхода — если текущее время больше 14:00:00 (часы: мин: сек), то выйти из цикла. Все фильтры летят в мусорную корзину, потому что если в работе обход массива занимает 10 сек и я запущу фильтр в 13:59:45, то в конечный массив попадут все элементы массива. Затем я запускаю перебор в 13:59:59 и получаю полный перебор массива до 14:00:09, хотя он должен был остановиться в 14:00:00 по break.
raveclassic
22.05.2017 17:02нет выхода из map в любой момент времени
А это идеологически неверно. Map — проекция всех элементов, и выхода из нее нет. Если нужен выход, значит не нужен map.AlexZaharow
22.05.2017 22:57+1Бинго. Я же раньше и написал, что map решает свои задачи, которые только частично перекрываются с for. Поэтому вроде как смерть for откладывается? )))
Maiami
18.05.2017 18:09+2О каком отсутствии сайд эффекта можно говорить, если я могу случайно сделать вот так:
const isKitten = cat => cat.months = 7
И никакой const не поможет
Поэтому в условиях невозможности гарантировать отсутвие сайд-эффекта, можно только договорится, что его не будет. Но в таком случае разница между
const getKittenNames = cats => cats.filter(isKitten) .map(getName)
И
const getKittenNames = cats => { const kittens = [] for (let cat of cats) { if (isKitten(cat)) { kittens.push(getName(cat)) } } return kittens }
Только в количестве строк и скорости выполнения. Или в читаемости, кому-то больше filter-map/reduce нравится, а кому-то for-ofJSmitty
20.05.2017 06:45-2Код существует не в вакууме обычно, поломка такого вида выявляется юнит-тестом сразу же (из isKitten() возвращается уже не boolean). Если его нет — существуют Immutable структуры данных (одно решение тянет за собой цепочку других).
cjbars
18.05.2017 18:33Вот вы мне скажите: const подразумевает собой константу, а в итоге оказывается функцией, в которую еще и что то передать надо. Это по вашему чистый код который говорит сам за себя?
Если я хочу число пи, то ИМХО должно быть const pi = 3.14; и все это константа!
Может я чего то не понимаю?Drag13
18.05.2017 18:35+1В JS функции объекты первого порядка. Т.е. грубо говоря такие же как int или string в других языках. Так что const function вполне себе ничего.
cjbars
18.05.2017 18:55+2Ну ок, с этим более менее согласен.
Попробую побухтеть про читабельность.
Вариант с циклом: пройтись по всем котам, если возраст кота меньше заданного (вот тут бы константу) — запомнить имя кота.
Вариант без цикла: пройтись по всем котам, и если кот удовлетворяет условию(сходить узнать условие) то пометить кота. Затем взять помеченных котов и сделать с ними что-то ( надо сходить куда-то за действием). Результатом будет список котят.
Чот как то много бегать придется, и выше справедливо заметили про сложность прерывания данного процесса.
Но это я так бухчу ;-)Drag13
18.05.2017 18:59+2Почему «бухчу»? Абсолютно справделивые замечания. Поэтому вся функциональная парадигма должна быть использована в правильном месте и будет все прекрасно.
Но нельзя не отдать должного что она приучает к Single Responsibility в функциях, что, как по мне, просто прекрасно.
GlebkaF
18.05.2017 19:14Вся прелесть в том, что при правильном именовании функций и переменных вам не нужно идти и изучать детали реализации. Вот же, прямо в коде написано, отфильтруй по признаку isKitten, а затем получи имя.
Это скорее непривычно поначалу, записи типа
const sum = a => b => a + b console.log(sum(2)(3))
мне первое время давались с трудом, а теперь жить без них не могу :)cjbars
18.05.2017 19:24+3Но тут внезапно всплывает известная проблема — как назвать эту чертову функцию? :-)
TheShock
18.05.2017 19:39+2Это только вначале, когда у вас примеры уровня isKitten. А потом вы делаете
var validUsers = users.map(isValid);
Но isValid, оказывается, проверяетuser.name !== 'Vasek'
, а неuser.access.contains(ADMIN)
. Но вы слишком горды, чтобы пользоваться типизацией, вы ведь модны и молодежны, а типизация — для старперов и теперь только Васек и не имеет доступ к вашей админ-панели.
А отвратительные названия — это одно из проклятий ФП в ЖС. ВедьcanUserAccessAdminPanel
пишут только джависты, по-молодежному надо написатьis_ok
.
Вот некоторые названия функций из модных и молодежных библиотек:
- pipe - it - connect - put - dispatch
Да-да, это именно те «емкие и понятные» названия, в которые не нужно заходить, чтобы понять, что они делают. А ведь это библиотечные. А еще сколько будет локальных для приложения.
Nicklasos
18.05.2017 18:41+7Как использовать map/forEach/filter вместе с async/await?
for (const foo of bar) { const result = await doSomethins(foo); }
JSmitty
18.05.2017 21:29-2return bar.reduce((r, v) => r.then(doSomething.bind(null, v)), Promise.resolve());
PS после транспайлинга это будет выглядеть (и работать) лучше, чем async/await. Прямая противоположность ситуации с for :)JSmitty
20.05.2017 06:28Пример для последовательного выполнения. Напоминаю, что в JS async/await — всего лишь сахар поверх промисов. И да, если кто не в курсе, генераторы (через который выражается async функция в Babel) порождают очень странный код в конечном ES5.
IIIEII
19.05.2017 00:40Примерно так:
const result = await Promise.all(bar.map(doSomething)
В качестве бонуса — параллельное выполнение. Если нужно именно последовательное — пригодится reduce
muxa_ru
18.05.2017 18:42+3Я дико извиняюсь, но почему ничего не сказано про скорость работы этих вариантов?
GlebkaF
18.05.2017 18:52-4По большому счету, в js map — это сахар на while, посмотрел здесь: map Polyfill.
Код в примере использует map и filter, вместо одного for, вероятно, что он работает в 2 раза медленнее.TheShock
18.05.2017 19:11+1С вызовом функции, которая значительно более дорогостоящая операция. Два цикла — это еще фигня в сравнии с вызовом функции на каждую операцию, потому там не в 2 раза медленнее, а на порядок.
TheShock
18.05.2017 19:53+8Да, вы правы, кто меня минусует. Я ошибся с порядком. Не на порядок, а на два порядка, то есть в сто раз. Функциональный вариант ровно в сто раз медленнее, чем классический:
faiwer
18.05.2017 19:59-4Не стоит серьёзно относится к подобным тестам на jsperf. jsperf не чурается модифицировать предоставленный код, изменяя его порой, весьма существенно. Бенчмарки сами по себе редко бывают объективны, но если уж хочется с ними поиграть, то лучше запускать их за пределами таких площадок (скажем в node или просто используя профилирование/console.time браузера).
TheShock
18.05.2017 20:05+2Как скажете. Получилось… разница в 100 раз. Увеличил число, чтобы было видно, что количество цифр одинаковое и уж слишком мало получалось в классическом варианте:
raveclassic
18.05.2017 20:15Ну так вы ж лукавите :) Сколько у вас в fp прогонов, а сколько в classic? Не, я не спорю, все верно, map/reduce и компания банально увеличивают количество прогонов. Но можно изловчиться и написать на трансдьюсерах. Другое дело, зачем?..
TheShock
18.05.2017 20:19+2Простите, в fp-варианте количество прогонов в три раза больше, чем в классическом варианте, а не в сто раз.
Я лишь стараюсь убедить, что утверждение ложно:
Код в примере использует map и filter, вместо одного for, вероятно, что он работает в 2 раза медленнее.
Основной источник увеличения нагрузки не три цикла for вместо одного (это как раз совершенно не страшно), а «три тяжеловесных вызова функции + три операции» вместо «три операции» на каждую итерацию. То есть у нас сам вызов функции занимает в сто раз больше времени, чем операция, которую эта функция выполняет.
И это особенность именно JS, а не FP в целом.maeris
18.05.2017 22:53Отдельно стоит заметить, что если даже написать свои собственные map и filter, они зачастую будут работать быстрее. Потому что встроенные методы реализованы на сишке, и вызываются через FFI, с соответствующими затратами на маршаллинг аргументов и возвращаемого значения.
raveclassic
22.05.2017 10:57Где я мог облажаться?
const numbers = []; for (let i = 0; i < 1000000; i++) { numbers.push(i); } function classic() { console.time('classic'); let result = 0; const mapped = []; for (let i = 0; i < numbers.length; i++) { mapped.push(numbers[i] + 1); } const filtered = []; for (let i = 0; i < mapped.length; i++) { const value = mapped[i]; if (value % 3 === 0) { filtered.push(value); } } for (let i = 0; i < filtered.length; i++) { result += filtered[i]; } console.timeEnd('classic'); return result; } function fp() { console.time('fp'); let result = numbers.map(n => n + 1).filter(n => n % 3 === 0).reduce((acc, n) => acc + n, 0); console.timeEnd('fp'); return result; } console.log(classic()); console.log(fp()); console.log('---'); console.log(fp()); console.log(classic()); console.log('---'); console.log(classic()); console.log(fp()); console.log('---'); console.log(fp()) console.log(classic()); console.log('---'); console.log(classic()); console.log(fp()); console.log('---'); console.log(fp()) console.log(classic()); console.log('---'); console.log(classic()); console.log(fp()); /* VM3953:30 fp: 336.918701171875ms VM3953:34 166666833333 VM3953:23 classic: 259.02001953125ms VM3953:35 166666833333 VM3953:37 --- VM3953:30 fp: 294.345947265625ms VM3953:38 166666833333 VM3953:23 classic: 188.5771484375ms VM3953:39 166666833333 VM3953:41 --- VM3953:23 classic: 233.133056640625ms VM3953:42 166666833333 VM3953:30 fp: 303.2099609375ms VM3953:43 166666833333 VM3953:45 --- VM3953:30 fp: 273.15283203125ms VM3953:46 166666833333 VM3953:23 classic: 232.2529296875ms VM3953:47 166666833333 VM3953:49 --- VM3953:23 classic: 224.306884765625ms VM3953:50 166666833333 VM3953:30 fp: 272.4609375ms VM3953:51 166666833333 VM3953:53 --- VM3953:30 fp: 266.1357421875ms VM3953:54 166666833333 VM3953:23 classic: 377.88623046875ms VM3953:55 166666833333 VM3953:57 --- VM3953:23 classic: 176.39013671875ms VM3953:58 166666833333 VM3953:30 fp: 294.071044921875ms VM3953:59 166666833333 */
TheShock
22.05.2017 16:37-1Возможно, я ошибся с первопричинной и основная нагрузка — создание и наполнение новых массивов на каждой итерации, а не вызов функции?
Aries_ua
18.05.2017 18:50+3Хочу добавить, что с async / await такие циклы работать не будут. Возможно не к месту мое замечание. Но когда переходили на async / await я был удивлен, что это перестает работать для forEach (в доке потом выяснил что к чему). Так что старый добрый цикл выручил.
faiwer
18.05.2017 19:40+3Какой-то оголтелый фанатизм. И с каждым днём таких статей всё больше. Главное заголовок сделать как можно более пафосным (смерть for). В следующий раз накал надо ещё пуще нагнать. Как насчёт хтонических исчадий из преисподней?
Фунциональное программирование интересная и полезная штука, если ваш язык позволяет из него взять что-то полезное. Императивное программирование напротив тоже очень интересная и полезная штука. Мир не делится на белое и чёрное. И задачи можно решать выбирая наиболее удобные и привычные для себя инструменты. Выбирая один набор преимуществ, вы неизбежно приобретаете к нему такой же набор недостатков.
Скажем мутабельность и иммутабельность. И то и другое может быть весьма удобным и к месту. Важно только понимать, где чего стоит избегать (умоляю — избегайте избыточных аллокаций в
.reduce
).
Вот скажем мои 5 копеек по сабжу:
map
&reduce
в качестве заменыfor
удобная штука, т.к. код получается несколько нагляднее, ибо синтаксис конструкцииfor
не позволяет красиво возвращать новый объект как результат операций над итерациямиforEach
вместоfor-of
по мне так, на мой вкус, выглядит очень… неопрятно, требует лишнего метода, фигурных скобок. В случае одиночного пробега по какой-либо коллекции я прибегаю кfor-of
как раз в рамках читаемости. Особенно это удобно в случае 2-3 вложенныхfor
.- цепочки трансформаций из ФП — очень удобная и полезная штука. Однако мы тут сталкиваемся с двумя весьма пренепреятнейшими проблемами:
- в JS нет
pipe
-оператора. В итоге если цепочка какой-либо метод не умеет, то приходится либо оборачивать её всю целиком (читаемость катастрофически падает), или делить цепочку на несколько, добавляя какие-нибудь вветвления, либо делать какие-нибудь.tap
методы. Тоже касается и композиции методов, без pipe оператора выглядит это ну прямо безобразно. - в JS из коробки нет никаких удобств для работы с callback-ами в
async & *
контекстах, а это бывает ну очень актуально
- в JS нет
- ФП подразумевает очень хорошее понимание работы и методов своей ФП библиотеки. Для нового человека ваш код может напоминать набор слов. А если учесть что ряд из либ ещё постоянно меняет свои сигнатуры и названия методов (камень в сторону lodash)...
В своём коде я не чураюсь использовать любые известные мне подходы, исходя из задач. Не понимаю я этого фанатизма.
maeris
18.05.2017 22:50А если ещё на jsperf сравнить
for
иmap
, то станет понятно, почему в каждом angular и react внутри толькоfor
.
dom1n1k
18.05.2017 19:41+1Недавно сравнивал for и forEach на реальной задаче — оказалось, что в ноде (последняя LTS версия) forEach на порядок медленнее. Я без понятия откуда такая огромная разница, в браузерах обычно отличия невелики.
TheShock
18.05.2017 19:54Потому что кроме самой операции (каждая итерации очень дешевая) допольнительно происходит вызов функции, который очень тяжелый. В браузерах это проявляется точно так же, см. выше.
dom1n1k
18.05.2017 22:15Про вызов функции я в курсе. Но как-то раньше опыт показывал, что в отличие от классических языков, в JS дело с этим обстоит не так однозначно. Что-то внутри JIT такое происходит, что эффект смазывается и разница во многих случаях не получается очень уж огромной.
TheShock
18.05.2017 22:24Предполагаю, что вы где-то прочитали и запомнили некорректную информацию. Я выше показал, что в JS все очень однозначно.
dom1n1k
18.05.2017 22:30+1Не читал, а из собственных тестирований.
Правда, припоминаю, что я частенько их делал в консоли браузеров (как выше), а известный Вячеслав Егоров говорит, что в бенчмарки в консоли это плохо, потому что там оптимизации могут отключаться.TheShock
18.05.2017 23:44-1Ну выше есть и в консоли и в jsperf. И там, и там одинаковый результат. Можете еще на jsfiddle попробовать. Тот же результат будет
dom1n1k
18.05.2017 23:01+6И отвечая на вопрос в конце статьи — нет, for живее всех живых.
Я понимаю, что
выглядит лаконично и красиво. Но меня ломает, что:const kittens = cats.filter(isKitten).map(getName);
а) два прохода по массиву вместо одного (а может и более, если цепочку наращивать);
б) filter возвращает промежуточный массив, то есть лишняя память, лишнее инстанцирование, чаще приход GC;
в) вызов функций отнюдь не бесплатен.
Применяю такой подход только к малым массивам. Видимо, первичное обучение программированию в те времена, когда 640 кб хватало всем, наложило неизгладимый отпечаток на мою психику.
Maiami
19.05.2017 01:19+1оказалось, что в ноде (последняя LTS версия) forEach на порядок медленнее.
forEach хорошо оптимизируется на реальном коде, разницы с for может и не быть, но LTS версия имеет старый оптимизатор, последний турбофан умеет намного больше
Но даже это не столь важно. Проблема микробенчмарков на js в том, что сравнивать нужно в изолированной среде. Вот например типичный микробенчмарк:
Заголовок спойлера'use strict'; const iter = 1000000 const items = [] for (let i = 0; i < iter; i++) { const obj = {} obj.a = 'a' + i obj.num = '1' + i obj.random = Math.random() * 1000 | 0 items.push(obj) } function doSomething(item) { if (item.random % 2 === 0) { result += item.num | 0 } else { str += item.a } } console.time('forEach') var result = 0 var str = '' items.forEach(function (item) { if (item.random % 2 === 0) { result += item.num | 0 } else { str += item.a } }) console.log(`result: ${result}, str len: ${str.length}`) console.timeEnd('forEach') console.time('for') var result = 0 var str = '' for (let i = 0; i < items.length; i++) { const item = items[i] doSomething(item) } console.log(`result: ${result}, str len: ${str.length}`) console.timeEnd('for')
raveclassic
19.05.2017 01:34+1Побольше бы таких дельных замечаний, а то от бенчей с jsperf уже изжога, честное слово.
TheShock
19.05.2017 02:17Две физически разных страницы для фп-стиля и классического, результат — тот же, разница в сто раз, проверьте сами.
dom1n1k
19.05.2017 03:07Про порядок тестов я в курсе — JIT «разогревается» поначалу. Я это учитывал, менял и так и сяк — принципиально расстановка сил не менялась.
Код так сходу сейчас не предоставлю, но могу сказать, что задачка была про вычисление convex hull на большом массиве географических координат (сотни тысяч элементов * многократные проходы).
amaksr
18.05.2017 19:41+3Хайп насчет const сильно преувеличен, так как он не запрещает изменять внутренности объекта. Т.е. не совсем получается и const.
filter и map хороши на циклах с совсем уж простенькой логикой. Если нужно что-то сложное и/или быстрое, то for может быть более к месту.gearbox
18.05.2017 20:14+2Хайп насчет const сильно преувеличен, так как он не запрещает изменять внутренности объекта.
хайп вокруг const не более чем непонимание того что при ссылке на объект константой является ссылка а не объект. При const на скаляр — все ровно так как ожидалось. Но кого это сейчас интересует, 2017 год на дворе как ни как.
Error1024
18.05.2017 21:00+2Далек от js — но то, что у автора получилось в итоге вызывает боль, везде…
maeris
18.05.2017 22:49+1А как быть с yield и await внутри for? Заменять нативный синтаксис на библиотеки с npm?
nullc0de
19.05.2017 12:40Перед тем как делать громкие заявления, советую глубже автору разобраться в вопросе. Где-то синтаксический сахар транслируется в цикл for хочет этого автор или нет, где-то запускается как есть и реализация синтаксического сахара имеет определенный оверхед и работает медленее for, особенно на старых платформах. Советую поиграться с правильными бенчмарками. Если автор не пишет качественный код и не заботится о производительности, то это только его недальновидность. Синтаксический сахар удобен только в некритических участках кода.
lxsmkv
20.05.2017 01:10+3Почитал комментарии. Офигел, как из-за куска кода в 10 строк можно устроить Ледовое Побоище? Понял почему код ревью никогда не проводят командно, а по-одиночке — просто этот бой никогда не закончится :) Программисты все-таки они такие программисты ;)
Это отличный пример того, что все зависит от того под каким углом посмотреть. Нужна производительность — лучше так, нужна удобочитаемость — лучше эдак.
От себя добавлю что если бы мне пришлось тестировать этот кусок кода, то функции проще тестировать чем цикл. Взял фунцкию сунул в нее значения проверил результат. Цикл надо сперва во что-то обернуть, если обертка(метод) без возвратного значения, то ага, уже посложнее будет, возможно даже код рефакторить придется. Так что есть свои преимущества и в том и в этом подходе.
Я просто почитал, порадовался чего нового умеет яваскрипт, чего я еще не видел, подумал, что надо будет попробовать надосуге. Как и практически все комментаторы оригинала. Тут уже на хабре как-то упоминали, мол. наша аудиотория совсем материал по другому воспринимает чем на западе. У нас в принципе любой намек на императив в стиле повествования обречен быть принятым в штыки. (Это замечание переводчику)
Pilat
20.05.2017 03:40+1Когда читаю подобные статьи, всё время думаю: и ведь это те же самые люди ругают Perl :)
Mycolaos
27.05.2017 00:45Ёмаё, эту тему уже более недели обсуждают :D
А не думали делать компиляцию из комментариев в новую статью? Есть же переводы, а будут еще компиляции.
token
«Пишите что вы думаете по этому поводу в комментариях. Умер ли для вас цикл for?» — конечно уже давно умер, мы уже одними точками с запятыми пишем.
justboris
Более того, точки с запятыми тоже писать не надо. Видали?