Функциональное программирование в PHP: продвинутый уровень
PHP традиционно ассоциируется с императивным и объектно-ориентированным стилями, однако современный язык (особенно с версии 7.4 и выше) предоставляет мощные инструменты для применения функциональной парадигмы. В этой статье мы разберём продвинутые концепции функционального программирования (ФП) в PHP, которые помогут писать более предсказуемый, тестируемый и выразительный код.
1. Чистые функции и неизменяемость данных
В основе ФП лежат чистые функции — функции, которые:
- Зависят только от своих аргументов (не используют глобальные переменные или состояние вне области видимости);
- Не вызывают побочных эффектов (не изменяют внешние данные, не пишут в файлы, не отправляют HTTP-запросы);
- Для одних и тех же входных данных всегда возвращают один и тот же результат.
В PHP для обеспечения чистоты функций активно применяется неизменяемость данных. Вместо мутации существующих массивов или объектов создаются их копии с изменениями.
<?php
// Нечистая функция (использует глобальный массив)
$globalData = [1, 2, 3];
function addElementImpure($value) {
global $globalData;
$globalData[] = $value; // Побочный эффект
return $globalData;
}
// Чистая функция (возвращает новый массив)
function addElementPure(array $data, $value): array {
$newData = $data;
$newData[] = $value;
return $newData;
}
$original = [1, 2, 3];
$modified = addElementPure($original, 4);
// $original остаётся [1, 2, 3], $modified = [1, 2, 3, 4]
?>
2. Замыкания (Closures) и захват переменных
Замыкания в PHP — это анонимные функции, которые могут захватывать переменные из родительской области видимости через ключевое слово use. Это основа для создания функций высшего порядка.
Важно различать захват по значению (по умолчанию) и по ссылке (&use). В контексте ФП предпочтительнее захват по значению, чтобы не нарушать чистоту.
<?php
$multiplier = 3;
// Замыкание, захватывающее $multiplier по значению
$multiply = function ($value) use ($multiplier) {
return $value * $multiplier;
};
echo $multiply(5); // 15
// Пример функции высшего порядка, возвращающей замыкание
function createGreeter(string $greeting): Closure {
return function (string $name) use ($greeting) {
return "{$greeting}, {$name}!";
};
}
$sayHello = createGreeter('Hello');
echo $sayHello('World'); // Hello, World!
?>
3. Каррирование (Currying) и частичное применение
Каррирование — это преобразование функции, принимающей несколько аргументов, в последовательность функций, каждая из которых принимает один аргумент. Частичное применение — фиксация части аргументов для получения новой функции с меньшим числом параметров.
В PHP нет встроенной поддержки каррирования, но его можно реализовать с помощью замыканий.
<?php
// Обычная функция
function add(int $a, int $b, int $c): int {
return $a + $b + $c;
}
// Каррированная версия
$curriedAdd = function (int $a) {
return function (int $b) use ($a) {
return function (int $c) use ($a, $b) {
return $a + $b + $c;
};
};
};
echo $curriedAdd(1)(2)(3); // 6
// Частичное применение с помощью вспомогательной функции
function partial(callable $fn, ...$fixedArgs): Closure {
return function (...$remainingArgs) use ($fn, $fixedArgs) {
return $fn(...array_merge($fixedArgs, $remainingArgs));
};
}
$addFive = partial('add', 2, 3);
echo $addFive(10); // 15 (2 + 3 + 10)
?>
4. Композиция функций и конвейеры
Композиция функций позволяет объединить несколько простых функций в одну сложную, передавая результат одной функции на вход следующей. Это ключевой паттерн для построения читаемых конвейеров обработки данных.
В PHP 8.1 появился атрибут #[Pure], который помогает статическим анализаторам проверять чистоту функций, но для композиции по-прежнему используются ручные реализации.
<?php
// Простые функции
function trimSpaces(string $s): string {
return trim($s);
}
function toUpperCase(string $s): string {
return strtoupper($s);
}
function addExclamation(string $s): string {
return $s . '!';
}
// Функция композиции (слева направо)
function compose(callable ...$fns): Closure {
return function ($initial) use ($fns) {
return array_reduce($fns, function ($carry, $fn) {
return $fn($carry);
}, $initial);
};
}
$processString = compose('trimSpaces', 'toUpperCase', 'addExclamation');
echo $processString(' hello world '); // HELLO WORLD!
?>
5. Функторы и монады: практический пример с Maybe
Функторы — это структуры данных, которые реализуют метод map, применяющий функцию к содержимому. Монады расширяют эту идею методом bind (или flatMap), позволяя цепочки операций без вложенных проверок на null.
Реализация простого монадического типа Maybe в PHP:
<?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 bind(callable $fn): self {
if ($this->value === null) {
return self::nothing();
}
return $fn($this->value);
}
public function getValue() {
return $this->value;
}
}
// Использование
$result = Maybe::just(' PHP ')