Функциональное программирование в PHP: продвинутый уровень
Функциональное программирование (ФП) — это парадигма, в которой вычисления рассматриваются как вычисление математических функций, а изменение состояния и изменяемые данные избегаются. Хотя PHP изначально не был спроектирован как чисто функциональный язык, начиная с версии 7.4 и особенно в PHP 8.x, он получил мощные инструменты для написания кода в функциональном стиле. В этой статье мы разберем продвинутые концепции ФП в PHP: от функций высшего порядка до монад, с практическими примерами и рекомендациями.
1. Функции высшего порядка и замыкания
Функции высшего порядка — это функции, которые принимают другие функции в качестве аргументов или возвращают их. В PHP это реализуется через анонимные функции (замыкания) и встроенные функции array_map, array_filter, array_reduce.
Пример: функция обратного вызова для фильтрации
<?php
$numbers = [1, 2, 3, 4, 5, 6];
// Функция высшего порядка
$filterEven = function(array $items, callable $callback): array {
$result = [];
foreach ($items as $item) {
if ($callback($item)) {
$result[] = $item;
}
}
return $result;
};
// Анонимная функция-предикат
$isEven = fn($n) => $n % 2 === 0;
$evenNumbers = $filterEven($numbers, $isEven);
// Результат: [2, 4, 6]
print_r($evenNumbers);
?>
Пример: фабрика функций (замыкание с состоянием)
<?php
function createMultiplier(float $factor): callable {
// Замыкание "запоминает" $factor
return fn($number) => $number * $factor;
}
$double = createMultiplier(2);
$triple = createMultiplier(3);
echo $double(5); // 10
echo $triple(5); // 15
?>
Замыкания в PHP позволяют захватывать переменные из внешней области видимости через use (в PHP 7.4+ можно использовать стрелочные функции fn). Это основа для построения каррирования и частичного применения.
2. Каррирование и частичное применение
Каррирование — это преобразование функции от нескольких аргументов в последовательность функций от одного аргумента. Частичное применение — фиксация части аргументов функции. В PHP нет встроенной поддержки, но это легко реализовать.
Пример: ручное каррирование
<?php
// Обычная функция
function add(int $a, int $b): int {
return $a + $b;
}
// Каррированная версия
$curriedAdd = function(int $a): callable {
return function(int $b) use ($a): int {
return $a + $b;
};
};
$addFive = $curriedAdd(5);
echo $addFive(3); // 8
?>
Пример: универсальная функция каррирования
<?php
function curry(callable $fn, int $arity = null): callable {
$reflection = new ReflectionFunction($fn);
$arity = $arity ?? $reflection->getNumberOfRequiredParameters();
$curry = function(...$args) use ($fn, $arity, &$curry) {
if (count($args) >= $arity) {
return $fn(...$args);
}
return function(...$nextArgs) use ($fn, $args, $arity, $curry) {
return $curry(...array_merge($args, $nextArgs));
};
};
return $curry;
}
$add = function(int $a, int $b, int $c): int {
return $a + $b + $c;
};
$curriedAdd = curry($add);
$result = $curriedAdd(1)(2)(3); // 6
echo $result;
?>
Каррирование особенно полезно при композиции функций и создании специализированных версий общих функций.
3. Композиция функций и функциональные паттерны
Композиция функций — это создание новой функции путем последовательного применения нескольких функций. Результат одной функции передается на вход следующей.
Пример: композиция "слева направо" (pipe)
<?php
function pipe(callable ...$fns): callable {
return function($input) use ($fns) {
$result = $input;
foreach ($fns as $fn) {
$result = $fn($result);
}
return $result;
};
}
$trim = fn(string $s): string => trim($s);
$upper = fn(string $s): string => strtoupper($s);
$addExclamation = fn(string $s): string => $s . '!';
$process = pipe($trim, $upper, $addExclamation);
echo $process(' hello world '); // HELLO WORLD!
?>
Паттерн "Maybe" (функтор)
Функтор — это контейнер, который реализует метод map. В PHP это можно реализовать для обработки значений, которые могут быть null.
<?php
class Maybe {
private $value;
private function __construct($value) {
$this->value = $value;
}
public static function just($value): self {
return new self($value);
}
public static function nothing(): self {
return new self(null);
}
public function map(callable $fn): self {
if ($this->value === null) {
return self::nothing();
}
return self::just($fn($this->value));
}
public function getOrElse($default) {
return $this->value !== null ? $this->value : $default;
}
}
// Пример использования
$result = Maybe::just(' PHP ')
->map('trim')
->map('strtoupper')
->getOrElse('default');
echo $result; // PHP
$result2 = Maybe::nothing()
->map('trim')
->map('strtoupper')
->getOrElse('fallback');
echo $result2; // fallback
?>
Паттерн "Either" (монада)
Монада Either представляет собой вычисление, которое может завершиться успешно (Right) или с ошибкой (Left). Это альтернатива исключениям.
<?php
class Either {
private $isRight;
private $value;
private function __construct($value, bool $isRight) {
$this->value = $value;
$this->