Разработчик
0,0
рейтинг
9 мая 2014 в 23:57

Разработка → Функция reduce tutorial

JavaScript в последние годы набрал нешуточную популярность, в связи с чем его подводные камни также стали явственно видны. Справедливости ради, стоит отметить, что любой язык в некоторой мере имеет как своё legacy, так и подводные камни.
Конкретно JavaScript обладает целым огородом камней. Подводным огородом.

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

Меня заинтересовал вопрос номер 3:
Каков результат этого выражения (или нескольких)?

[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]

В качестве ответа авторами, на выбор, даются следующие варианты:
* ошибка
* [9, 0]
* [9, NaN]
* [9, undefined]

Попробуйте и вы, без запуска интерпретатора, пользуясь только своим умом ответить на этот вопрос.

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

А в этой статье вы найдёте:
* Разбор задачки.
* JavaScript reduce с чисто практической точки зрения.
* Несколько акробатических этюдов с reduce (reduce с академической точки зрения).
* Репозиторий с плюшками к статье.
* Несколько других reduce.

Разбор задачки


Чтож, для начала разберёмся с задачей в начале статьи. А вариантов здесь хватает.
reduce (здесь и далее имеется ввиду Array.prototype.reduce), вместе с другими функциями из прототипа Array: filter, map, forEach, some, every, является функцией высшего порядка, то есть она принимает на вход другую функцию (будем называть эту передаваемую функцию f*). Функция f* будет вызвана с некоторыми агрументами для каждого элемента коллекции.

Конкретно reduce, используется для генерации некоторого агрегирующего значения на основе коллекции. Она последовательно применяет f* к каждому элементу, передавая ей текущее значение переменной, в которой накапливается результат (аккумулятора) и текущий обрабатываемый элемент. Также, в reduce можно передать начальное значение аккумулятора. Причём, (!) поведение reduce будет различаться в зависимости от того, передано это значение или нет.

Функция Math.pow производит возведение в степень, то есть её поведение различается в зависимости от переданной степени: это может быть квадрат, куб, или квадратный корень или любая другая вещественная степень.

При этом остаются открытыми следующие вопросы:
* Как ведёт себя reduce, если вызвать её на пустом массиве?
* Как ведёт себя Math.pow, если недодать ей степень?

Для стандартных функций JS нет общей политики обработки ошибок. Некоторые функции могут действовать строго: бросать исключения, если что-то не так в переданных данных, некоторые будут возвращать всяческие пустые значения: null, undefined, NaN, а прочие будут работать пермиссивно: попытаются что-то сделать даже с не совсем корректными данными.

Как много вопросов затронул всего один пример.

А теперь правильный ответ: мы получим TypeError, в котором виновато второе подвыражение. Функция reduce на пустом массиве И без переданного начального значения бросает TypeError.

Почему так? Вчитываемся в спецификацию reduce


Чтож, давайте почитаем что пишет MDN o Array.prototype.reduce. Выясняются следующие тонкости работы функции:
Если initialValue передано, то на первой итерации функция будет вызвана с этим значением и значением первого элемента массива. Если же, initialValue не передано, то функция будет вызвана со значениями первого и второго элементов массива. Отсюда также следует, что если начальное значение не передано, то функция вызывается на один раз меньше, иначе ровно столько раз, сколько элементов в массиве.

Можно представлять форму с initialValue вот так:

array.reduce(fn, initialValue) ⇔ [ initialValue ].concat(array).reduce(fn);

Вторым интересным аспектом является обработка пустого массива. Если массив пустой, и передано начальное значение, то оно является результатом работы функции, а результат f* игнорируется. Если же массив пуст, а начальное значение не передано, то выбрасывается TypeError.

[].reduce(fn, initialValue) ⇔ [ initialValue ].reduce(fn) ⇒ initialValue;
[].reduce(fn) ⇒ TypeError;

На самом деле поведение функции достаточно логично: она пытается вызвать f* со значениями из входных данных. Если начальное значение передано, то оно является элементом данных идущим перед первым элементом. Если не передано ничего (нет элементов и начального значения), то функция не имеет данных для генерации агрегата, и она выбрасывает исключение. Так или иначе, поведение немножко сложное и может стать подводным камнем. reduce, по сути, перегружается для одного агрумента и для двух, и перегруженные варианты имеют разное поведение на пустом массиве.

