Реальные примеры использования reduceRight в JavaScript
Некоторое время назад я разместил вопрос о StackOverflow, показывающий, что встроенная реализация reduceRight
в JavaScript раздражает. Следовательно, я создал функцию стиля foldr
в стиле Haskell как средство:
function foldr(array, callback, initial) {
var length = array.length;
if (arguments.length < 3) {
if (length > 0) var result = array[--length];
else throw new Error("Reduce of empty array with no initial value");
} else var result = initial;
while (length > 0) {
var index = --length;
result = callback(array[index], result, index, array);
}
return result;
}
Однако я никогда не использовал эту функцию foldr
просто потому, что мне никогда не нужно было перебирать массив справа налево. Это заставило меня задуматься, почему я не использую foldr
в JavaScript столько, сколько я делаю в Haskell, и какие примеры реальных примеров использования foldr
в JavaScript?
Я мог ошибаться, но я считаю, что функция foldr
широко используется в Haskell из-за:
Это объясняет, почему foldr
или reduceRight
широко не используются в JavaScript. Я еще не видел реальное использование foldr
в реальном мире только для его итерации справа налево.
Это подводит меня к двум моим вопросам:
- Каковы некоторые реальные примеры использования
reduceRight
в JavaScript? Возможно, вы использовали его в пакете npm. Было бы здорово, если бы вы могли связать меня с вашим кодом и объяснить, почему вам нужно использовать reduceRight
вместо reduce
.
- Почему
reduceRight
не используется так широко, как reduce
в JavaScript? Я уже предоставил свои два цента по этому вопросу. Я считаю, что foldr
в основном используется только для его лени, поэтому reduceRight
не очень полезен в JavaScript. Однако я мог ошибаться.
Для первого вопроса я попытался найти примеры реальных примеров использования reduceRight
в JavaScript. Однако я не нашел удовлетворительных ответов. Единственные примеры, которые я нашел, были тривиальными и теоретическими:
когда использовать reduce и reduceRight?
То, что я ищу, является практическим примером. Когда целесообразно использовать reduceRight
в JavaScript вместо reduce
?
По второму вопросу, я понимаю, что это прежде всего мнение, основанное на том, почему это хорошо, если вы не отвечаете на него. Основное внимание в этой статье уделяется первому вопросу, а не второму.
Ответы
Ответ 1
Чтобы ответить на ваш первый вопрос, reduceRight
приходит очень удобно, когда вы хотите указывать элементы слева направо, но выполняйте их вправо-влево.
Рассмотрим эту наивную реализацию функции компоновки, которая принимает аргументы слева направо, но считывается и выполняется справа налево:
var compose = function () {
var args = [].slice.call(arguments);
return function (initial) {
return args.reduceRight(function (prev, next) {
return next(prev);
}, initial);
}
}
Вместо того, чтобы занимать время/пространство с вызовом reverse
в массиве, проще и проще понять вызов reduceRight
.
Пример использования этой функции compose
будет выглядеть примерно так:
var square = function (input) {
return input * input;
};
var add5 = function (input) {
return input + 5;
};
var log = function (input) {
console.log(input);
};
var result = compose(log, square, add5)(1); // -> 36
Я уверен, что есть много других технических примеров reduceRight
, которые являются полезными, и это всего лишь один.
Ответ 2
Вы абсолютно правы, до такой степени, что я не совсем уверен, что это даже реальный вопрос. Лень и слияние являются огромными причинами, почему foldr
является предпочтительным в Haskell. Обе эти вещи отсутствуют в массивах JavaScript, поэтому нет буквально смысла использовать reduceRight в реальном мире JavaScript. Я имею в виду, что вы могли бы создать случай, когда вы построили массив, нажимая вещи на концах, а затем вы хотите перебирать их от самых новых до самых старых, накапливая результат. Но это очень надуманное.
Просто, чтобы проиллюстрировать сторону Хаскелла. Обратите внимание, что в Haskell правая складка фактически не выполняет оценку справа налево. Вы можете подумать об оценке группировки справа налево, но из-за лень это не то, что вычислено. Рассмотрим:
foldr (\a _ -> Just a) undefined [1..]
Я поставил начальное значение undefined
для аккумулятора и бесконечный список натуральных чисел, которые нужно сложить. О, Боже. Но это не имеет значения. Это выражение радостно оценивается как Just 1
.
Концептуально группировка работает следующим образом:
let step a _ = Just a
let foldTheRest = foldr step undefined [2..]
step 1 foldTheRest
Думая о группировке, мы "складываем остальные", затем применяем функцию step
к двум аргументам: "первый элемент списка" и "что бы мы ни получили от складывания остальной части списка". Однако, поскольку функция степпинга даже не нуждается в аргументе аккумулятора, эта часть вычисления никогда не оценивается. Все, что нам нужно, чтобы оценить эту особую складку, было "первым элементом списка".
Чтобы повторить, массивы JavaScript не сохраняют никаких преимуществ foldr
, которыми пользуется Haskell, поэтому нет смысла использовать reduceRight
. (Напротив, в Haskell иногда бывают веские причины использовать строчную левую складку.)
n.b. Я не согласен с вашим другим вопросом, когда вы заключаете, что "внутренняя реализация reduceRight неверна". Я согласен с тем, что это раздражает то, что они выбрали порядок аргументов, который они сделали, но это не является по своей сути неправильным.
Ответ 3
Используйте reduceRight
, когда вы пытаетесь создать новый массив элементов хвоста из заданного массива:
var arr = [1,2,2,32,23,4,5,66,22,35,78,8,9,9,4,21,1,1,3,4,4,64,46,46,46,4,6,467,3,67];
function tailItems(num) {
var num = num + 1 || 1;
return arr.reduceRight(function(accumulator, curr, idx, arr) {
if (idx > arr.length - num) {
accumulator.push(curr);
}
return accumulator;
}, []);
}
console.log(tailItems(5));
//=> [67, 3, 467, 6, 4]
Еще одна интересная вещь, которая полезна для reduceRight, заключается в том, что, поскольку она меняет массив, в котором он выполняется, индексный параметр функции проецирования предоставляет индексы массива, начиная с arr.length, например:
var arr = [1,2,2,32,23];
arr.reduceRight(function(accumulator, curr, idx, arr) {
console.log(idx);
}, '');
// => 4,3,2,1,0
Это может быть полезно, если вы пытаетесь найти конкретный элемент массива по его индексу, т.е. arr[idx]
, который относится к хвосту массива, что, очевидно, становится более практичным, чем больше массив - считайте тысячи элементов.
Возможно, хорошим примером в этом случае будет фид социальных сетей, который показывает первые 10 пунктов и может загружать больше по запросу. Рассмотрим, как reduceRight
может быть практичным в этом случае для организации наборов массивов в обратном порядке в соответствии с этим примером.
Ответ 4
Кроме того, вы можете использовать reduceRight
, если вам нужно построить вложенную структуру вычислений/данных наизнанку. Вместо ручной записи
const compk = f => g => x => k => f(x) (x => g(x) (k));
const compk3 = (f, g, h) => x => k => f(x) (x => g(x) (y => h(y) (k)));
const inck = x => k => setTimeout(k, 0, x + 1);
const log = prefix => x => console.log(prefix, x);
compk3(inck, inck, inck) (0) (log("manually")); // 3
Ответ 5
Array.reduceRight()
отлично, когда:
- вам нужно выполнить итерацию по массиву элементов для создания HTML
- И нужен счетчик в HTML ранее для элементов
.
var bands = {
Beatles: [
{name: "John", instruments: "Guitar"},
{name: "Paul", instruments: "Guitar"},
{name: "George", instruments: "Guitar"},
{name: "Ringo", instruments: "Drums"}]
};
function listBandplayers(bandname, instrument) {
var bandmembers = bands[bandname];
var arr = [ "<B>" , 0 , ` of ${bandmembers.length} ${bandname} play ` , instrument , "</B>",
"\n<UL>" , ...bandmembers , "\n</UL>" ];
var countidx = 1;
return arr.reduceRight((html, item, idx, _array) => {
if (typeof item === 'object') {
if (item.instruments.contains(instrument)) _array[countidx]++;
item = `\n\t<LI data-instruments="${item.instruments}">` + item.name + "</LI>";
}
return item + html;
});
}
console.log( listBandplayers('Beatles', 'Drums') );
/*
<B>1 of 4 Beatles play Drums</B>
<UL>
<LI data-instruments="Guitar">John</LI>
<LI data-instruments="Guitar">Paul</LI>
<LI data-instruments="Guitar">George</LI>
<LI data-instruments="Drums">Ringo</LI>
</UL>
*/
console.log( listBandplayers('Beatles', 'Guitar') );
/*
<B>3 of 4 Beatles play Guitar</B>
<UL>
<LI data-instruments="Guitar">John</LI>
<LI data-instruments="Guitar">Paul</LI>
<LI data-instruments="Guitar">George</LI>
<LI data-instruments="Drums">Ringo</LI>
</UL>
*/
Ответ 6
Array.prototype.reduceRight
действительно полезен в Javascript даже при строгой оценке. Чтобы понять, почему можно взглянуть на преобразователь карты в Javascript и тривиальный пример. Поскольку я являюсь функциональным программистом, моя версия в основном основывается на валютных функциях:
const mapper = f => g => x => y => g(x) (f(y));
const foldl = f => acc => xs => xs.reduce((acc, x) => f(acc) (x), acc);
const add = x => y => x + y;
const get = k => o => o[k];
const len = get("length");
const concatLen = foldl(mapper(len) (add)) (0);
const xss = [[1], [1,2], [1,2,3]];
console.log(concatLen(xss)); // 6