2017-02-23 16 views
1

Я знаю, что Ramda.js обеспечивает функцию уменьшения, но я пытаюсь научиться использовать ramda, и я подумал, что редуктор будет хорошим примером. Учитывая следующий код, какой будет более эффективный и функциональный подход?Реализация специального редуктора с использованием ramda

(function(){ 

    // Some operators. Sum and multiplication. 
    const sum = (a, b) => a + b; 
    const mult = (a, b) => a * b; 

    // The reduce function 
    const reduce = R.curry((fn, accum, list) => { 
    const op = R.curry(fn); 
    while(list.length > 0){ 
     accum = pipe(R.head, op(accum))(list); 
     list = R.drop(1, list); 
    } 
    return accum; 
    }); 

    const reduceBySum = reduce(sum, 0); 
    const reduceByMult = reduce(mult, 1); 

    const data = [1, 2, 3, 4]; 
    const result1 = reduceBySum(data); 
    const result2 = reduceByMult(data); 

    console.log(result1); // 1 + 2 + 3 + 4 => 10 
    console.log(result2); // 1 * 2 * 3 * 4 => 24 

})(); 

Выполнить это на РЕПЛ: http://ramdajs.com/repl/

+0

Существуют ли особые случаи, которые вы хотите обработать, которые не будут работать для 'const reduce = R.curry ((fn, accum, list) => list.reduce (fn, accum));'? – user3297291

ответ

2

Я предполагаю, что это упражнение обучения, а не для реального применения. Верный?

Существует определенная эффективность, которую вы могли бы получить над этим кодом. В основе реализации Ramda в году, когда все диспетчеризация, преобразующие и т.д. раздели прочь, что-то вроде:

const reduce = curry(function _reduce(fn, acc, list) { 
    var idx = 0; 
    while (idx < list.length) { 
    acc = fn(acc, list[idx]); 
    idx += 1; 
    } 
    return acc; 
}); 

Я не проверял, но это, вероятно, получает от версии, потому что он использует только номер функций, строго требуемых: по одному для каждого члена списка, и он делает это с игоризацией с голубыми костями. Ваша версия добавляет вызов к curry, а затем на каждой итерации звонит pipe и head, с этой функцией op, в результате вызова pipe, и до drop. Так что это должно быть быстрее.

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

const reduce = curry(function _reduce(fn, acc, list) { 
    return (list.length) ? _reduce(fn, fn(acc, head(list)), tail(list)) : acc; 
}); 

Это приносит в жертву все показатели выше для звонков в tail. Но это явно более простая функциональная реализация. Однако во многих современных JS-машинах это не будет работать даже в больших списках из-за глубины стека.

Поскольку он является хвостовым рекурсивным, он сможет воспользоваться оптимизацией хвостового вызова, указанной ES2015, но пока мало реализованной. До тех пор это в основном академический интерес. И даже когда это доступно, из-за head и - особенно - tail звоните туда, это будет намного медленнее, чем настоятельная реализация выше.

Возможно, вам будет интересно узнать, что Ramda стала второй попыткой создания API. Его оригинальные авторы (отказ от ответственности: я один из них) сначала построили Eweda в строках этой последней версии. Этот эксперимент потерпел неудачу именно по этим причинам. Javascript просто не может справиться с такой рекурсией ... пока.

+0

Да, это было всего лишь учебное упражнение. Большое спасибо за ваш обширный и продуманный ответ. У меня возникло соблазн пойти рекурсивным путем, но, как вы сказали, нет рекурсивной оптимизации. Тем не менее, ваш пример кода очень полезен. Еще раз спасибо! –