Переосмысление JavaScript: Смерть for

Автор оригинала: Joel Thoms
  • Перевод
image

Цикл 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 из их объектов. Задание со звездочкой: исследовать композицию функций.


Что насчет break


Многие из вас спрашивали, “Что насчет break”, посмотрите часть вторую серии “Переосмысление JavaScript: break это GOTO циклов”.


Заключение


Пишите что вы думаете по этому поводу в комментариях. Умер ли для вас цикл for?

Для вас это мелочь, но меня очень радует когда кто-то подписывается на меня на медиуме или в твиттере (@joelnet), а если вы считаете мой рассказ бесполезным, то расскажите об этом в комментариях.

Спасибо!
Поделиться публикацией

Похожие публикации

Комментарии 164

    +7
    «Пишите что вы думаете по этому поводу в комментариях. Умер ли для вас цикл for?» — конечно уже давно умер, мы уже одними точками с запятыми пишем.
      –3

      Более того, точки с запятыми тоже писать не надо. Видали?

        0
        Все конечно прекрасно и можно хоронить FOR, если вы не пишете под IE 11…
        +14

        Я сомневаюсь, что внутри несомненно любимого автором функциональненького React найдется так много map/reduce вместо циклов. Проблема в том, что безблагодатные императивные циклы существенно быстрее красивых трансдьюсеров, а, значит, в узких местах будут применяться они.


        break это не goto, он не отправляет тебя в неизвестное произвольное место.

          –2
          В узких местах — возможно, но мы каждый день пишем огромное количество кода, который не является узким местом. Интерфейс, скорее всего, не будет лагать если мы напишем map вместо for.
          Конечно, глубоко внутри все сплошная императивщина, но ее уже написали для нас, давайте этим пользоваться и писать красивый, декларативный код :)
            +8

            Конечно, я тоже люблю красивый код. Просто писать "смерть for" как-то тупо.

            0
            Вот интересно, почему map/reduce медленнее циклов? Ведь любой map можно разложить в эквивалентный ему цикл for, так почему его реализация столь медленна?
            Теоретически можно транслировать map в for на лету, при загрузке модуля, не затратив практически ничего, если вдруг нет возможности изменить реализацию map.
              +1
              Вот интересно, почему map/reduce медленнее циклов?

              Да просто вызов функции это относительно дорого.
              А еще все эти map/reduce/forEach делают кучу дополнительных проверок на каждом шагу, потому что должны работать с разреженными массивами, с массивами, в которых кто-то напихал кастомных свойства и прочими граничными случаями. Я не помню, как называлась либа, но там были на порядок более быстрые реализации всего этого дела — но которые работали только с «нормальными» массивами.


              Ведь любой map можно разложить в эквивалентный ему цикл for
              Теоретически можно транслировать map в for на лету

              Мне кажется, это возможно только если итератор — чистая функция...


              Ну ладно, map мы разложили в for автоматом, а если гений типа автора написал filter().map()? В общем, по хорошему, нужна поддержка частых ФП-паттернов, всяких там трансдьюсеров, и, возможно, у авторов движков дойдут до этого руки, если это станет достаточно частым сценарием.

                –2
                В Ramda map имеет одинаковую с for производительность.
              0
              эти функции в движке написаны на JS, все дело в этом, цикл for, к слову, тоже можно затормозить неожиданными вещами, например в v8 недавно пофиксили let в цикле
                +2
                Вот интересно, почему map/reduce медленнее циклов? Ведь любой map можно разложить в эквивалентный ему цикл for, так почему его реализация столь медленна?

                Всё дело в спецификации, которая не идентична циклу for. Если сделать именно через цикл, как, например, в fast.js или lodash.js, то будет примерно так же быстро (остаются ещё накладные расходы на вызовы функции).


                А спецификация говорит, что map/foreach и аналоги должны пропускать так называемые «дырки». «Дырки» — это такие ключи, которые не инициализированы.¹ Например, new Array(10) не содержит ни одного ключа, и поэтому не вызовет обработчик ни разу. Из-за этого реализация методов в браузерах должна на каждом шаге проверять, а есть ли такой ключ.


                ¹ Если к ним обратиться, то будет undefined, но не стоит путать с тем случаем, когда значение установлено в undefined.

              +11
              1. Плохо не глобальное состояние, а глобальные переменные. Глобальным состоянием можно управлять, держать в одном месте и оно в целом может быть неизменяемым или мало изменяемым. С переменными так не получится.
              2. Причем тут ФП к циклу for? Side эффект от цикла for — это только переменная цикла, которая окажется снаружи.
              3. Первый пример — чем он лучше?

              const isKitten = cat => cat.months < 7
              const getName = cat => cat.name
              const kittens =
               cats.filter(isKitten)
                   .map(getName)

              У вас тут:


              1. Две лишних lambda функции
              2. Вместо одного цикла, у вас их тут два (в зависимости от реализации, но скорее всего).
                И в чем выгода?

              Пропаганда ФП это круто, но ФП далеко не везде и не всегда влазит.


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

                –1
                В ФП for либо не используется, либо используется крайне редко, ведь что угодно можно сделать через reduce.

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

                ФП — инструмент, выбирайте инструмент исходя из задачи. Вопрос применимости конкретных инструментов обсуждался уже тысячу раз :)

                  +2
                  Лишние лямбды кушать не просят, а если вас волнуют лишние циклы используйте трансдьюсеры.

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


                  В Android появилась эта странная мода на retrolambda, которые еще очень как тормозят. Они проигрывают где-то в 20 раз по скорости обработки вызовов.

                    –2
                    Конечно зависит, сильно зависит. Но я, в своем фронтенде, в 99% случаев готов идти на такие компромиссы. Я пока не сталкивался с вещами, где несколько лишних функций сыграли бы хоть какую-то роль, хотя не отрицаю того что такие вещи где-то есть.
                      +4

                      А потом мы получаем сайты, которые поедают все CPU и грузятся по пять минут.
                      Дело не в нескольких лишних функций, дело в подходе.
                      "Несколько" лишних функций в большом проекте превращаются в 500-1000 и это уже заметно везде.

                        –4
                        Остановитесь, сайты жрут все cpu и грузятся долго не потому что ФП, map, filter или reduce, а потому что разработчик раздолбай. Говно код можно писать в любой парадигме и на любом языке.
                        А вот что бы на количестве функций экономить, это я от вас первый раз услышал рекомендацию, спасибо :)
                          +3
                          Остановитесь, сайты жрут все cpu и грузятся долго не потому что ФП, map, filter или reduce, а потому что разработчик раздолбай. Говно код можно писать в любой парадигме и на любом языке.

                          Говнокод обычно заметно, и с ним можно боротся. А вот боротся с выбранным уровнем абстракции нельзя.
                          Каждый дополнительный уровень сверху — это оверхед. ФП такой же оверхед, который может привести к увеличению времени работы от 2 до 100 раз, если вычисления не ленивые, что вполне возможно.

                            0

                            Иногда бывает и наоборот. Допустим, есть какая-то операция со списком. Через некоторое время нужно сделать еще одну почти такую же, но с некоторыми изменениями.


                            В случае цепочки map/filter переиспользовать часть коллбеков намного проще, чем в императивном коде с for, где все написано единым куском кода.

                              +1

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


                              Другое дело, что ФП штуки работают гораздо лучше и проще в случае работы с данными, но, в языках, в которых они для этого нужны.
                              Если у вас работа с данными не основная часть (а для сайта это врядли так), то я сомневаюсь, что ФП покажет себя лучше.

                                0
                                Пример в статье слишком надуманный, чтобы отразить необходимость использования ФП. И пока никто не привел реального примера ФП где он бы подошел. И странно как то выглядит, что сначала обругали jQuery с его $(selector).each(...).otherfunc(...), а потом пришли к .filter(...).map(...)
                            +4
                            ФП сам по себе может быть быстр, очень быстр. Но вот ФП в браузерном JS, особенно когда куча функционала реализована в либах, а не в браузере…
                            Представьте, что у вас на входе 10 000 котят. Миллион котят. Для цикла for нет вообще никакой проблемы вывести не всех, а только текущих видимых, в позициях с 12345-ого по 12435-го.
                            Что там у вас внутри filter/map и как это оптимизировать — а крен его знает.
                              0
                              Array.slice?

                              http://stackoverflow.com/questions/3978492/javascript-fastest-way-to-duplicate-an-array-slice-vs-for-loop

                              Кстати, специально пробовал заменить while на for — в 3 раза медленней.
                                0
                                А если попробовать за-map/reduce-ить котят в вебворкеры? Штук по 10к-100к для миллиона? For-то все-равно быстрей будет, но так для интереса? Хотя, наверное большую часть времени займет доставка котят до воркера и обратно.
                        0
                        Классически замена циклов — не reduce, а рекурсия. И отсутствие TCO в JS этому приему не дает возможности эффективно применяться.
                      +1
                      Я на днях статью увидел где на картинке фп стоит выше чем ооп и так же автор уверял что фп это новая парадигма 90 года. То есть фп пропаганда идет ну очень сильно и если раньше казалось что это откидывает людей назад, то сейчас мне кажется что это даже лучше, ведь как ещё научить людей писать правильный ооп, который по факту впитал фп и сделал его ещё лучше. Любой грамотный ооп-шник скажет что в ооп все тоже самое, но вот люди начали это осваивать только тогда, когда им начали подавать это на чистом подносе-фп. Возможно так им легче.
                      Только мне не понятно почему до сих пор не начали говорить что код в статье это не фп, ведь в фп вообще нет переменных, а то фп где есть переменные, это ооп. В фп не может быть объявлений const, var, или let.
                        –1
                        Код статьи написан в функциональном стиле, т.е. не мутирует переменных и использует чистые функции.
                        Действительно, основополагающие принципы у обеих парадигм одинаковые, но их реализация имхо ортогональна, поэтому говорить что ООП впитал ФП не совсем корректно
                        p.s. А как бы вы переписали код из статьи на JS?
                          0

                          Переменная объявленная как неизменяемая вряд ли переменной остается.


                          Так же лисп функциональный, но переменные в нем все равно есть

                            0
                            Не назвал бы лисп прямо таким функциональным (мы же о Common Lisp?). Да, присваиваний в нормальном коде не увидишь, но и функциями типа map/reduce пользоваться там не слишком удобно.

                            Зато, возвращаясь к теме статьи, в нём самый шикарный цикл for из всех языков, что я видел (точнее конструкция loop и библиотека iter как развитие этой идеи). Там решение задачи типа «найти такой элемент массива A, что функция F от следующего за ним элемента примет максимальное значение» будет выглядеть примерно как сам текст этой задачи. В чистом ФП же тут будет жонглирование zip, map, filter, etc. А в условном C или императивном js будет куча вспомогательных переменных и манипуляций с ними. Оба варианта как-то так себе.
                            0

                            Можно поинтересоваться почему в ФП не может быть const и let?
                            Const в них используются неявно, а let есть ничто иное как псевдоним, для некоторого выражения, наподобии того как в математики вводят дополнительные переменные

                              0
                              а то фп где есть переменные, это ооп
                              в математики вводят дополнительные переменные
                              Видимо, математика — это ооп. =/
                          +3
                          const isKitten = cat => cat.months < 7
                          const getName = cat => cat.name
                          const kittens = cats.filter(isKitten).map(getName)

                          Красиво, но, вероятно, раза в 2 медленнее. Критичные части приходится переписывать в императивщину.

                            +2
                            На самом деле медленнее значительно больше, чем в два раза, но все-равно линейно, потому в большинстве программ, которые пишут на ФП (а на нем все-равно ничего сложного написать нельзя) — это не критично.
                              0
                              В написанном хорошо коде переписать узкие места более эффективно можно в любой момент (особенно заменить мапы и фильтры на циклы). Преждевременная оптимизация — корень всех зол.
                                +6
                                Преждевременная оптимизация — корень всех зол.

                                Эх, как же надоела эта вырванная из контекста цитата.

                                  +2
                                  Прошу прошения. Не подскажете, из какого контекста эта фраза?
                                    +1
                                    There 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

                                      0

                                      Подчеркну, что здесь Кнут говорит не "забейте на заботу о производительности до тех пор, пока не окажется, что у вас всё тормозит" (как это обычно подразумевают цитирующие), а "заботьтесь о производительности там, где это важно и даст наибольший эффект".

                                  0

                                  Поэтому, вместо вызова у списка метода sort вы каждый раз пишите два цикла сортировки вставкой?

                                    0
                                    Нет, я пишу вызов метода sort(), а при необходимости анализирую, нужно ли заменить встроенную сортировку на какой-то конкретный алгоритм, больше подходящий для задачи.
                                      +1

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


                                      Если мы знаем, что у нас каждая такая цепочка порождает еще один цикл и мы получаем переизбыток циклов и не особо то и выигрываем в читаемости — зачем?

                                        0
                                        Ну, на мой вкус, в читаемости мы выигрываем весьма ощутимо.
                                          0

                                          Уже приводили контрпример немного ниже.
                                          Как только названия фильтров начинают превращатся в "isValid" или что-то такое, то все превращается в кашу. Далеко не всегда по фильтру можно понять, что конкретно он делает, а в случае с циклом for + if практически всегда.


                                          А если так не делать, то получается гиганская цепочка фильтров, которую тоже сложно читать.

                                    0

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

                                  +3
                                  Хоронили if, порвали три редакса.

                                  cats.filter(isKitten).map(getName)
                                  


                                  Ну сколько можно то? Еще примитивней пример нельзя привести? Или только для таких основ и годятся все эти тренды? Да, на однострочниках ФП выигрывает. Ну вот на однострочниках его и можно применять. А чем длинеее — тем более трудноподдерживаемым код становится
                                    +7
                                    Да да, For умер, While разложился А Do While вообще не рождался. For имеет свою область применения. И никуда оттуда не денется.
                                      +2
                                      Спокойно, просто хипстера несут «истину» в массы. В перерывах между походами в барбершоп и покатушками на моноколесах.
                                      +4
                                      Вообще проблема .map и прочей функциональности (как по мне конечно) что код исполняется «где то там». И вместо того что бы сходу посмотреть что происходит в цикле, нам нужно прыгать по анонимным методам и искать там… Для однозначных вещей это ок, но когда задачи идут менее тривиальные все становится банально не удобным.
                                        0

                                        А я вот противоположной точки зрения
                                        Глядя на композицию map, filter, reduce можно сразу выделить структуру даже не вникая в детали (банально если заканчивается reduce — значит результат одиночное значение, иначе — массив). С циклом же нужно полностью изучить весь код, чтобы выявить какую-то структуру.

                                          0
                                          А зачем вам структура? Структура описана или классом или интерфейсом. И посмотреть ее можно и так. А вот что там происходит это нужно дебажить… А дебажить чистые циклы проще, лично мне. Но тут разговор не об этом. .filter((cat)=>cat.isBlack ) мне гораздо больше нравится чем for(){}. Но при этом я не рискну утверждать что for умер.
                                            0

                                            Да я ничего и не имею против for :)
                                            На самом деле он вполне себе декларативен и если присмотреться то можно в нем увидеть монвду list в do нотации (особенно в for of)

                                              0
                                              Вот и сошлись :)
                                        +10

                                        Я рад, что автор открыл для себя .map() из ES5, он действительно удобнее for, но это 2011 год. Если он изучил ещё и .reduce(), то смог бы обойтись одним проходом вместо двух.


                                        Но зачем публиковать это сейчас, в 2017, на Хабре? Да ещё с таким ужасным переводом. Есть русский термин «побочный эффект». Он не «применяется», а появляется.


                                        Тем временем в ES2015 появилась разновидность цикла for-of, которая гораздо элегантнее .foreach() со вложенной функцией (когда отдельная функция не нужна). С учётом этого говорить о смерти for может только невежда.


                                        апофеоз перевода
                                        мутабельное состояние (англ. mutable state — изменяемое состояние — прим. переводчика)

                                        Что мешало сразу написать «изменяемое состояние»?

                                          +1
                                          Очень интересно посмотреть на выход из map по break?
                                            +2

                                            Смысл map в том что производится трансформация над каждым элементом (можете считать что это групповая операция) и кол-во элементов на входе и на выходе обязано быть одинаковым

                                              0

                                              По идее, нужно просто фильтр перед map сделать для такого.

                                                +2
                                                А если брейк по результатам вычисления, а не до результатов?
                                                  0

                                                  Сформулируйте задачу детальнее, пожалуйста.
                                                  В случае, если ФП сделано правильно, через ленивые вычисления, то можно сделать фильтр после. Что-то в духе:


                                                  map(тут ваши вычисления).filter(тут ваше условие выхода).any()


                                                  Должно помочь получить нужные данные.


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

                                                    0
                                                    Хорошо, давайте обсудим ваше условие — «нужно найти первый такой элемент в цикле». Ну или «найти максимум 5 элементов, удовлетворяющих результатам поиска».

                                                    В случае, если ФП сделано правильно

                                                    А если ФП у нас JS?
                                                      0
                                                      Да и в случае правильного fp есть ряд задач плохо решаемых редьюсерами. 21 очко на колоде карт?
                                                        0
                                                        21 очко на колоде карт?

                                                        Объясните
                                                          +1
                                                          колода карт, перетасована, надо выбирать карты пока не выпадет 21 очко или больше. Редьюсер пойдет по всему массиву а нужен выход. Сделать фильтр не получится — такой же полный перебор.
                                                            –2
                                                            для этого есть трансдьюсер
                                                              –3
                                                              вы просто новое слово выучили или реально понимаете о чем говорите? Трансдьюсер — это грубо говоря генератор редьюсеров. Какой редьюсер он должен дать что бы решить эту задачу ОПТИМАЛЬНО без выхода? С учетом того что редьюсер не должен выходить, если он выходит — это уже обычный цикл в функциональной обертке. То есть изначально вы не решите ОПТИМАЛЬНО задачу редьюсером если она ОПТИМАЛЬНО решается неполным проходом. Редьюсер по своей природе подразумевает полный проход. Если для решения задачи полный проход не нужен — значит для решения этой задачи не нужен редьюсер.
                                                                –2
                                                                фу как грубо! а не грубо трансдьюсер это мидлвар который может выходить когда нужно и никакого полного прохода не требуется.
                                                                  –3
                                                                  >это мидлвар который может выходить когда нужно и никакого полного прохода не требуется.

                                                                  В этом момент мы прекращаем назвать его или результат его работы редьюсером и не нарываемся на грубости на хабре. Если вам нужен фолдинг — делаем редьюсер и делаем полный проход. Если полный проход не нужен — делаем рекурсию или итерацию но не называем это фолдингом. Либо называем фолдингом  но тогда меняем структуру и берем не массив а список, в котором после получения результата не применяем редьюсер к остатку. На массиве редьюсер подразумевает полный проход, изначально мы говорили за массив. Для преобразования его в нужную структуру нужно применить логику в которую и перекочует логика выхода (трансформированная но тем не менее) которую вы пытаетесь запихнуть в редьюсер.
                                                                    –3
                                                                    Грубости они не от хабра, а от воспитания. Вас так учили, нас по другому. Я про редьюсер вам вообще ничего не говорил.
                                                                      –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);
                                                                      Все, теперь можно делать фолдинг на ком угодно, это нормально оптимизится.
                                                                        –4
                                                                        в немутабельном мире наверное, а так это всего лишь способ сделать свертку на полугруппе.
                                                                          –3
                                                                          >в немутабельном мире наверное

                                                                          ? У вас мир меняется пока вы его сворачиваете? ) (это риторический вопрос)
                                                                  –3
                                                                  вот пример из рамды http://ramdajs.com/docs/#transduce take(2) выходит после 2х элементов.
                                                                    –3
                                                                    А пример из Рамбды, когда выходит после N элементов, ума которых больше 20?
                                                                      –2
                                                                      Паша, зачем тебе пример из Рамды? Я думаю ты сам можешь написать функцию, которая вместо проверки длинны массива результатов выходит при другом условии.
                                                                        –2
                                                                        Та я ж тут чисто достаю всех, а сам даже FizzBuzz не напишу)

                                                                        Ты предлагаешь такую функцию написать в императивном стиле с умершим for?
                                                                          –2
                                                                          Да, ты тот еще… кодер ))

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

                                                                          Статьи эти смешные конечно, они на самом деле не о функ программировании а как-то вообще не о чем. Функ программирование по моему это скорее про структуры данных, тут ни слова о функторах и фолдебл, зато много пафоса про фор мертв.

                                                                          Из реальных примеров все что генерируется компилятором удобнее писать в функ стиле так как оно тупо менее многословно и подчиняется простым правилам. А вот реализация этого функ стиля уже делается этими самыми мертвыми фор =)
                                                            0
                                                            В правильном fp функция reduce умеет проверять у себя флаг reduced и выходить когда ей потребуется.
                                                              +1
                                                              Покажите, как вы из reduce выходите. И объясните, что значит выйти из reduce, если идеологически reduce подразумевает проход по всем элементам?
                                                                –2

                                                                Да вариантов много, зависит от взглядов автора библиотеки. К примеру в lodash есть transform. Это такой мутабельный reduce, которые помимо мутабельности итогового значения умеет выходить из цикла за счёт return false.

                                                                  +2
                                                                  Но, тем не менее, это ж не reduce, я к этому подводил :)
                                                                  0
                                                                  Откуда информация что reduce должен проходить обязательно по всем элементам коллекции?
                                                                  Вот map — да, подразумевает обход всех элементов.
                                                                  reduced в clojureScript, по большому счету — дополнительный внутренний флаг isReduced по которому происходит выход из reduce.
                                                                    0
                                                                    Ну мы же на JS пишем. Плюс, reduced в кложе выглядит как in-place оптимизация, в других языках я такого не припомню (вы можете меня поправить).
                                                                    Из MapReduceconsists of folding all available b:B to a single value.
                                                                    Ну и reduce иначе зовется foldat each node
                                                                  –1

                                                                  Как только вы вводите флаг reduced, ваше fp перестает быть правильным и становится пародией на императивное программирование.

                                                                +2
                                                                А если ФП у нас JS?

                                                                Я думаю, вам сюда. Как я понял, ленивые вычисления в фп называются трансдьюсеры и точно что-то такое там есть. К сожалению, я знаком с такой штукой для Java, которую они добавили в восьмой версии.


                                                                В случае, если использовать их синтаксис, задачи будут выглядит так:


                                                                stream.filter(x => x.state == "ok").findAny()

                                                                Второе будет выглядить так:


                                                                stream.filter(x => x.state == "ok").limit(5)
                                                                  –1
                                                                  Симпатично.

                                                                  К сожалению, я знаком с такой штукой для Java, которую они добавили в восьмой версии.

                                                                  Почему «К сожалению»?
                                                                    0

                                                                    Это относить к предыдущей фразе, в которой я говорил, что не знаю такие штуки для JavaScript)

                                                                  0

                                                                  А как выглядел бы трансдьюсер limitBySum?

                                                              +1
                                                              0
                                                              т.е. в for фильтр был не нужен, теперь в статье предлагается похоронить for и использовать вместо него map, но вот загвоздка — как жить без фильтра, которого в for не было?
                                                              Думаю, что вполне можно было бы ограничиться сведениями, в которых map хорош и удобнее, чем for, но в задачах определённого класса, в которых конструкция for достаточно громоздка.
                                                              Мне вот не очень понятно, зачем вводить map в стандарт, когда эту функцию можно написать в прототип массива и без всяких стандартов и выглядеть она будет точно так же?
                                                              0
                                                              У автора оригинальной статьи есть продолжение — Rethinking JavaScript: Replace break by going functional
                                                                +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
                                                                }
                                                                
                                                                  +2

                                                                  Боже ж ты мой. Главное чтобы это потом не приснилось. Второй кусок кода предельно очевиден: отфильтровать из list только то, что проходит predicate, но не более limit элементов. А первое?


                                                                  • Рекурсия… Представим что limit около 10'000… и всё это в стек. Кошмар
                                                                  • [...newList, list[i]] в рамках рекурсии для аккумулируемого значения. Какая там асимптотика будет у такого решения? O(n^2)?
                                                                  • Даёшь тернарный оператор внутри тернарного оператора, чтобы ты мог написать терна...

                                                                  Я вот не могу понять. Это издержки фанатизма? Или человек действительно не понимаешь, что за дичь он пишет? А ведь потом это ещё и переводят, т.е. это "популярно" и "востребовано".

                                                                    0

                                                                    Именно рекурсия не проблема, т.к. соптимизируется.
                                                                    Но это, конечно, не отменяет превышение нормы СЭС по показателю hipsterity/line)

                                                                      –2
                                                                      const 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
                                                                        +1

                                                                        В ES2015 ввели правильную хвостовую рекурсию.
                                                                        Только что проверил на массиве из 10000 элементов.
                                                                        node 7.7.2

                                                                          –2

                                                                          Хм, странно, но я запустил тот же самый код на v7.7.4 и снова получил RangeError: Maximum call stack size exceeded. Может быть я неправильно понимаю суть хвостовой рекурсии?

                                                                            +2

                                                                            Попробуйте с --harmony.
                                                                            Если верить node.green, то должно оптимизироваться, если флаг стоит.

                                                                              0

                                                                              Попробовал c --harmony — у меня снова Maximum call stack size exceeded. Код запускал тот же, что и на прошлом скриншоте. Перешёл на node.green и вытащи оттуда тест. Запустил в Chrome — падает с переполнением стека. Запустил в node ― работает. Т.е. оптимизация и правда есть. Почему она не может пережевать код Joel Thoms не знаю. Надо будет поковыряться.

                                                                                0

                                                                                Вероятно, дело в тернарном операторе. Из-за того, что вложенный вызов является частью выражения, оптимизатор не видит возможности применить TCO.

                                                                                  0

                                                                                  Интересная статья про tail call optiomization в JS.

                                                                    0
                                                                    Но в этом продолжении нет выхода из map в любой момент времени. Я легко выдумываю условие выхода — если текущее время больше 14:00:00 (часы: мин: сек), то выйти из цикла. Все фильтры летят в мусорную корзину, потому что если в работе обход массива занимает 10 сек и я запущу фильтр в 13:59:45, то в конечный массив попадут все элементы массива. Затем я запускаю перебор в 13:59:59 и получаю полный перебор массива до 14:00:09, хотя он должен был остановиться в 14:00:00 по break.
                                                                      0
                                                                      нет выхода из map в любой момент времени
                                                                      А это идеологически неверно. Map — проекция всех элементов, и выхода из нее нет. Если нужен выход, значит не нужен map.
                                                                        +1
                                                                        Бинго. Я же раньше и написал, что map решает свои задачи, которые только частично перекрываются с for. Поэтому вроде как смерть for откладывается? )))
                                                                          0
                                                                          Упс, только сейчас заметил
                                                                  +1
                                                                  Пост — отличная лакмусовая бумажка!
                                                                  Очень показательно, спасибо.
                                                                    +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-of
                                                                      –2
                                                                      Код существует не в вакууме обычно, поломка такого вида выявляется юнит-тестом сразу же (из isKitten() возвращается уже не boolean). Если его нет — существуют Immutable структуры данных (одно решение тянет за собой цепочку других).
                                                                      0
                                                                      Вот вы мне скажите: const подразумевает собой константу, а в итоге оказывается функцией, в которую еще и что то передать надо. Это по вашему чистый код который говорит сам за себя?

                                                                      Если я хочу число пи, то ИМХО должно быть const pi = 3.14; и все это константа!

                                                                      Может я чего то не понимаю?
                                                                        +1
                                                                        В JS функции объекты первого порядка. Т.е. грубо говоря такие же как int или string в других языках. Так что const function вполне себе ничего.
                                                                          +2
                                                                          Ну ок, с этим более менее согласен.

                                                                          Попробую побухтеть про читабельность.

                                                                          Вариант с циклом: пройтись по всем котам, если возраст кота меньше заданного (вот тут бы константу) — запомнить имя кота.

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

                                                                          Чот как то много бегать придется, и выше справедливо заметили про сложность прерывания данного процесса.

                                                                          Но это я так бухчу ;-)
                                                                            +2
                                                                            Почему «бухчу»? Абсолютно справделивые замечания. Поэтому вся функциональная парадигма должна быть использована в правильном месте и будет все прекрасно.
                                                                            Но нельзя не отдать должного что она приучает к Single Responsibility в функциях, что, как по мне, просто прекрасно.
                                                                              +1
                                                                              Вот прям полностью с вами согласен :-)
                                                                              0
                                                                              Вся прелесть в том, что при правильном именовании функций и переменных вам не нужно идти и изучать детали реализации. Вот же, прямо в коде написано, отфильтруй по признаку isKitten, а затем получи имя.
                                                                              Это скорее непривычно поначалу, записи типа
                                                                              const sum = a => b => a + b
                                                                              console.log(sum(2)(3))
                                                                              

                                                                              мне первое время давались с трудом, а теперь жить без них не могу :)
                                                                                +3
                                                                                Но тут внезапно всплывает известная проблема — как назвать эту чертову функцию? :-)
                                                                                  +2
                                                                                  Это только вначале, когда у вас примеры уровня isKitten. А потом вы делаете
                                                                                  var validUsers = users.map(isValid);
                                                                                  


                                                                                  Но isValid, оказывается, проверяет user.name !== 'Vasek', а не user.access.contains(ADMIN). Но вы слишком горды, чтобы пользоваться типизацией, вы ведь модны и молодежны, а типизация — для старперов и теперь только Васек и не имеет доступ к вашей админ-панели.

                                                                                  А отвратительные названия — это одно из проклятий ФП в ЖС. Ведь canUserAccessAdminPanel пишут только джависты, по-молодежному надо написать is_ok.

                                                                                  Вот некоторые названия функций из модных и молодежных библиотек:
                                                                                  - pipe
                                                                                  - it
                                                                                  - connect
                                                                                  - put
                                                                                  - dispatch


                                                                                  Да-да, это именно те «емкие и понятные» названия, в которые не нужно заходить, чтобы понять, что они делают. А ведь это библиотечные. А еще сколько будет локальных для приложения.
                                                                            +7

                                                                            Как использовать map/forEach/filter вместе с async/await?


                                                                            for (const foo of bar) {
                                                                               const result = await doSomethins(foo);
                                                                            }
                                                                              –2
                                                                              return bar.reduce((r, v) => r.then(doSomething.bind(null, v)), Promise.resolve());
                                                                              


                                                                              PS после транспайлинга это будет выглядеть (и работать) лучше, чем async/await. Прямая противоположность ситуации с for :)
                                                                                0
                                                                                Пример для последовательного выполнения. Напоминаю, что в JS async/await — всего лишь сахар поверх промисов. И да, если кто не в курсе, генераторы (через который выражается async функция в Babel) порождают очень странный код в конечном ES5.
                                                                                  0

                                                                                  Нет, это будет требовать больше памяти, чем async/await из-за того, что вы заранее строите длинную "сосиску" из продолжений — так что async/await все же лучше.


                                                                                  Но при отсутствии возможности вставить async/await или библиотеку прямо сейчас так действительно можно делать.

                                                                                  0
                                                                                  Примерно так:
                                                                                  const result = await Promise.all(bar.map(doSomething)
                                                                                  

                                                                                  В качестве бонуса — параллельное выполнение. Если нужно именно последовательное — пригодится reduce
                                                                                    0
                                                                                    const results = await Promise.all(bar.map(doSomething))
                                                                                    
                                                                                    +3

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

                                                                                      –4
                                                                                      По большому счету, в js map — это сахар на while, посмотрел здесь: map Polyfill.
                                                                                      Код в примере использует map и filter, вместо одного for, вероятно, что он работает в 2 раза медленнее.
                                                                                        +1
                                                                                        С вызовом функции, которая значительно более дорогостоящая операция. Два цикла — это еще фигня в сравнии с вызовом функции на каждую операцию, потому там не в 2 раза медленнее, а на порядок.
                                                                                          +8
                                                                                          Да, вы правы, кто меня минусует. Я ошибся с порядком. Не на порядок, а на два порядка, то есть в сто раз. Функциональный вариант ровно в сто раз медленнее, чем классический:

                                                                                            –4

                                                                                            Не стоит серьёзно относится к подобным тестам на jsperf. jsperf не чурается модифицировать предоставленный код, изменяя его порой, весьма существенно. Бенчмарки сами по себе редко бывают объективны, но если уж хочется с ними поиграть, то лучше запускать их за пределами таких площадок (скажем в node или просто используя профилирование/console.time браузера).

                                                                                              +2
                                                                                              Как скажете. Получилось… разница в 100 раз. Увеличил число, чтобы было видно, что количество цифр одинаковое и уж слишком мало получалось в классическом варианте:
                                                                                                0
                                                                                                Ну так вы ж лукавите :) Сколько у вас в fp прогонов, а сколько в classic? Не, я не спорю, все верно, map/reduce и компания банально увеличивают количество прогонов. Но можно изловчиться и написать на трансдьюсерах. Другое дело, зачем?..
                                                                                                  +2
                                                                                                  Простите, в fp-варианте количество прогонов в три раза больше, чем в классическом варианте, а не в сто раз.

                                                                                                  Я лишь стараюсь убедить, что утверждение ложно:
                                                                                                  Код в примере использует map и filter, вместо одного for, вероятно, что он работает в 2 раза медленнее.

                                                                                                  Основной источник увеличения нагрузки не три цикла for вместо одного (это как раз совершенно не страшно), а «три тяжеловесных вызова функции + три операции» вместо «три операции» на каждую итерацию. То есть у нас сам вызов функции занимает в сто раз больше времени, чем операция, которую эта функция выполняет.

                                                                                                  И это особенность именно JS, а не FP в целом.
                                                                                                    0

                                                                                                    Отдельно стоит заметить, что если даже написать свои собственные map и filter, они зачастую будут работать быстрее. Потому что встроенные методы реализованы на сишке, и вызываются через FFI, с соответствующими затратами на маршаллинг аргументов и возвращаемого значения.

                                                                                                      0
                                                                                                      Где я мог облажаться?
                                                                                                      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
                                                                                                      */
                                                                                                      
                                                                                                        –1
                                                                                                        Возможно, я ошибся с первопричинной и основная нагрузка — создание и наполнение новых массивов на каждой итерации, а не вызов функции?
                                                                                                0
                                                                                                В ramda и в lodash очень наивная реализация. Думаю честно будет добавить в тест Rx.JS реализацию для сравнения. Как никак еще одна хипстерская технология
                                                                                                  0
                                                                                                  Это нативный код без фреймворков. Но если вы хотите — можете протестировать и фреймворки)
                                                                                          +3
                                                                                          Хочу добавить, что с async / await такие циклы работать не будут. Возможно не к месту мое замечание. Но когда переходили на async / await я был удивлен, что это перестает работать для forEach (в доке потом выяснил что к чему). Так что старый добрый цикл выручил.
                                                                                            +3

                                                                                            Какой-то оголтелый фанатизм. И с каждым днём таких статей всё больше. Главное заголовок сделать как можно более пафосным (смерть for). В следующий раз накал надо ещё пуще нагнать. Как насчёт хтонических исчадий из преисподней?


                                                                                            Фунциональное программирование интересная и полезная штука, если ваш язык позволяет из него взять что-то полезное. Императивное программирование напротив тоже очень интересная и полезная штука. Мир не делится на белое и чёрное. И задачи можно решать выбирая наиболее удобные и привычные для себя инструменты. Выбирая один набор преимуществ, вы неизбежно приобретаете к нему такой же набор недостатков.


                                                                                            Скажем мутабельность и иммутабельность. И то и другое может быть весьма удобным и к месту. Важно только понимать, где чего стоит избегать (умоляю — избегайте избыточных аллокаций в .reduce).


                                                                                            Вот скажем мои 5 копеек по сабжу:


                                                                                            • map & reduce в качестве замены for удобная штука, т.к. код получается несколько нагляднее, ибо синтаксис конструкции for не позволяет красиво возвращать новый объект как результат операций над итерациями
                                                                                            • forEach вместо for-of по мне так, на мой вкус, выглядит очень… неопрятно, требует лишнего метода, фигурных скобок. В случае одиночного пробега по какой-либо коллекции я прибегаю к for-of как раз в рамках читаемости. Особенно это удобно в случае 2-3 вложенных for.
                                                                                            • цепочки трансформаций из ФП — очень удобная и полезная штука. Однако мы тут сталкиваемся с двумя весьма пренепреятнейшими проблемами:
                                                                                              • в JS нет pipe-оператора. В итоге если цепочка какой-либо метод не умеет, то приходится либо оборачивать её всю целиком (читаемость катастрофически падает), или делить цепочку на несколько, добавляя какие-нибудь вветвления, либо делать какие-нибудь .tap методы. Тоже касается и композиции методов, без pipe оператора выглядит это ну прямо безобразно.
                                                                                              • в JS из коробки нет никаких удобств для работы с callback-ами в async & * контекстах, а это бывает ну очень актуально
                                                                                            • ФП подразумевает очень хорошее понимание работы и методов своей ФП библиотеки. Для нового человека ваш код может напоминать набор слов. А если учесть что ряд из либ ещё постоянно меняет свои сигнатуры и названия методов (камень в сторону lodash)...

                                                                                            В своём коде я не чураюсь использовать любые известные мне подходы, исходя из задач. Не понимаю я этого фанатизма.

                                                                                              0

                                                                                              А если ещё на jsperf сравнить for и map, то станет понятно, почему в каждом angular и react внутри только for.

                                                                                              +1
                                                                                              Недавно сравнивал for и forEach на реальной задаче — оказалось, что в ноде (последняя LTS версия) forEach на порядок медленнее. Я без понятия откуда такая огромная разница, в браузерах обычно отличия невелики.
                                                                                                0
                                                                                                Потому что кроме самой операции (каждая итерации очень дешевая) допольнительно происходит вызов функции, который очень тяжелый. В браузерах это проявляется точно так же, см. выше.
                                                                                                  0
                                                                                                  Про вызов функции я в курсе. Но как-то раньше опыт показывал, что в отличие от классических языков, в JS дело с этим обстоит не так однозначно. Что-то внутри JIT такое происходит, что эффект смазывается и разница во многих случаях не получается очень уж огромной.
                                                                                                    0
                                                                                                    Предполагаю, что вы где-то прочитали и запомнили некорректную информацию. Я выше показал, что в JS все очень однозначно.
                                                                                                      +1
                                                                                                      Не читал, а из собственных тестирований.
                                                                                                      Правда, припоминаю, что я частенько их делал в консоли браузеров (как выше), а известный Вячеслав Егоров говорит, что в бенчмарки в консоли это плохо, потому что там оптимизации могут отключаться.
                                                                                                        –1
                                                                                                        Ну выше есть и в консоли и в jsperf. И там, и там одинаковый результат. Можете еще на jsfiddle попробовать. Тот же результат будет
                                                                                                  +6
                                                                                                  И отвечая на вопрос в конце статьи — нет, for живее всех живых.
                                                                                                  Я понимаю, что
                                                                                                  const kittens = cats.filter(isKitten).map(getName);
                                                                                                  
                                                                                                  выглядит лаконично и красиво. Но меня ломает, что:
                                                                                                  а) два прохода по массиву вместо одного (а может и более, если цепочку наращивать);
                                                                                                  б) filter возвращает промежуточный массив, то есть лишняя память, лишнее инстанцирование, чаще приход GC;
                                                                                                  в) вызов функций отнюдь не бесплатен.
                                                                                                  Применяю такой подход только к малым массивам. Видимо, первичное обучение программированию в те времена, когда 640 кб хватало всем, наложило неизгладимый отпечаток на мою психику.
                                                                                                    +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')
                                                                                                    


                                                                                                    Результат такой:
                                                                                                    >node fortest.js
                                                                                                    
                                                                                                    forEach: 154.271ms
                                                                                                    for: 42.073ms
                                                                                                    

                                                                                                    Вроде бы for обогнал forEach в 3 раза. Но достаточно поменять местами for и forEach, сделать чтобы forEach по коду был ниже, как результат становится ровно противоположным:

                                                                                                    >node fortest.js
                                                                                                    for: 153.958ms
                                                                                                    forEach: 51.414ms
                                                                                                    


                                                                                                    То есть уже становится понятно, что в лоб тестирование не провести. Можно ухищряться по разному, но самый простой способ это изолировать 2 куска кода друг от друга. То есть удалить код относящийся к for и запустить тест отдельно для forEach, потом перезапустить node (если код из консоли запускали, а не из файла), удалить код forEach и отдельно запустить уже for. Результат будет такой:

                                                                                                    >node fortest.js
                                                                                                    for: 153.152ms
                                                                                                    

                                                                                                    >node fortest.js
                                                                                                    forEach: 146.153ms
                                                                                                    


                                                                                                    То есть если вы хотите протестировать 2 куска кода, лучше сразу брать benchmark.js, он хоть и не идеален, но хотя бы решает вопрос с изолированным запуском:

                                                                                                    Пример с benchmark.js
                                                                                                    'use strict';
                                                                                                    const Benchmark = require('benchmark')
                                                                                                    const suite = new Benchmark.Suite
                                                                                                    
                                                                                                    const iter = 1000000
                                                                                                    const items = []
                                                                                                    for (let i = 0; i < iter; i++) {
                                                                                                      items.push('abc' + i)
                                                                                                    }
                                                                                                    
                                                                                                    let result1 = ''
                                                                                                    let result2 = ''
                                                                                                    let result3 = ''
                                                                                                    
                                                                                                    function doSome(item) {
                                                                                                      if (item[3] % 2 === 0) {
                                                                                                        return item
                                                                                                      }
                                                                                                      return ''
                                                                                                    }
                                                                                                    
                                                                                                    suite
                                                                                                      .add('For', function () {
                                                                                                        result2 = ''
                                                                                                        for (let i = 0; i < items.length; i++) {
                                                                                                          const item = items[i]
                                                                                                          if (item[3] % 2 === 0) {
                                                                                                            result2 += item
                                                                                                          }
                                                                                                        }
                                                                                                      })
                                                                                                      .add('For with function call', function () {
                                                                                                        result3 = ''
                                                                                                        for (let i = 0; i < items.length; i++) {
                                                                                                          result3 += doSome(items[i])
                                                                                                        }
                                                                                                      })
                                                                                                      .add('forEach', function () {
                                                                                                        result1 = ''
                                                                                                        items.forEach(function (item) {
                                                                                                          if (item[3] % 2 === 0) {
                                                                                                            result1 += item
                                                                                                          }
                                                                                                        })
                                                                                                      })
                                                                                                    
                                                                                                      .on('cycle', function (event) {
                                                                                                        console.log(String(event.target))
                                                                                                      })
                                                                                                      .on('complete', function () {
                                                                                                        console.log('Fastest is ' + this.filter('fastest').map('name'))
                                                                                                    
                                                                                                        console.log(result1.length)
                                                                                                        console.log(result2.length)
                                                                                                        console.log(result3.length)
                                                                                                      })
                                                                                                      .run({ 'async': true })
                                                                                                    


                                                                                                    Результат:
                                                                                                    >node test.js
                                                                                                    For x 15.70 ops/sec ±3.85% (40 runs sampled)
                                                                                                    For with function call x 14.72 ops/sec ±3.52% (38 runs sampled)
                                                                                                    forEach x 15.55 ops/sec ±9.37% (30 runs sampled)
                                                                                                    Fastest is For, forEach
                                                                                                    

                                                                                                    Жалко нет реального куска кода на котором вы тестировали, чтобы посмотреть что с ним такое)
                                                                                                      +1
                                                                                                      Побольше бы таких дельных замечаний, а то от бенчей с jsperf уже изжога, честное слово.
                                                                                                        0
                                                                                                        Две физически разных страницы для фп-стиля и классического, результат — тот же, разница в сто раз, проверьте сами.

                                                                                                          0
                                                                                                          Про порядок тестов я в курсе — JIT «разогревается» поначалу. Я это учитывал, менял и так и сяк — принципиально расстановка сил не менялась.
                                                                                                          Код так сходу сейчас не предоставлю, но могу сказать, что задачка была про вычисление convex hull на большом массиве географических координат (сотни тысяч элементов * многократные проходы).
                                                                                                        +3
                                                                                                        Хайп насчет const сильно преувеличен, так как он не запрещает изменять внутренности объекта. Т.е. не совсем получается и const.

                                                                                                        filter и map хороши на циклах с совсем уж простенькой логикой. Если нужно что-то сложное и/или быстрое, то for может быть более к месту.
                                                                                                          +2
                                                                                                          Хайп насчет const сильно преувеличен, так как он не запрещает изменять внутренности объекта.

                                                                                                          хайп вокруг const не более чем непонимание того что при ссылке на объект константой является ссылка а не объект. При const на скаляр — все ровно так как ожидалось. Но кого это сейчас интересует, 2017 год на дворе как ни как.
                                                                                                          +2
                                                                                                          Далек от js — но то, что у автора получилось в итоге вызывает боль, везде…
                                                                                                            +1

                                                                                                            А как быть с yield и await внутри for? Заменять нативный синтаксис на библиотеки с npm?

                                                                                                              –3
                                                                                                              Javascript — это куча компоста, куда каждый кидает вонючие объедки. Давно пора перевести браузеры на Lua.
                                                                                                                0
                                                                                                                Давно пора перевести браузеры на Lua.

                                                                                                                Это полумера ;)
                                                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                  0

                                                                                                                  но map все равно не станет быстрее for.
                                                                                                                  Максимум что близко приблизится.

                                                                                                                  0
                                                                                                                  Перед тем как делать громкие заявления, советую глубже автору разобраться в вопросе. Где-то синтаксический сахар транслируется в цикл for хочет этого автор или нет, где-то запускается как есть и реализация синтаксического сахара имеет определенный оверхед и работает медленее for, особенно на старых платформах. Советую поиграться с правильными бенчмарками. Если автор не пишет качественный код и не заботится о производительности, то это только его недальновидность. Синтаксический сахар удобен только в некритических участках кода.
                                                                                                                    +3
                                                                                                                    Почитал комментарии. Офигел, как из-за куска кода в 10 строк можно устроить Ледовое Побоище? Понял почему код ревью никогда не проводят командно, а по-одиночке — просто этот бой никогда не закончится :) Программисты все-таки они такие программисты ;)
                                                                                                                    Это отличный пример того, что все зависит от того под каким углом посмотреть. Нужна производительность — лучше так, нужна удобочитаемость — лучше эдак.

                                                                                                                    От себя добавлю что если бы мне пришлось тестировать этот кусок кода, то функции проще тестировать чем цикл. Взял фунцкию сунул в нее значения проверил результат. Цикл надо сперва во что-то обернуть, если обертка(метод) без возвратного значения, то ага, уже посложнее будет, возможно даже код рефакторить придется. Так что есть свои преимущества и в том и в этом подходе.

                                                                                                                    Я просто почитал, порадовался чего нового умеет яваскрипт, чего я еще не видел, подумал, что надо будет попробовать надосуге. Как и практически все комментаторы оригинала. Тут уже на хабре как-то упоминали, мол. наша аудиотория совсем материал по другому воспринимает чем на западе. У нас в принципе любой намек на императив в стиле повествования обречен быть принятым в штыки. (Это замечание переводчику)
                                                                                                                      +1

                                                                                                                      Когда читаю подобные статьи, всё время думаю: и ведь это те же самые люди ругают Perl :)

                                                                                                                        0
                                                                                                                        Ёмаё, эту тему уже более недели обсуждают :D

                                                                                                                        А не думали делать компиляцию из комментариев в новую статью? Есть же переводы, а будут еще компиляции.

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

                                                                                                                        Самое читаемое