Частичное применение функций появится в PHP 8.6

В PHP 8.6 представлено частичное применение функций (PFA) — долгожданное решение для замены однотипных стрелочных функций в `array_map` и `array_filter`. Этот инструмент, выходящий за рамки синтаксического сахара, способствует созданию более чистого и понятного кода. В статье рассматривается принцип работы PFA, приводятся практические примеры и проводится сравнение с классическими подходами.

Частичное применение функций (Partial Function Application, PFA) — одно из ключевых нововведений PHP 8.6. Оно напрямую решает проблему шаблонного кода при работе с обратными вызовами (callback), предлагая синтаксис на основе заполнителей для создания специализированных версий функций.

Это улучшение пригодится разработчикам, работающим с функциональным программированием в PHP, и позволит упростить обработку данных, сократить количество boilerplate-кода и улучшить читаемость скриптов.

Что такое частичное применение функции

Частичное применение функций в PHP 8.6 позволит писать частично применённую функцию (замыкание), вызывая функцию с несколькими аргументами и используя заполнители для остальных. Вместо выполнения PHP возвращает замыкание, список параметров которого автоматически формируется из отсутствующих частей.

Заполнители:

Пример с str_contains:

// Допустим, мы хотим создать функцию, которая проверяет, содержит ли строка подстроку "error".
// Без PFA:
$hasError = fn($s) => str_contains($s, 'error');
// С PFA, фиксируя искомую подстроку (иглу):
$hasError = str_contains(?, 'error');

var_dump($hasError('An error occurred')); // true
var_dump($hasError('All good')); // false

Что изменилось: Исчезает необходимость явно объявлять анонимную функцию для простой фиксации аргумента. Синтаксис становится декларативным: мы прямо указываем, какой аргумент фиксируется ('error'), а какой остаётся свободным (?).

Это актуально при работе с массивами, фильтрацией данных и обработкой событий, где часто требуются однотипные callback-функции.

Фиксация одного аргумента:

function add4(int $a, int $b, int $c, int $d): int
{
return $a + $b + $c + $d;
}

// Задача: создать функцию, которая прибавляет 1, 3 и 4 к переданному числу.
$addFixed = add4(1, ?, 3, 4);
// Эквивалентная стрелочная функция:
// $addFixed = static fn(int $b): int => add4(1, $b, 3, 4);

echo $addFixed(2); // 1 + 2 + 3 + 4 = 10

Что изменилось: Вместо объявления стрелочной функции, которая явно передаёт аргументы, мы используем вызов с заполнителем. Создаваемая функция $addFixed имеет арность 1, хотя исходная add4 принимает 4 аргумента.

Таким образом, PFA убирает рутину: вместо явного объявления анонимной функции для подстановки одного аргумента вы сразу указываете, что фиксируете. Это не просто «продолжение концепции», а прямой инструмент для уменьшения кода в типовых сценариях работы с array_map, array_filter и другими функциями высшего порядка

Также можете оставить несколько незаполненных аргументов.

Фиксация нескольких аргументов:

// Задача: создать функцию, которая складывает 1 и 3 с двумя переданными числами.
$addOneAndThree = add4(1, ?, 3, ?);
// Эквивалентная стрелочная функция:
// $addOneAndThree = static fn(int $b, int $d): int => add4(1, $b, 3, $d);

echo $addOneAndThree(5, 7); // 1 + 5 + 3 + 7 = 16

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

И «Все остальное» с ...

Использование заполнителя ...:

// Задача: создать функцию, которая прибавляет 1 к трём переданным числам.
$addOneToThree = add4(1, ...);
// Эквивалентная стрелочная функция:
// $addOneToThree = static fn(int $b, int $c, int $d): int => add4(1, $b, $c, $d);

echo $addOneToThree(2, 3, 4); // 1 + 2 + 3 + 4 = 10

