Мемоизация и каррирование в JS

Сейчас в JavaScript приносят много техник из функционального программирования. Преимущество в использовании функциональных подходов в JS является лаконичная реализация в пару строк кода и уменшение дублирования. Из этого следует и минусы. Например, в виде читаемости и понимании. Особенно если незнакомы как работает функциональное программирование.

Мемоизация

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function memo(fn) {
    const cache = {};
    const slice = Array.prototype.slice;
    
    return function () {
        const args = slice.call(arguments);
        const key = JSON.stringify(args);
        
        if (cache[key]) {
            return cache[key]; 
        }
        
        const result = fn.apply(null, args);
        cache[key] = result;
        
        return result;
    };
}

Где это можно применить? К примеру, в тех случаях когда нам нужно выполнить сложные и продолжительные вычисления.

Каррирование

Каррирование - это способ создания функций, позволяющий частичное применение аргументов функции. Например, имеется функция add(), вычисляющая сумму двух чисел: x и y. Ниже демонстрируется, как бы мы находили сумму исходя из того, что x имеет значение 5, а y равен 4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// есть функция сложения двух чисел
function add(x, y) {
    return x + y;
}

// так же нам известны значения аргументов
add(5, 4);

// первый шаг – подстановка первого аргумента
function add(5, y) {
    return 5 + y;
}

// второй шаг – подстановка второго аргумента
function add(5, 4) {
    return 5 + 4;
}

На первом шаге мы не получаем решение, а только другую функцию. Этот шаг и является частичным применением, так как приминили только первый элемент.

Как бы это можно было реализовать в JavaScript с помощью замыкания:

1
2
3
4
5
const add = x => y => x + y;

const fiveAdd = add(5); // typeof fiveAdd(5) === 'function'

fiveAdd(4); // 9

Но такая реализация не является универсальной. Можно реализовать функцию, которая будет выполнять каррирование, где в качестве аргумента будет передаваться любая функция:

1
2
3
4
5
6
7
8
9
10
function curry(fn) {
    let slice = Array.prototype.slice;
    let args = slice.call(arguments, 1);
    
    return function() {
        let fnArgs = args.concat(slice.call(arguments));
        
        return fn.apply(null, fnArgs);
    }
}

Где можно применять этот подход? Если в есть функция которая неоднократно вызывается с практически одними и теми же аргументами, то она будет хорошим кандидатом на каррирование. Можно создать новую функцию, используя метод частичного применения параметров к оригинальной функции. Новая функция будет хранить повторяющиеся параметры. Багодаря этому не нужно передавать каждый раз аргументы.