Теперь можно понять, почему задачка имеет такой результат, а именно, второе подвыражение бросает исключение: оно вызывается с пустым входным списком и без стартового значения. Но! Первое подвыражение всё-таки вычислилось. Предлагаю в качестве упражнения попытаться разобраться в этом вычислении. Можно пойти двумя путями:
* Джедайский: исполнить код в уме, зная о том как работают reduce и Math.pow.
* Ковбойский: вбить в REPL этот код и попытаться подвести рассуждения под результат.

Также, можно ознакомиться с моим примером, который должен помочь понять задачку:
StreetStrider/habrahabr-javascript-reduce:tests/puzzler.js. Он является jasmine-тестом.

Магия и шарм reduce


reduce примечателен тем, что он может быть использован для того, чтобы описать все остальные функции высшего порядка объекта Array: forEach, filter, map, some, every.

Это станет понятным, если избавиться от мысли, что reduce обязан аккумулировать значение того же типа, что и значения в массиве. Действительно, логичным кажется мыслить, что если мы берём массив чисел и суммируем их, то получаем также число. Если мы берём массив строк и конкатенируем их, то также получаем строку. Это естественно, но reduce также способен возвращать массивы и объекты. Причём передача будет происходить из итерации в итерацию благодаря аккумулятору. Это позволяет строить на reduce функции любой сложности.

Для примера, давайте построим map:

function map$viaReduce (array, fn)
{
	return array.reduce(function (memo, item, index, array)
	{
		return memo.concat([ fn(item, index, array) ]);
	}, []);
};

Здесь через аккумулятор передаётся накапливающийся массив. Он будет того же размера, что и исходный, но со значениями, пропущенными через функцию-трасформатор fn. Также здесь не забыто, то fn принимает не только элемент, но индекс и массив последующими параметрами. Параметр функции concat обёрнут в массив, чтобы избежать «развёртки» значения, если fn вернёт массив. В качестве начального значения передан пустой массив.

Этот код есть в репозитории, а ещё для него есть тесты.

Тем, кто заинтересовался, предлагаю в качестве упражнения реализовать функции filter, и одну из кванторных: some либо every. Вы заметите, что везде используется возврат накапливаемого массива.

Ещё один нетривиальный пример, который приходит на ум, это реализация функции uniq. Как известно, JavaScript страдает от отсутствия в стандартной либе многих нужных вещей. В частности, нет функции, которая устраняет дубликаты в массиве, и разработчики используют разные кастомные реализации (лично я советую использовать _.uniq из LoDash/Underscore).

Эта реализация немного «хипстерская», но как пример возможностей reduce сойдёт.
function uniq$viaReduce (array)
{
	return array.reduce(function (memo, item)
	{
		return (~ memo.indexOf(item) ? null : memo.push(item)), memo;
	}, []);
};

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

Код и тесты есть в репозитории.

Ладно, не «немного», этот код был сильно странный, меня оправдывает только наличие тестов и то, что это функция библиотечного типа, поведение которой не будет меняться. Желательно использовать другие реализации в своём коде, использование же как reduce, так и indexOf скажется отрицательным образом на производительности такого uniq, а обильное использование однострочников и тильд — на читаемости.

В качестве разминки, я рекомендую реализовать, например, функцию zipObject суть её в том, что она принимает на вход массив пар (массивов), где нулевой элемент это ключ, а первый — значение, и возвращает сконструированный Object с соответствующими ключами/значениями.

Подробнее о репозитории.


Репозиторий с примерами является npm-пакетом. Его можно поставить, используя адрес на гитхабе:
npm install StreetStrider/habrahabr-javascript-reduce

В src/ лежат примеры функций, в tests/ — jasmine-тесты. Прогнать все тесты можно с помощью npm test.

В репозитории есть также ответ на вопрос о значении Math.pow при отсутствии степени (и другие граничные случаи).

Другие reduce


* В JavaScript у reduce есть злой брат-близнец правосторонний аналог: reduceRight. Он нужен, чтобы агрегировать массивы справа-налево, без необходимости в дорогостоящем reverse.
* LoDash/Underscore есть _.reduce, _.reduceRight. Они обладают рядом дополнительных возможностей.
* В Python есть reduce. Да. Но он официально не рекомендуется к использованию и его даже убрали из глобального неймспейса. Вместо него предлагается использовать списковые выражения и конструкции for-in. Кода получается больше, но он становится намного более читаемым. Это соответствует Дао языка.
* В некоторых языках reduce/reduceRight называются foldl/foldr.

