Подтвердить что ты не робот

Реальные примеры использования 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?

По второму вопросу, я понимаю, что это прежде всего мнение, основанное на том, почему это хорошо, если вы не отвечаете на него. Основное внимание в этой статье уделяется первому вопросу, а не второму.

4b9b3361

Ответ 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