Partial: примеры (JAVASCRIPT)

Техника частичного применения функций в JavaScript
Раздел: Функциональное программирование, Частичное применение
partial(fn (function), ...args (any)): function

Что такое функция partial

В JavaScript функция partial сама по себе не является встроенной. Это программный шаблон, часто реализуемый разработчиками для частичного применения другой функции. При частичном применении создается новая функция, где часть аргументов оригинальной функции фиксируется заранее.

Эта техника используется для:

  • Упрощения вызова функций с повторяющимися аргументами.
  • Создания специализированных функций из более общих.
  • Подготовки функций, соответствующих ожидаемой сигнатуре (например, для передачи в map или setTimeout).

Аргументы реализации partial:

  • func: Исходная функция, к которой применяется частичное применение (обязательный).
  • presetArgs: Массив или список аргументов, которые фиксируются первыми (опционально).
  • context: Значение this, которое будет привязано к новой функции (часто реализуется отдельно в bind).

Возвращаемое значение: Новая функция, которая при вызове объединит заранее фиксированные аргументы с переданными в момент вызова и выполнит исходную функцию func.

Базовые примеры применения

Пример реализации и использования простой функции partial.

function partial(fn, ...presetArgs) {
    return function(...laterArgs) {
        return fn.apply(this, [...presetArgs, ...laterArgs]);
    };
}

// Исходная функция
function greet(greeting, name) {
    return `${greeting}, ${name}!`;
}

// Создание частично примененной функции
const sayHello = partial(greet, "Hello");
const sayHi = partial(greet, "Hi");

console.log(sayHello("Alice"));
console.log(sayHi("Bob"));
Hello, Alice!
Hi, Bob!

Пример с указанием контекста выполнения.

function multiply(a, b) {
    return a * b;
}

// Фиксация первого аргумента
const double = partial(multiply, 2);
const triple = partial(multiply, 3);

console.log(double(7));
console.log(triple(5));
14
15

Похожие методы в JavaScript

  • Function.prototype.bind(): Встроенный метод для создания новой функции с привязанным контекстом this и начальными аргументами. Предпочтительнее, когда важен контекст или нужна точная встроенная реализация.
  • Каррирование (currying): Процесс преобразования функции от нескольких аргументов в цепочку функций, каждая от одного аргумента. Каррирование и частичное применение — родственные, но разные техники. Каррированная функция ожидает аргументы по одному.
  • Замыкания вручную: Создание функции-обёртки, захватывающей нужные аргументы через замыкание. Используется для полного контроля над поведением.

Аналоги в других языках

Python: functools.partial

from functools import partial

def greet(greeting, name):
    return f"{greeting}, {name}!"

say_hello = partial(greet, "Hello")
print(say_hello("Alice"))  # Hello, Alice!
Hello, Alice!

PHP: Замыкания (Closure)

function partial($fn, ...$presetArgs) {
    return function(...$laterArgs) use ($fn, $presetArgs) {
        return $fn(...array_merge($presetArgs, $laterArgs));
    };
}

$greet = function($greeting, $name) { return "$greeting, $name!"; };
$sayHello = partial($greet, "Hello");
echo $sayHello("Alice");
Hello, Alice!

C++: std::bind (очень упрощенно)

#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

void greet(string greeting, string name) {
    cout << greeting << ", " << name << "!" << endl;
}

int main() {
    auto sayHello = bind(greet, "Hello", _1);
    sayHello("Alice"); // Hello, Alice!
    return 0;
}

Распространенные ошибки

1. Потеря контекста this при использовании самописной partial без явной передачи.

const obj = {
    prefix: 'Mr.',
    getName: function(first, last) {
        return `${this.prefix} ${first} ${last}`;
    }
};

const badPartial = partial(obj.getName, 'John');
console.log(badPartial('Doe')); // undefined John Doe
undefined John Doe

2. Путаница между partial и каррированием.

// Каррированная версия (ожидает один аргумент за раз)
function curriedAdd(a) {
    return function(b) {
        return a + b;
    };
}

// Частичное применение (может зафиксировать несколько сразу)
const addFive = partial((a, b) => a + b, 5);
// curriedAdd(5)(10) vs addFive(10)

Изменения в реализации

Поскольку partial не является нативной функцией JavaScript, её изменения зависят от реализации в библиотеках или пользовательском коде. В современных версиях ES6+ типичные реализации стали лаконичнее благодаря использованию операторов rest/spread и стрелочных функций.

Пример эволюции реализации:

// Старый стиль (ES5)
function partialES5(fn) {
    var presetArgs = Array.prototype.slice.call(arguments, 1);
    return function() {
        var laterArgs = Array.prototype.slice.call(arguments);
        return fn.apply(this, presetArgs.concat(laterArgs));
    };
}

// Современный стиль (ES6+)
const partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs);

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

1. Частичное применение с пропускаемыми аргументами (используя плейсхолдер, например, _).

Пример javascript
function partialWithPlaceholders(fn, ...args) {
    const placeholders = args.map(arg => arg === partialWithPlaceholders._ ? null : arg);
    return function(...laterArgs) {
        let i = 0;
        const finalArgs = placeholders.map(arg =>
            arg === null ? laterArgs[i++] : arg
        );
        return fn(...finalArgs, ...laterArgs.slice(i));
    };
}
partialWithPlaceholders._ = Symbol('_');

function log(a, b, c, d) {
    console.log(a, b, c, d);
}

const logWithSkip = partialWithPlaceholders(log, 'A', partialWithPlaceholders._, 'C');
logWithSkip('B', 'D'); // Заполняет пропуск вторым аргументом
A B C D

2. Композиция функций с partial.

Пример javascript
const compose = (f, g) => x => f(g(x));
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

const addTwo = partial(add, 2);
const triple = partial(multiply, 3);

const addTwoThenTriple = compose(triple, addTwo);
console.log(addTwoThenTriple(5)); // (5 + 2) * 3
21

3. Использование в обработчиках событий или колбэках.

Пример javascript
function createLogger(moduleName, message) {
    console.log(`[${moduleName}] ${message}`);
}

const logModuleA = partial(createLogger, 'ModuleA');
const logModuleB = partial(createLogger, 'ModuleB');

// Передача в setTimeout
setTimeout(partial(logModuleA, 'Загрузка завершена'), 1000);
// Использование в обработчике
// button.addEventListener('click', partial(logModuleB, 'Клик'));

4. Частичное применение для методов объекта с фиксацией контекста.

Пример javascript
function bindPartial(context, methodName, ...presetArgs) {
    return context[methodName].bind(context, ...presetArgs);
}

const calculator = {
    multiply(a, b) { return a * b; }
};

const double = bindPartial(calculator, 'multiply', 2);
console.log(double(10)); // 20
20

JS partial function comments

En
Partial Creates a function with some arguments pre-filled