В SQL есть пять стандартных агрегирующих функций: COUNT, SUM, AVG, MAX, MIN. Эти функции используются, чтобы свести результирующую выборку к одному кортежу. Аналогичные функции можно реализовать на JavaScript (тоже на reduce).

Кстати, четыре из пяти агрегирующих функций SQL (не считая COUNT) возвращают NULL, если выборка пустая (COUNT возвращает определённое значение: 0). Это полностью аналогично JS-ному TypeError на пустом списке.

postgres=# SELECT SUM(x) FROM (VALUES (1), (2), (3)) AS R(x);
 sum 
-----
   6
(1 row)

postgres=# SELECT SUM(x) IS NULL AS sum FROM (VALUES (1), (2), (3)) AS R(x) WHERE FALSE;
 sum 
-----
 t
(1 row)

Итог


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

Ссылки

  1. JavaScript Puzzlers.
  2. MDN: Array.prototype.reduce().
  3. github:StreetStrider/habrahabr-javascript-reduce.
  4. JavaScript.ru: Массив: Перебирающие методы.

Благодарности


Спасибо subzey за то, что натолкнул меня на мысль, что reduce может возвращать что угодно.
Спасибо всем, кто напишет мне в личные сообщения об ошибках и недочётах в статье, а также в репозитории.