Что изменилось: Заполнитель ... захватывает все оставшиеся аргументы, что позволяет лаконично создавать адаптеры или откладывать выполнение функции без явного описания всех параметров вручную.

PFA позволяет записывать обратные вызовы в более компактной и декларативной форме. Больше не нужно использовать шаблонные стрелочные функции только для перегруппировки или исправления аргументов. Используйте заполнители ? и ... для указания свободных аргументов, а PHP сделает все остальное.

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

PFA и современный синтаксис PHP

Именованные аргументы и порядок:

function stuff(int $i, string $s, float $f, Point $p, int $m = 0): string { /* ... */ }

// Именованные значения, расположенные не по порядку, все равно работают:
$c = stuff(?, ?, f: 3.5, p: $point);
// Замыкание ожидает (int $i, string $s)

// Именованные заполнители определяют собственный порядок параметров:
$c = stuff(s: ?, i: ?, p: ?, f: 3.5);
// Замыкание ожидает (string $s, int $i, Point $p)

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

Такая гибкость важна при использовании PHP в современных фреймворках, таких как Laravel или Symfony, где часто применяются цепочки вызовов и middleware.

Вариативные функции:

function things(int $i, ?float $f = null, Point ...$points) { /* ... */ }

// Сохраняйте вариативность открытой:
$c = things(1, 3.14, ...);
// Замыкание ожидает (Point ...$points)

// Принудительное точное подсчитывание (вариативные становятся обязательными слотами):
// Указание трёх '?' после первых двух захватывает первые три элемента из вариативного параметра.
$c = things(?, ?, ?, ?);
// Замыкание ожидает (int $i, ?float $f, Point $points0, Point $points1)

Что изменилось: Упрощается работа с вариативными функциями. Заполнитель ... сохраняет вариативность, а использование ? для каждого слота позволяет превратить вариативный параметр в серию явных, что даёт контроль над их количеством без написания сложных обёрток.

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

Особые случаи и ограничения

Вы также можете легко реализовать Thunk-функции с помощью PFA.

Создание Thunk-функций:

function expensive(int $a, int $b, Point $c) { /* heavy work */ }

// Предварительно заполнить всё, отложить выполнение:
$thunk = expensive(3, 4, $pt, ...); // Замыкание с отсутствием требуемых параметров

// Позже:
$result = $thunk();

Что изменилось: Появляется лаконичный способ создания thunk'ов (функций отложенного вычисления) без необходимости писать обёртки вида fn() => expensive(3, 4, $pt).

Вы не можете частично применять конструкторы (new). Вместо этого можно использовать статические методы или фабричные функции.

$maker = Widget::make(?, size: 10); // OK
$new = new Widget(?, 10); // Ошибка компиляции

Что изменилось: Синтаксис PFA не применяется к конструкторам через оператор new. Для создания объектов с частично применёнными параметрами следует использовать статические фабричные методы.

PFA на практике: замена шаблонных стрелочных функций

Главная выгода PFA — элиминация шаблонного кода. Нагляднее всего это проявляется при прямой замене однотипных стрелочных функций. Давайте сравним подходы на реальных задачах.

Замена подстроки в массиве:

// Старый способ (стрелочная функция)
$newArray = array_map(static fn($s) => str_replace('hello', 'hi', $s), $strings);

// Новый способ с PFA (короче и нагляднее)
$newArray = array_map(str_replace('hello', 'hi', ?), $strings);

Что изменилось: Исчезает шаблонная стрелочная функция static fn($s) => .... Синтаксис акцентирует операцию (str_replace) и данные (?), а не структуру обёртки.

Это упрощает рефакторинг, тестирование и поддержку кода, особенно в больших проектах с активной разработкой.

Фильтрация по значению:

$allowed = [1, 2, 3];
// Старый способ
$filtered = array_filter($input, static fn($v) => in_array($v, $allowed, true));

// Новый способ с PFA
$filtered = array_filter($input, in_array(?, $allowed, strict: true));

