Реальные примеры использования reduceRight в JavaScript

некоторое время назад я опубликовал вопрос о StackOverflow, показывающий, что собственную реализацию reduceRight в JavaScript раздражает. Следовательно, я создал стиль Haskell foldr функции как устранение:

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 из-за:

  1. Ленивостью (foldl является хвост рекурсивным, так почему foldr работает быстрее, чем foldl?)
  2. короткое слияние с помощью foldr/build (правильность короткого замыкания fusion:foldr/build)

это объясняет, почему foldr или reduceRight Не широко используется в JavaScript. Я еще не видел реального использованияfoldr только для его порядка итерации справа налево.

это подводит меня к двум моим вопросам:

  1. Каковы некоторые реальные примеры использования reduceRight в JavaScript? Возможно, вы использовали его в пакете npm. Было бы здорово, если бы вы могли связать меня с вашим кодом и объяснить, почему вам нужно использовать reduceRight вместо reduce.
  2. почему reduceRight не используется так широко, как reduce в В JavaScript? Я уже высказала свое мнение по этому вопросу. Я верю этому foldr в основном используется только для своей лени, поэтому reduceRight не очень полезно в JavaScript. Однако я могу ошибаться.

для первого вопроса я попытался найти примеры реального мира для использования reduceRight в JavaScript. Однако, я не нашел удовлетворительных ответов. Единственные примеры, которые я нашел, были тривиальными и теоретическими:

когда использовать сокращения и reduceRight?

то, что я ищу является практическим примером. Когда это практично использовать reduceRight в JavaScript вместо reduce?

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

6 ответов


чтобы ответить на ваш первый вопрос, reduceRight очень удобно, когда вы хотите указать элементы слева направо, но выполнять их справа налево.

рассмотрим эту наивную реализацию функции compose, которая принимает аргументы слева направо, но читается и выполняется справа налево:

var compose = function () {
    var args = [].slice.call(arguments);

    return function (initial) {
        return args.reduceRight(function (prev, next) {
            return next(prev);
        }, initial);
    }
}

вместо того, чтобы занимать время / пространство с вызовом reverse на массиве проще и проще понять reduceRight звонок.

An пример использования 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 быть полезным и это только один.


вы абсолютно правы, до такой степени, что я не совсем уверен, что это даже реальный вопрос. Лень и Fusion являются огромный причины foldr предпочтительнее в Haskell. Обе эти вещи отсутствуют в массивах JavaScript,поэтому буквально нет причин использовать reduceRight в реальном JavaScript. Я имею в виду, вы могли бы придумать случай, когда вы построили массив, нажав вещи на конец, а затем вы хотите перебирать их от новейших до старейший при накоплении результата. Но ИМО это очень надуманно.


просто чтобы проиллюстрировать сторону Хаскелла. Обратите внимание, что в Haskell правая складка делает не на самом деле выполнить оценка справа налево. Ты можешь!--9-->думаю о группировка оценка справа налево, но из-за лени, это не то, что вычисляемые. Подумайте:

foldr (\a _ -> Just a) undefined [1..]

я поставил undefined начальное значение для аккумулятора, и бесконечный список натуральных чисел, чтобы сложить. О боже. Но это не имеет ни малейшего значения. Это выражение радостно оценивается как Just 1.

концептуально группировка работает следующим образом:

let step a _ = Just a
let foldTheRest = foldr step undefined [2..]
step 1 foldTheRest

думая о группировке, мы "складываем остальное", затем применяем step функция двух аргументов: "первый элемент списка", и "все, что мы получили от складывания остальной части списка."Однако, так как функция stepping даже не нужен аргумент аккумулятора, эта часть вычисления никогда не оценивается. Все, что нам нужно было для оценки этого конкретного сгиба, - это "первый элемент списка"."


чтобы повторить, массивы JavaScript сохраняют нет преимущества foldr что Haskell пользуется, так что буквально нет причин использовать reduceRight. (В отличие от этого, в Haskell иногда есть веские причины использовать строгую левую складку.)

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


использовать 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, заключается в том, что, поскольку она изменяет массив, который она выполняет, параметр index функции projection предоставляет индексы массива, начиная с arr.длина, например:

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 может быть практично в этом случае организовать коллекции массивов в обратном порядке в соответствии с этим примером.


дополнительно вы можете использовать reduceRight, Если вам нужно построить вложенную структуру вычислений / данных наизнанку. Вместо того чтобы вручную писать!--4-->

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

Я хочу применить программное решение, которое строит рекурсивную структуру:

const compkn = (...fs) => k => fs.reduceRight((chain, f) => x => f(x) (chain), k);

const inc = x => x + 1;

const lift = f => x => k => k(f(x));

const inck = x => k => setTimeout(k, 0, x + 1);

const log = prefix => x => console.log(prefix, x);

compkn(inck, lift(inc), inck) (log("programmatically")) (0); // 0

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>
*/

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

mapper (f => g => x => y => g(x) (f(y))) по существу является функциональным составом во втором аргументе. И когда мы вспоминаем, что правая складка ((a -> b -> b) -> b -> [a] -> b) перевернул аргументы внутри редуктора, мы можем заменить mapper с нормальным составом функции. Из-за абстракции над arity мы также можем легко составить двоичную функцию с унарной.

к сожалению, порядок аргументов Array.prototype.reducdeRight сломан, потому что он ожидает аккумулятор в качестве первого аргумента редуктора. Поэтому нам нужно исправить это с помощью странной лямбды:

const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);

const comp = (f, g) => x => f(g(x));

const add = x => y => x + y;

const len = xs => xs.length;

const concatLen = foldr(comp(add, len)) (0);

const xss = [[1], [1,2], [1,2,3]];

console.log(concatLen(xss));

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