Спасибо за внимание.
@StreetStrider
карма
29,7
рейтинг 0,0
Разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +40
    Когда я вижу такой код
    return (~ memo.indexOf(item) ? null : memo.push(item)), memo;
    — хочется обратиться ко всем начинающим (или «продолжающим») JS разработчикам — не пишите так никогда. Совсем никогда. Вы можете подумать, что это какой-то крутой, «джедайский» код — который позволяет очень лаконично выразить вашу бизнес-логику, используя доступные только «гуру» «экзотические» операторы и конструкции.
    Это абсолютно не так.

    Сравните
    return (~ memo.indexOf(item) ? null : memo.push(item)), memo;
    и
    if(memo.indexOf(item) < 0) memo.push(item); return memo;


    Второй вариант на порядок читабельнее, не использует ничего кроме банального if, и (вот где профит-то :-)) — короче на 6 символов.

    Он же в виде целой функции, чтобы можно было сразу проверить работоспособность (перенос строки перед return — по желанию)
    
    function uniq$viaReduce (array)
    {
        return array.reduce(function (memo, item)
        {
            if(memo.indexOf(item) < 0) memo.push(item); 
            return memo;
        }, []);
    };
    
    • +4
      только логичнее делать сравнение не <0, а ===-1
      • +2
        Ну, на вкус и цвет…
        На мой субъективный взгляд "-1" выглядит чуть более «магическим» числом чем «0», для тех кто не знает, что в точности должен возвратить indexOf в случае отсутствия элемента в массиве.
        Только в данном случае ===-1 не надо, достаточно == -1
        Ведь метод indexOf нам заведомо (согласно спецификации) не может вернуть ничего кроме числа, поэтому нет нужды в дополнительной проверке типа.
        • +3
          Я, согласно здравому смыслу, предпочитаю не думать, достаточно == или нет. В тех (крайне редких) случаях, когда действительно нужен ==, ставлю. Имело бы смысл выбирать, если бы === был дороже, чем ==, так нет же.
        • +1
          Да, соглашусь, тут вопрос того, какой вариант мозг на автомате воспринимает как канонический :)
        • +2
          === не делает проверку типа. == делает приведение. Разница есть.
          • 0
            Неточно немного. === делает, конечно же, проверку типа. Но и == тоже, и если что — приводит.
            • 0
              Ну в том и смысл, что до приведения типов дело не доходит, когда мы сравниваем заведомо два числовых значения.
              Предлагаю не обсуждать дальше разницу в поведении === и == в общем случае — все это давно обсуждено и всем известно. Я специально указал в первоначальном своем комментарии, что он относится только к конкретному фрагменту кода и ни к чему больше.
    • 0
      Рад, что вам понравился этот пример. Вообще, ни он, ни ваш код не нужны, т.к. можно просто использовать библиотечный аналог (о чём я упомянул в статье). Пример чисто синтетический и он реализует не «бизнес-логику», а библиотечную функцию, спека которой заморожена, и, следовательно, это код не подвержен изменениям. Поэтому я позволил себе использовать джедайский подход.

      По вашему варианту (если предположить, что это всё таки БЛ) можно сказать, что он более читаемый, но я бы ещё обрамил ветви в фигурные скобки, потому что они имеют свойство расширяться, и тогда скобки уже необходимы. А также, я бы разнёс на разные строчки условие и ветвь then. Это сделает дифф чище, если условие или одна из ветвей будет меняться по отдельности. Ну, и как уже заметили, лучше использовать сравнение с -1, потому что это флаговое значение.

      А вообще, мне интересно померить скорость самого подхода с reduce. Возможно он не так быстр, как простой плоский цикл, и стоило бы придраться именно к этому :)
      • +1
        Естественно reduce не так быстр, как плоский цикл, не говоря уже о том, что конкретно для uniq$viaReduce в момент вызова indexOf будет перебираться весь массив в поисках нужного элемента, что раздувает количество шагов цикла в квадрат раз.

        Однако, все это может быть совершенно не заметно на массивах размером до, например, 1000 элементов (ну или 10 000, я не знаю). Т.е. разница в несколько десятков миллисекунд, либо меньше.

        Очень странно слышать комментарии про фигурные скобки и разнесение ветвей на разные строчки в контексте обсуждения
        return (~ memo.indexOf(item) ? null : memo.push(item)), memo;

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

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

        И комментарий я написал именно ради того, чтобы этот ваш термин не вводил никого в заблуждение.
        • 0
          А, теперь я вас понял. Вы указываете на то, что слово «джедайский» несёт позитивный оттенок, и это проецируется на код. Вероятно, стоит добавить в статью после примера более конкретный дисклеймер. Спасибо.
          • 0
            По теме стоит отметить, что indexOf — вообще плохое название для действия «проверить, содержит ли массив данный элемент». Не говоря уже обо всей этой чернухе с -1. В js явно не хватает метода с номальным именем, возвращающего булево значение.
            • 0
              Я так понимаю, вы хотите метод навроде exists(item) → Boolean? Это было бы и вправду круто, но в защиту indexOf скажу, что у него есть одно достоинство. Зачастую, если элемент найден мы должны с ним что-то сделать далее, а для этого нужен его индекс. Т.е. возврат indexOf сначала используется для проверки «есть-нету», и если «есть», то далее с этим что-то сделать (splice, например, или получение по индексу).
    • +2
      Я не то что бы «за» этот способ, но чего это вдруг бинарные операции стали «экзотическими», как и принцип работы с запятой?
      • 0
        Приравняйте «экзотические» к «редко используемые», и ответ найдется сам собой. Усредненный «JS программист в вакууме» за 3, например, года своей карьеры с такими конструкциями может сталкиваться пару раз, даже с учетом кода сторонних плагинов и библиотек. Не надо только говорить, что такому в программировании не место, что надо развивать кругозор и т.д. — по состоянию рынка труда на текущий день у нас таких спецов все равно большинство, и это надо всегда учитывать при написании кода.
        • 0
          Если оператор «запятая» крайне вредна, а тернарный оператор вообще не к месту, то унарное «не» в конструкции ~array.indexOf(foo) давно устоялось и смотрится явно красивее сравнения с магическими цифрами 0 и -1. Если перед indexOf унарное «не» — явно проверка вхождения, сравнение с чем-то — еще нужно подумать. Улучшать читаемость кода для говнокодеров в ущерб себе я, например, не намерен.
        • –1
          А я скажу, что такого программиста нужно учить, в том числе и такими примерами, чтобы его не пугали в коде конструкции вроде этой if (ret ^ not) { .. }. А не то в итоге мы получаем людей, которые не знают базовых понятий, регеспов, зачем parseInt второй параметр, не понимающих 5.67e8 или .911, прототипы и т.п.
          • +5
            К rock и RubaXa у меня вопрос — вы веб программистов нанимали когда-нибудь на работу? собеседование проводили? искали подрядчиков на аутсорсе? какой процент из соискателей (или потенциальных исполнителей) готов сходу разобраться в ~indexOf или разглядеть XOR оператор в (ret ^ not)? и за какие деньги они готовы потом работать?

            а то у меня ощущение что мы с разных точек зрения на это смотрим
            • +1
              Да, и порой складывается крайне печальное впечатление, базы нет, не говоря уже о знание самого языка. И в этом есть «наша» вина. Просто порой мне кажется, что javascript программист, это какой то недалекий человек, с отсутствием долговременной памяти. В какой-то момент этот человек, вместо битовых масок начинает писать код повышенной бредовости, якобы читаемый. А ведь битовые операции, как и всю булевскую логику давно в школе преподают.

              Ведь проблема не только с битовыми операциями, замакания, функция-конструктор, область видимости и т.п. для многих магия, это печально.
    • +1
      Абсолютно не соглашусь с вами, во всем, за исключением действительно странного примера с тернарным оператором, тернарность которого тут и правда не нужна. Я убежден, что крокфорданизация JavaScript преследует немного иную цель, нежели писать все согласно заветам, и эта цель — единообразие.

      Если команда решает, что нужно по возможности использовать тернарки, то нужно следовать этому. Если команда решает, что нужно использовать лексему `~[].indexOf()` для проверки вхождения — нужно этому следовать. Если в команде принято иначе — нужно придерживаться общих правил.

      В JavaScript есть некоторые «неудобные конструкции» — hoisting, автоподстановка точки-с-запятой, которая не срабатывает на строках оканчивающихся или начинающихся на скобочки и операторы, которые могут быть как унарными, так и бинарными. Но все это не значит, что этими штуками грушновато пользоваться. Я считаю это глупым. Посмотрите, сколько команд не используют точку с запятой, посмотрите сколько дейстивительно великолепных JS-разработчиков пишут вызов функций через тернарные операторы. TJ Holowaychuk, Isaac Schlueter, да у каждого состоявшегося разработчика есть предпочтения, и это не просто нормально. Это отлично!

      Совершенно не имеет значения, какие лексемы и практики языка использовать, а какие нет, главное, делать это единообразно в рамках всей команды. Если вы не знакомы с побитовым оператором `~` — это значит, что нужно разобраться с ним. Если вы знакомы — в чем проблема запомнить лексему? Научиться практикам команды для начинающего разработчика практически тождественно тому, чтобы запомнить стайлгайд, возможность разобраться с острыми углами стайлгайдов — это комплимент, который вам сделала команда.

      Для примера сравните такие конструкции:

      function (rights) {
        return ~[rights].indexOf('write')
          ? fs.writeFile(...)
          : next(throw new Error());
      }
      


      function (rights) {
        if ([rights].indexOf('write') !== -1) {
          return fs.writeFile(...)
        } else {
          return next(throw new Error());
        }
      }
      


      function (rights) {
        if ([rights].indexOf('write') !== -1) {
          return fs.writeFile(...)
        }
          return next(throw new Error());
      }
      


      По-моему, нет никаких причин, не писать так, как в первом примере, если а) У вас нет желания научиться в тернарные и битовые операторы б) У вас нет договоренности с командой.

      В любом случае, указывать другим как писать код — это весело, но нужно иметь свою голову на плечах и понимать причины тех или иных гайдов. А читаемость кода — я понимаю исключительно как единообразный стиль, остальное — комформизм.
      • +2
        ОК, давайте по порядку:
        1. Насчет договоренностей в команде — вопросов нет, можно договориться хоть в LISP-стиле писать, если это нужно для решения задачи. Только помимо членов вашей команды есть еще масса людей, которые могут ваш код читать потом — если результат вашей работы передается дальше как часть большого проекта, если вы пишете общую либу, если вы используете ваш код в качестве примеров в документации, докладах или вот в статье на хабре. Ну или вот вы написали все прекрасно, сдали заказчику, потом решили чем-то другим заняться, а через 3 года ему другая уже команда должна доработать это все. Возможно за другие деньги совсем. Я понимаю что как программисту вам на это пофиг, но это не значит, что так надо делать. Есть еще такое слово как ответственность.

        2. Не надо, пожалуйста, валить в кучу тернарные операторы, побитовые операторы, а также (это к комментарию от RubaXa выше) замыкания, области видимости и т.д. У вас какой-то бинарный программист получается — он либо знает только if-else и все (видимо только вчера basic изучил в школе), либо знает и активно использует все операторы и конструкции языка, которые в нем только есть. Это крайности.
        Если посмотреть объективно, то можно выстроить некоторую шкалу встречаемости различных конструкций в коде (в любом JS коде, усредненно), где var-if-else-for будут на первых местах где-то, замыкания и тернарники на 5-10, а побитовые операторы и возврат значения после запятой (return expression, bar) — на 100 по частоте использования.

        Опять же, если вы действительно пишите какой-то математический алгоритм или активно работаете с числами, можно и нужно использовать побитовые операторы. Они для этого и сделаны в таких языках как JS. Но без реальной нужды их лучше не вставлять в код, если вы не пишете just for fun и только для себя.
        • –1
          Поверьте, даже в мыслях не было валить всё в кучу, просто такова картина после собеседования. Конечно, если вам нужен человек, который будет натягивать jQuery-плагины, то ему всего этого не надо, но если вы делаете что-то серьезное, нужно знать базу и тонкости языка.
        • +2
          1. С первым аргументом не соглашусь. Это скорее вопрос запоминания лексических конструкций языка. Я считаю абсолютно нормальным, если я берусь дорабатывать чей-то код и нахожу там какие-то непривычные мне лексемы. Стиль написания кода у всех разный, и я с этим лучше смирюсь, чем решу, что теперь все нужно заново переписать. Да и вообще боттл-нек не тут, если речь о ресурсах на поддержку.

          2. Тернарные операторы — это частный случай. С вашей логикой приоритезации согласен. Это нужно понимать, но не более. Если ты не понимаешь тонкости языка, и правда, не стоит лесть в дебри Если ты пишешь интерфейс для тех, кто не понимает — не стоит подвергать их соблазну. Но если с этим проблем нет — то ограничивающий фактор — исключительно командные решения,.

          Кроме того, мне кажется, что вы преувеличиваете идеальный код. Это не более чем элементарные вещи: DRY, KISS, настроенный линтинг, стайлгайды, тесты, лаконичные методы и тд. Возможно, это еще и модульность: блек-бокс — это потрясающе — передаешь что-то на вход, получаешь что-то на выходе. Подробности реализации не имеют значения, так как они должны стабильно решать задачи, не более того. Да и в коммерческих проектах идеальный код не всегда возможен — есть задачи, которые нужно было «вчера и на оленях», тут блек-боксы очень помогают.

          Даже код в примере (хотя он и в правду наркоманский, и запятая — это очень не ок, а используй concat([item]) вместо push(item) она и не нужна будет) решает свою задачу — показать пример реализации метода, не вдаваясь в подробности.

          Стремиться быть всем понятным — это утопия, и лично я против того, чтобы следовать ей. Но стремиться ыть понятным своей команде — это очень важно, и этому следовать нужно.
  • +2
    Не смотря на некоторую магию в коде спасибо за внятное разъяснение. И особое спасибо за интересные факты в конце статьи.
    • +1
      да, занятно. хотя иногда кажется: ну что там еще можно сказать про js. а оказывается есть что.
      • +2
        Иногда натыкаешься на очень странные вещи, странные подходы.
        Например, пару месяцев назад мне на глаза попалась вот такая либа: github.com/tarruda/s-pipe. Это JS, это потоки (stream) и это закос под LISP (цитирую: «lisp-inspired chaining syntax»).
        Как-то наткнулся на декораторы функций, основанные на действии valueOf. Сразу понимаешь, что не всё ещё сказано и использовано, и в JS ещё вагон разных интересных штук.
        • 0
          Сейчас вспомнил школу, когда учитель литературы допытывал вопросами типа «Что автор стихотворения хотел сказать этими словами?»…
          Мне кажется, Брендан, закладывая в Javascript именно такие принципы, даже и не представлял, что спустя годы программисты будут строить вокруг языка столько интересных вещей…
  • –2
    Вот только не функция, а метод.
  • +4
    На мой взгляд, идеальный вариант разобраться в методах — это заглянуть в исходник (или в код полифила, как в данном случае). А ещё лучше предварительно [попытаться] реализовать их самому, а потом уже посмотреть, что забыли, что не учли а чего не знали об этом методе. Тогда всё очень хорошо и на долго запоминается. Прям в мышечной памяти:)

    А статья неплохая, спасибо.
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Добавлю от себе еще один туториал, который дает возможность понять не только функциональное программирование но и реактивное. Ребята из организации Rx создали вот такой туториал reactive-extensions.github.io/learnrx/
    • 0
      Да, я помню, как Гвидо это говорил, но забыл поискать ссылку. Я хотел добавить это в статью, в пункт про Python. Тем более есть на хабре, спасибо.
    • 0
      Тем самым он еще больше внес путаницы в язык.
    • 0
      Добавил в статью.
      • +1
        Это авторитетное, но еще одно личное мнение :)
  • 0
    Хорошая функция reduce, я её в парсинге после первичного разбора строки использую…

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