Что изменилось: Синтаксис становится более декларативным, приближаясь по чтению к инструкции «отфильтровать по вхождению в массив $allowed».

Создание "фабрики" действий:

function logMessage(string $level, string $message, array $context): void { /* ... */ }
// Старый способ: создаём новую функцию
$logError = static fn(string $msg, array $ctx = []) => logMessage('error', $msg, $ctx);
// Новый способ с PFA
$logError = logMessage('error', ?, ?);

Что изменилось: PFA позволяет создавать специализированные функции ($logError) прямо на месте вызова, без явного объявления новой анонимной функции.

Он также удобен для операторов pipe.

Применение в операторах pipe (|>):

$foo
|> array_map(strtoupper(...), ?)
|> array_filter(?, is_numeric(...));
// Правая сторона pipe требует callable максимум с одним аргументом; PFA предоставляет его в лаконичной форме.

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

Практический пример

Этот подход особенно эффективен, когда мы работаем с конвейерами данных или цепочками вызовов. Проиллюстрируем это на примере из области обработки HTTP-запросов, когда необходимо добавить заголовки в HTTP-запрос. Можно заранее заполнить имя и значение заголовка, оставив массив запроса для заполнения позже.

Практический пример с HTTP-запросом:

// 1. Базовые функции-обработчики запроса
function addHeader(array $req, string $name, string $value): array
{
$req['headers'][$name] = $value;
return $req;
}
function setMethod(array $req, string $method): array
{
$req['method'] = $method;
return $req;
}

// 2. Создаём специализированные обработчики с помощью PFA
$withAuth = addHeader(?, 'Authorization', 'Bearer TOKEN');
$asPost = setMethod(?, 'POST');

// 3. Собираем конвейер обработки запроса
$request = ['url' => '/api/data', 'headers' => []];
$request = $withAuth($request); // Добавляем заголовок
$request = $asPost($request); // Меняем метод

// Или даже так (с учётом поддержки pipeline):
// $request = $request |> $withAuth(?) |> $asPost(?);

Что изменилось: Мы создаём переиспользуемые компоненты ($withAuth, $asPost) с помощью PFA прямо на месте, избегая объявления отдельных функций-хелперов или анонимных функций для фиксации параметров. Это повышает модульность и ясность кода при построении конвейеров обработки.

Советы по внедрению и границы применения

Прежде чем массово внедрять PFA в проект, стоит учесть несколько практических аспектов:

Для команд, применяющих практики CI/CD и автоматическое тестирование, это означает более стабильный и предсказуемый код, меньшее количество runtime-ошибок и упрощённую отладку.

Заключение

Частичное применение функций является дополнением, позволяющим сократить шаблонный код и повысить его декларативность при работе с обратными вызовами. Позволяя фиксировать часть аргументов функции с помощью заполнителей, PFA упрощает создание лаконичных, понятных callable без необходимости использования многословных стрелочных функций. С PFA код, связанный с обратными вызовами и высшими функциями, становится не только короче, но и семантически яснее. Вместо явного описания передачи аргумента через анонимную функцию используется прямое указание фиксируемых значений. Такой подход делает логику кода более явной и снижает когнитивную нагрузку при чтении кода.

PFA — это элегантный инструмент для уменьшения шума в коде. Начинайте с малого: в следующий раз, когда будете писать fn($x) => str_contains($x, 'pattern'), попробуйте заменить это на str_contains(?, 'pattern'). Со временем это сделает ваш код не только компактнее, но и выразительнее. Такое внедрение соответствует современным стандартам веб-разработки и способствует созданию чистого, масштабируемого кода.

Хотите глубже разобраться в основах? Изучите Руководство по замыканиям и стрелочным функциям в PHP для полного понимания концепций, на которых построено PFA.

Подробнее о частичном применении функций читайте в официальном RFC.

Комментарии


Дополнительные материалы

Предыдущая Статья

Генерируемые столбцы и SQL-представления: использование в Laravel