Если не обращать внимание на содержание текстов, то вот как обычно выглядит сложный код, участки которого похожи на лежащие на боку буквы «V», и простой код, блок которого, если не учитывать разную длину строк, похож на прямоугольник.
![](https://habrastorage.org/getpro/habr/post_images/673/12a/d7f/67312ad7f4fd83a676ec9fc9d05691d6.png)
Чем больше отступов – тем сложнее обычно и код
Конструкции, которые надо выделять отступами, будут в коде всегда, речи о том, чтобы совсем избавиться от них, не идёт. Однако, в наших силах уменьшить сложность программ, которые мы пишем, за счёт рационального выбора абстракций для решения встающих перед нами задач.
Возьмём, например, массивы. Традиционно для их обработки используют различные виды циклов. Понятия «массив» и «цикл» неразрывно связаны в сознании многих программистов. Однако цикл – конструкция весьма неоднозначная. Вот что пишет о циклах Луис Атенцио в книге «Функциональное программирование в JavaScript»: «Цикл – это жёсткая управляющая конструкция, которую нелегко использовать повторно и сложно состыковать с другими операциями. Кроме того, использование циклов означает появление кода, который меняется с каждой итерацией.»
![](https://habrastorage.org/getpro/habr/post_images/9c1/864/5e6/9c18645e630249a48b26ce0f2e82eb07.png)
Можно ли избавиться от циклов?
Цикл – одна из основных структурных управляющих конструкций, и, собственно, мы не собираемся говорить о том, что циклы – это зло, от которого надо избавляться. Наша главная цель заключается в снижении сложности собственного кода за счёт минимального использования циклов при обработке массивов. Возможно ли это? Предлагаем выяснить вместе.
Циклы
Мы уже говорили о том, что управляющие конструкции, вроде циклов, усложняют код. Но почему это так? Взглянем на то, как работают циклы в JavaScript.
В JS существует несколько способов организации циклов. В частности, один из базовых видов циклов – это
while
. Прежде чем вникать в подробности, немного подготовимся. А именно – создадим функцию и массив, с которым будем работать.// oodlify :: String -> String
function oodlify(s) {
return s.replace(/[aeiou]/g, 'oodle');
}
const input = [
'John',
'Paul',
'George',
'Ringo',
];
Итак, имеется массив, каждый элемент которого мы собираемся обработать с помощью функции
oodlify
. Если использовать для решения этой задачи цикл while
, получится следующее:let i = 0;
const len = input.length;
let output = [];
while (i < len) {
let item = input[i];
let newItem = oodlify(item);
output.push(newItem);
i = i + 1;
}
Обратите внимание на то, что мы, для того, чтобы отслеживать текущий обрабатываемый элемент массива, используем счётчик
i
. Необходимо инициализировать его нулём и увеличивать на единицу в каждой итерации цикла. Кроме того, нужно сравнивать его с длиной массива, с len
, для того, чтобы знать, когда надо прекратить работу.Этот шаблон настолько распространён, что в JavaScript имеется более простой способ организовать подобную конструкцию – цикл
for
. Такой цикл позволит решить ту же задачу следующим образом:const len = input.length;
let output = [];
for (let i = 0; i < len; i = i + 1) {
let item = input[i];
let newItem = oodlify(item);
output.push(newItem);
}
Цикл
for
– полезная конструкция, так как благодаря ей все стандартные вспомогательные операции со счётчиком выносятся в верхнюю часть блока. Используя while
, легко забыть о необходимости инкрементировать счётчик i
, что приведёт к запуску бесконечного цикла. Определённо, цикл for
гораздо удобнее цикла while
. Но давайте притормозим и взглянем на то, чего пытается достичь наш код. Мы хотим обработать, с помощью функции oodlify()
, каждый элемент массива и поместить то, что получилось, в новый массив. Сам по себе счётчик, используемый для доступа к элементам массива, нас не интересует.Подобный шаблон работы с массивами, предусматривающий выполнение неких действий с каждым элементом, весьма распространён. В результате, в ES2015 появилась новая конструкция цикла, которая позволяет забыть о счётчике. Это – цикл
for…of
. В каждой итерации такого цикла предоставляется следующий элемент массива. Выглядит это так:let output = [];
for (let item of input) {
let newItem = oodlify(item);
output.push(newItem);
}
Код выглядит гораздо чище. Обратите внимание на то, что тут нет ни счётчика, ни операции сравнения. При таком подходе даже не нужно обращаться к конкретному элементу массива по индексу. Цикл
for…of
берёт на себя все вспомогательные операции.Если завершить на этом исследование способов работы с массивами и применять циклы
for…of
везде вместо циклов for
, это уже будет неплохим шагом вперёд за счёт упрощения кода. Но… мы можем пойти дальше.Трансформация массивов
Цикл
for…of
выглядит гораздо чище, чем цикл for
, но и с ним в коде имеется немало вспомогательных элементов. Так, надо инициализировать массив output
и в каждой итерации цикла вызывать метод push()
. Код можно сделать ещё более компактным и выразительным, но прежде чем этим заняться, давайте немного расширим демонстрационную задачу. Как быть, если с помощью функции oodlify()
надо обработать два массива?const fellowship = [
'frodo',
'sam',
'gandalf',
'aragorn',
'boromir',
'legolas',
'gimli',
];
const band = [
'John',
'Paul',
'George',
'Ringo',
];
Вполне очевидное решение – использовать два цикла и обработать массивы в них:
let bandoodle = [];
for (let item of band) {
let newItem = oodlify(item);
bandoodle.push(newItem);
}
let floodleship = [];
for (let item of fellowship) {
let newItem = oodlify(item);
floodleship.push(newItem);
}
Вполне рабочий вариант. А код, который работает – это гораздо лучше, чем код, который не решает поставленную перед ним задачу. Но два очень похожих фрагмента кода не особенно хорошо согласуются с принципом разработки ПО DRY. Код можно подвергнуть рефакторингу для того, чтобы снизить число повторений.
Следуя этой идее, создаём такую функцию:
function oodlifyArray(input) {
let output = [];
for (let item of input) {
let newItem = oodlify(item);
output.push(newItem);
}
return output;
}
let bandoodle = oodlifyArray(band);
let floodleship = oodlifyArray(fellowship);
Выглядит это гораздо лучше, но что, если имеется ещё одна функция, с помощью которой мы тоже хотим обрабатывать элементы массивов?
function izzlify(s) {
return s.replace(/[aeiou]+/g, 'izzle');
}
Теперь функция
oodlifyArray()
не поможет. Однако, если создать ещё одну подобную функцию, на этот раз – izzlufyArray()
, мы опять повторимся. Всё же, создадим такую функцию и сравним её с oodlifyArray()
:function oodlifyArray(input) {
let output = [];
for (let item of input) {
let newItem = oodlify(item);
output.push(newItem);
}
return output;
}
function izzlifyArray(input) {
let output = [];
for (let item of input) {
let newItem = izzlify(item);
output.push(newItem);
}
return output;
}
Эти две функции невероятно похожи. Может быть, обобщим шаблон, которому они следуют? Наша цель заключается следующем: «Имеются массив и функция. Нужно получить новый массив, в который будут записаны результаты обработки каждого из элементов исходного массива с помощью функции». Подобный способ обработки массивов называют «отображением» или «трансформацией» (mapping в англоязычной терминологии). Функции, которые выполняют подобные операции, обычно называют «map». Вот как выглядит наш вариант такой функции:
function map(f, a) {
let output = [];
for (let item of a) {
output.push(f(item));
}
return output;
}
Хотя цикл теперь и вынесен в отдельную функцию, совсем избавиться от него не получилось. Если пойти до конца и попытаться обойтись совсем без циклических конструкций, можно написать рекурсивный вариант того же самого:
function map(f, a) {
if (a.length === 0) { return []; }
return [f(a[0])].concat(map(f, a.slice(1)));
}
Рекурсивное решение выглядит весьма элегантно. Всего пара строчек кода и минимум отступов. Но рекурсивные реализации алгоритмов обычно используют с большой осторожностью, кроме того, они отличаются плохой производительностью в старых браузерах. И, на самом деле, нам совершено не нужно самим писать функцию реализации операции отображения, если только на то нет веской причины. То, что делает наша функция
map
– задача настолько распространённая, что в JavaScript имеется встроенный метод map()
. Если воспользоваться этим методом, код окажется вот таким:let bandoodle = band.map(oodlify);
let floodleship = fellowship.map(oodlify);
let bandizzle = band.map(izzlify);
let fellowshizzle = fellowship.map(izzlify);
Обратите внимание на то, что здесь вообще нет ни отступов, ни циклов. Конечно, при обработке данных, где-то в недрах JavaScript, могут использоваться циклы, но это уже – не наша забота. Теперь код получился и сжатым, и выразительным. Кроме того, он проще.
Почему этот код проще? Может показаться, что вопрос это глупый, но подумайте об этом. Он проще потому что он короче? Нет. Компактность кода – это не признак простоты. Он проще потому что при таком подходе мы разбили задачу на части. А именно, есть две функции, которые работают со строками:
oodlify
и izzlify
. Эти функции не должны ничего знать о массивах или о циклах. Имеется ещё одна функция – map
, которая работает с массивами. При этом ей совершенно безразлично, какого типа данные в массиве, или то, что именно мы хотим с этими данными делать. Она просто исполняет любую переданную ей функцию, передавая ей элементы массива. Вместо того, чтобы всё смешивать, мы разделили обработку строк и обработку массивов. Именно поэтому итоговый код оказался проще.Свёртка массивов
Итак, функция
map
весьма полезна, но она не перекрывает все варианты обработки массивов, в которых используются циклы. Она хороша в случаях, когда на основе некоего массива нужно создать новый, имеющий ту же длину. Но что, если нам нужно, например, сложить все элементы числового массива? Или, если надо найти самую короткую строку в списке? Иногда требуется обработать массив и, фактически, сформировать на его основе одно значение.Рассмотрим пример. Предположим, имеется список объектов, каждый из которых представляет супергероя:
const heroes = [
{name: 'Hulk', strength: 90000},
{name: 'Spider-Man', strength: 25000},
{name: 'Hawk Eye', strength: 136},
{name: 'Thor', strength: 100000},
{name: 'Black Widow', strength: 136},
{name: 'Vision', strength: 5000},
{name: 'Scarlet Witch', strength: 60},
{name: 'Mystique', strength: 120},
{name: 'Namora', strength: 75000},
];
Надо найти самого сильного героя. Для того, чтобы это сделать, можно воспользоваться циклом
for…of
:let strongest = {strength: 0};
for (let hero of heroes) {
if (hero.strength > strongest.strength) {
strongest = hero;
}
}
Учитывая все обстоятельства, этот код не так уж и плох. Мы обходим в цикле массив, храня объект самого сильного из просмотренных героев в переменной
strongest
. Для того, чтобы яснее увидеть шаблон работы с массивом, представим, что ещё надо выяснить общую силу всех героев.let combinedStrength = 0;
for (let hero of heroes) {
combinedStrength += hero.strength;
}
В каждом из этих двух примеров имеется рабочая переменная, которую инициализируют перед запуском цикла. Затем, в каждой итерации, обрабатывают один элемент массива и обновляют переменную. Для того, чтобы выделить схему работы ещё лучше, вынесем операции, выполняемые внутри циклов, в функции, и переименуем переменные для того, чтобы подчеркнуть схожесть производимых действий.
function greaterStrength(champion, contender) {
return (contender.strength > champion.strength) ? contender : champion;
}
function addStrength(tally, hero) {
return tally + hero.strength;
}
const initialStrongest = {strength: 0};
let working = initialStrongest;
for (hero of heroes) {
working = greaterStrength(working, hero);
}
const strongest = working;
const initialCombinedStrength = 0;
working = initialCombinedStrength;
for (hero of heroes) {
working = addStrength(working, hero);
}
const combinedStrength = working;
Если всё переписано так, как показано выше, два цикла оказываются очень похожими. Единственное, что их отличает – это вызываемые в них функции и начальные значения переменных. В обоих циклах массив сворачивается до одного значения. В англоязычной терминологии такая операция называется «reducing». Поэтому создадим функцию
reduce
, реализующую обнаруженный шаблон.function reduce(f, initialVal, a) {
let working = initialVal;
for (let item of a) {
working = f(working, item);
}
return working;
}
Надо отметить, что, как и в случае с шаблоном функции
map
, шаблон функции reduce
распространён так широко, что JavaScript предоставляет его в качестве встроенного метода массивов. Поэтому свой метод, если на это нет особой причины, писать не нужно. С использованием стандартного метода код будет выглядеть так:const strongestHero = heroes.reduce(greaterStrength, {strength: 0});
const combinedStrength = heroes.reduce(addStrength, 0);
Если присмотреться к итоговому результату, можно обнаружить, что получившийся код ненамного короче того, что был раньше, экономия совсем невелика. Если бы мы использовали функцию
reduce
, написанную самостоятельно, то, в целом, код получился бы больше. Однако, наша цель заключается не в том, чтобы писать короткий код, а в том, чтобы уменьшать его сложность. Итак, уменьшили ли мы сложность программы? Могу утверждать, что уменьшили. Мы отделили код циклов от кода, который обрабатывает элементы массива. В результате отдельные участки программы стали более независимыми. Код получился проще.На первый взгляд функция
reduce
может показаться довольно примитивной. В большинстве примеров использования этой функции демонстрируются простые вещи, вроде сложения всех элементов числовых массивов. Однако, нигде не сказано, что значение, которое возвращает reduce
, должно быть примитивным типом. Это может быть объект или даже другой массив. Когда я впервые это понял, это меня поразило. Можно, например, написать реализацию операции отображения или фильтрации массива с использованием reduce
. Предлагаю вам самим это попробовать.Фильтрация массивов
Итак, имеется функция
map
для выполнения операций над каждым элементом массива. Есть функция reduce
, которая позволяет сжать массив до единственного значения. Но что, если нужно извлечь из массива лишь некоторые его элементы? Для того, чтобы эту идею исследовать, расширим список супергероев, добавим туда некоторые дополнительные данные:const heroes = [
{name: 'Hulk', strength: 90000, sex: 'm'},
{name: 'Spider-Man', strength: 25000, sex: 'm'},
{name: 'Hawk Eye', strength: 136, sex: 'm'},
{name: 'Thor', strength: 100000, sex: 'm'},
{name: 'Black Widow', strength: 136, sex: 'f'},
{name: 'Vision', strength: 5000, sex: 'm'},
{name: 'Scarlet Witch', strength: 60, sex: 'f'},
{name: 'Mystique', strength: 120, sex: 'f'},
{name: 'Namora', strength: 75000, sex: 'f'},
];
Теперь предположим, что имеется две задачи:
- Найти всех героев-женщин.
- Найти всех героев, сила которых превышает 500.
К решению этих задач вполне можно подойти, используя старый добрый цикл
for…of
:let femaleHeroes = [];
for (let hero of heroes) {
if (hero.sex === 'f') {
femaleHeroes.push(hero);
}
}
let superhumans = [];
for (let hero of heroes) {
if (hero.strength >= 500) {
superhumans.push(hero);
}
}
В общем-то, выглядит это вполне пристойно. Но тут невооруженным глазом виден повторяющийся шаблон. На самом деле, циклы совершенно одинаковые, различаются они только блоками
if
. Что если вынести эти блоки в функции?function isFemaleHero(hero) {
return (hero.sex === 'f');
}
function isSuperhuman(hero) {
return (hero.strength >= 500);
}
let femaleHeroes = [];
for (let hero of heroes) {
if (isFemaleHero(hero)) {
femaleHeroes.push(hero);
}
}
let superhumans = [];
for (let hero of heroes) {
if (isSuperhuman(hero)) {
superhumans.push(hero);
}
}
Функции, которые возвращают только
true
или false
иногда называют предикатами. Мы используем предикат для принятия решения о том, надо ли сохранить в новом массиве очередное значение из массива heroes
.То, как мы переписали код, сделало его длиннее. Но, после выделения функций-предикатов, стало лучше видно повторяющиеся участки программы. Создадим функцию, которая позволит от этих повторений избавиться:
function filter(predicate, arr) {
let working = [];
for (let item of arr) {
if (predicate(item)) {
working = working.concat(item);
}
}
return working;
}
const femaleHeroes = filter(isFemaleHero, heroes);
const superhumans = filter(isSuperhuman, heroes);
Здесь, как и в случае со встроенными функциями
map
и reduce
, в JavaScript имеется то же самое, что мы тут написали, в виде стандартного метода filter
объекта Array
. Поэтому писать собственную функцию, без явной необходимости, не нужно. С использованием стандартных средств код будет выглядеть так:const femaleHeroes = heroes.filter(isFemaleHero);
const superhumans = heroes.filter(isSuperhuman);
Почему такой подход гораздо лучше, чем использование цикла
for…of
? Подумайте о том, как этим можно воспользоваться на практике. У нас имеется задача вида: «Найти всех героев, которые…». Как только выяснилось, что можно решить задачу с использованием стандартной функции filter
, работа упрощается. Всё, что нужно – сообщить этой функции, какие именно элементы нас интересуют. Делается это через написание одной компактной функции. Не нужно заботиться ни об обработке массивов, ни о дополнительных переменных. Вместо этого мы пишем крошечную функцию-предикат и задача решена.И, как и в случае с другими функциями, которые работают с массивами, использование
filter
позволяет выразить больше информации в меньшем объёме кода. Не нужно читать весь стандартный код цикла для того, чтобы разобраться в том, что именно мы фильтруем. Вместо этого всё, что нужно понять, описано прямо при вызове метода.Поиск в массивах
Фильтрация – весьма полезная операция. Но что, если надо найти лишь одного супергероя из списка? Скажем, нас интересует Black Widow. Функцию
filter
вполне можно использовать для решения этой задачи:function isBlackWidow(hero) {
return (hero.name === 'Black Widow');
}
const blackWidow = heroes.filter(isBlackWidow)[0];
Главная проблема тут заключается в том, что подобное решение не отличается эффективностью. Метод
filter
просматривает каждый элемент массива. Однако, известно, что в массиве лишь одного героя зовут Black Widow, а это значит, что можно остановиться после того, как этот герой найден. В то же время, функциями-предикатами удобно пользоваться. Поэтому напишем функцию find
, которая найдёт и вернёт первый подходящий элемент:function find(predicate, arr) {
for (let item of arr) {
if (predicate(item)) {
return item;
}
}
}
const blackWidow = find(isBlackWidow, heroes);
Тут, опять же, надо сказать, что в JavaScript есть встроенная функция, которая делает в точности то, что нужно:
const blackWidow = heroes.find(isBlackWidow);
В итоге, как и прежде, удалось выразить нашу идею более сжато. С использованием встроенной функции
find
, задача поиска определённого элемента сводится к одному вопросу: «По какому признаку можно определить, что искомый элемент обнаружен?» При этом не надо беспокоиться о деталях.О функциях reduce и filter
Читатели заметили, что неэффективно дважды проходиться по списку героев в вышеприведённых примерах к функциям
reduce
и filter
. Использование оператора расширения (spread operator) из ES2015 позволяет удобно скомбинировать две функции, занятых свёрткой массива, в одну. Вот изменённый фрагмент кода, который позволяет проходиться по массиву лишь один раз:function processStrength({strongestHero, combinedStrength}, hero) {
return {
strongestHero: greaterStrength(strongestHero, hero),
combinedStrength: addStrength(combinedStrength, hero),
};
}
const {strongestHero, combinedStrength} = heroes.reduce(processStrength, {strongestHero: {strength: 0}, combinedStrength: 0});
Не могу не заметить, что эта версия будет немного сложнее, чем та, в которой по массиву проходились дважды, но, если массив огромен, сокращение числа проходов по нему может оказаться весьма кстати. В любом случае, порядок сложности алгоритма остаётся O(n).
Итоги
Полагаю, представленные здесь функции – это отличный пример того, почему продуманно выбранные абстракции и пользу приносят, и в коде выглядят хорошо. Допустим, что мы используем встроенные функции везде, где это возможно. В каждом случае получается следующее:
- Мы избавляемся от циклов, что делает код более сжатым и, скорее всего, более лёгким для чтения.
- Используемый шаблон описывается с использованием подходящего имени стандартного метода. То есть –
map
,reduce
,filter
, илиfind
.
- Масштаб задачи уменьшается. Вместо самостоятельного написания кода для обработки массива, нужно лишь указать стандартной функции на то, что надо с массивом сделать.
Обратите внимание на то, что в каждом случае для решения задачи используются компактные чистые функции.
На самом деле, если обо всём этом задуматься, можно прийти к выводу, который, в первый момент, кажется удивительным. Оказывается, если использовать лишь четыре вышеописанных шаблона обработки массивов, можно убрать из JS-кода практически все циклы. Ведь что делается в практически каждом цикле, написанном на JavaScript? В нём либо обрабатывается, либо конструируется некий массив, либо делается и то и другое. Кроме того, в JS есть и другие стандартные функции для работы с массивами, вы вполне можете изучить их самостоятельно. Избавление от циклов практически всегда позволяет уменьшить сложность программ и писать код, который легче читать и поддерживать.
Уважаемые JavaScript-разработчики, а у вас на примете есть стандартные функции, которые позволяют улучшить код, избавившись от каких-нибудь распространённых «самописных» конструкций?
Комментарии (72)
MrCheater
28.02.2017 16:05+4старый добрый цикл
for…of
Улыбнуло :)
for...of
, который буквально недавно (в ES6) появился
xutesayor
28.02.2017 16:09С массивами то все понятно. А как быть с объектами?
Например, есть объект:
const modes = { air: false, road: true, sea: false };
Нужно получить новый объект, в котором будут только те поля, которые в исходном были «true».
Т.е:
const newModes = { road: true };
Очень часто такая штука используется в мультиселектах.Miklos
28.02.2017 16:41+1Можно так:
const modes = { air: false, road: true, sea: false }; const newModes = Object.keys(modes) .filter(key => modes[key]) .reduce((prev, key) => { return { ...prev, [key]: modes[key] } }, {});
xutesayor
28.02.2017 17:40Благодарю, как-то не додумал до этого.
Жаль что в JS нету map/filter/reduce для объектов. Думаю очень удобно было бы иметь что-то типа:
const newModes = modes.filter( (value, key, obj) => value); или const newModes = modes.map( (value, key, obj) => ({[key]: !value}));
eks1985
28.02.2017 23:40Можно еще чуть укоротить =)
const newModes = Object.keys(modes) .reduce((prev, key) => modes[key] ? ({ ...prev, [key]: modes[key] }) : prev, {});
punkkk
28.02.2017 16:57-1Может я не так понял, но один вариант такой:
let { road: fieldValue } = modes, newObj = { road: fieldValue };
RomanYakimchuk
28.02.2017 16:11+10TLDR:
Работая с массивами старайтесь использовать нативный API (forEach, map, filter, reduce), не делайте код избыточным.
vconst
28.02.2017 16:36+2похожи на лежащие на боку буквы «V»
Любому программисту знаком оператор ">" :)
Janom
28.02.2017 16:54Извиняюсь, за тупость, но поясните, чем рекурсия выгоднее цикла? Быстрее работает? жрет меньше памяти? или типо удобнее читать код?
ookami_kb
28.02.2017 17:00+1Я бы сказал, ни то, ни другое, ни третье. Просто циклы/рекурсия – это низкоуровневая абстракция, которую можно инкапсулировать в отдельной функции, чтобы детали имплементации не смешивались с бизнес-логикой.
Тот же lodash внутри и использует много циклов.
iShatokhin
28.02.2017 19:08Тот же lodash внутри и использует много циклов.
Между тем jdalton удалил из кодовой базы lodash внутренние модули на циклах и заменил их на нативные. Обновление до v5 будет болезненным.
punkkk
28.02.2017 17:02Рекурсивное решение выглядит весьма элегантно. Всего пара строчек кода и минимум отступов. Но рекурсивные реализации алгоритмов обычно используют с большой осторожностью, кроме того, они отличаются плохой производительностью в старых браузерах.
Где тут сказано про выгоду? :)
stardust_kid
28.02.2017 18:20Рекурсия позволяет работать с чистыми функциями, в отличие от массивов. Хотя потом начинаются извращения с трамплинами, а там и не заметишь, как монады начнешь писать.
klvov
28.02.2017 17:59Насколько я могу вспомнить, все нормальные введения в тему функционального программирования начинаются с показа функций map, filter и reduce.
Про javascript еще всегда интересны статьи на тему «Почему в javascript есть функция eval, откуда она там взялась, зачем она там нужна, и почему такой функции нету в императивных языках вроде Pascal и Java».
lovermann
28.02.2017 18:50Это очень круто, что есть люди, которые задумываются над такими вопросами. Когда пишешь код страницами, такие вещи запросто улетучиваются из головы (сыплю голову пеплом).
MrGobus
28.02.2017 19:08+4В названии обещали избавить меня от циклов а в результате упаковали циклы в методы и функции =(
Strain
28.02.2017 23:18+2Javascript без циклов — это в идеальном случае purescript, в хорошем случае livescript, и на худой конец — coffeescript
qbz
01.03.2017 03:40+3Вредная статья, как и каждая вторая про функциональное программирование в JavaScript. Подчеркиваю — в JavaScript.
JavaScript не рассчитан на функциональное программирование, хоть и с виду кажется, что расчитан. Да, есть хай-ордеред функции, да, всякие там лямбды и замыкания. Но если вы пишете [я понимаю, что это перевод, если что], что циклы не нужны, а вот функции нужны, то советую сравнить скорости работы императивного подхода в JS и функционального. Просто возмите и перепишите любой цикл на рекурсию. И сравните. Ведь в функциональных языках, на сколько я знаю, цикл замещается рекурсией? Ну так вот, вы скорее всего расстроетесь и немного загрустите.
Скажем так, против ФП никаких нареканий нет, но оно должно быть уместно и применено в тех языках, которые расчитывают именно на функциональный подход изначально и не являются просто "поддерживаемой парадигмой", а внутренним устройством.
Лично мой подход — это здравое комбинирование императивного и функционального. Функциональное, обычно, описывает архитектуру, какой-то паттерн. А вот конкретные имплементации отдельных функций/методов уже не брезгуют циклами и прочими вещами, далекими от ФП и близкими императивному подходу.
Тем самым сохраняется чистая архитектура и здравая скорость.
zorro1211
01.03.2017 05:56+1Flammar
01.03.2017 14:32Насколько я читал, изначально просто хотели сделать императивный диалект
Scheme
для скриптования браузераNetscape
по типуAutoCAD
'а, но потом пришли настойчивые маркетологи и сказали что нужен скриптовый язык, похожий на C и Java.
dontbrain
02.03.2017 07:14+3Из циклов for и while можно выйти в любой момент. Иногда это нужно.
Aingis
02.03.2017 11:01Из array-функций тоже можно выйти. Либо через
some
/every
, либо хаком с обнулением входящего массива:
[...Array(10)].forEach((_, n, arr) => { console.log(n); if (n == 3) arr.length = 0; })
qbz
03.03.2017 02:53function conditionBreak(condition, body){ var state = true; typeof condition !== 'function' && (condition = function(){}); typeof body !== 'function' && (body = function(){}); return function(){ if (state && condition.apply(undefined, arguments)) { body.apply(undefined, arguments); } else { state = false; } } } [1, 2, 3, 4, 1, 2, 5].map(conditionBreak((n) => { return n <= 3; }, (n) => { console.log(n); }));
Лучше уж как-нибудь вот так. В отличии от .filter он не отбирает, а реально "выбрасывает" при первом неудовлетворяющем условии.
Aingis
03.03.2017 13:42По сути ничем не отличается от
.filter().map()
. Можно и простоreturn
сделать, это не даст избежать лишних итераций.qbz
03.03.2017 17:23Я же написал, чем это отличается от фильтра. Фильтр, если, например, мы имеем массив со значениями 1, 2, 3, 1, 2, 3 и правило выхода "n <= 2", вернет массив со значениями 1, 2, 1, 2.
В вышеописанной функции conditionBreak остановка произойдет после первой тройки, то есть значения будут 1, 2 и все. Это более близко поведению break оператора цикла.Aingis
03.03.2017 18:09Ничто не мешает добавить такой же флаг и в
.filter()
, невелика наука.
var state = true; arr.filter((n) => state && (state = n <= 2)).map(/*...*/)
time2rfc
почему я люблю scala
коротко
читаемо
gogolor
Так в JS так же:
zapolnoch
Не разрушайте иллюзии. Пусть живут в эльфийском мире Scala/Haskell/F# и думают, что JS для орков.
time2rfc
Имею мнение, что вы что-то додумываете
develop7
справедливости ради, без sum types, pattern matching (нет, destructuring недостаточно) и с 2 (двумя) неэквивалентными типами, представляющими отсутствие значения, жить сильно сложнее
vndtta
давайте лучше подумаем, в каком языке этого нет
abyrkov
Pascal
vndtta
ну если принять во внимание то, что Pascal->Object Pascal ->Delphi
тогда и там можно найти и упорядоченные списки, и сортировки, и бинарный поиск…
я подумал, может delphi — это слишком притянуто за уши? дополнительно нагуглил freepascal rtl tlist http://www.freepascal.org/docs-html/rtl/classes/tlist.indexof.html
punkkk
Заранее извиняюсь за, возможно, глупый вопрос, но зачем фигурные скобки внутри списка аргументов? Учитывая, что можно вообще без скобок. Или я что-то путаю?
ookami_kb
Если без скобок, то в функцию передастся объект. А со скобками сразу произойдет деструктуризация.
justboris
Это деструктурирование.
можно написать так
А можно не писать
hero
а достать из него только поле sex, которое нам нужноpunkkk
Ох, там же объект передается, тогда логично. Как-то я не задумался, что аргумент — объект.
Мой косяк, спасибо. :)
FrozenInternet
Если аргумент один, то можно еще и без круглых скобок. :) Вот так: