Стрелочные функции и strlen(...)
До сих пор замыкание было карточкой рецепта, на которую цены записывают через use. Но бывают рецепты в одну строку, например «умножить на курс». Для них не заводят целую карточку — хватает стикера.
Стикер маленький: на нём помещается одна строка. Зато цены, которые в этой строке упомянуты, стикер переписывает сам, и перечислять их отдельно не нужно. Такой стикер в PHP — стрелочная функция fn.
Сравним анонимную и стрелочную функции, которые делают одно и то же:
<?php
$rate = 2;
$long = function ($n) use ($rate) {
return $n * $rate;
};
$short = fn($n) => $n * $rate;
echo $long(3), ' ', $short(3), "\n"; // 6 6Давайте разберём по шагам:
$long— анонимная функция со спискомuse, фигурными скобками иreturn.$shortделает то же одной строкой. Спискаuseв ней нет, а$rateона всё равно видит.- Оба вызова возвращают
6, иechoпечатает6 6.
Запись fn
Стрелочная функция — короткая анонимная функция, тело которой состоит из одного выражения. Разберём запись fn($n) => $n * $rate по частям:
fn— ключевое слово; в стиле PER Coding Style скобки с параметрами ставят сразу после него, без пробела;($n)— параметры, как у любой функции; типы тоже можно указать:fn(int $n): int => $n * 2;=>отделяет параметры от тела;$n * $rate— тело, одно выражение; его значение функция возвращает сама, поэтомуreturnне пишут.
Стрелочная функция — тоже объект Closure. Её кладут в переменную, передают в параметр callable и вызывают скобками.
Захват без use
Стрелочная функция сама захватывает внешние переменные, которые упоминает её выражение. Захват идёт по значению и в момент создания, как у use ($rate):
$rate = 10;
echo $short(3), "\n"; // 6
$count = 0;
$add = fn() => ++$count;
echo $add(), ' ', $add(), ' ', $count, "\n"; // 1 1 0Давайте посмотрим, что здесь происходит:
$rate = 10;меняет внешнюю переменную, но$shortзахватил копию2и по-прежнему возвращает6.$addувеличивает$count, но только свою копию. Каждый вызов начинает с0и возвращает1.- Внешняя
$countтак и остаётся0.
Здесь картинка со стикером неточна. Цены стикер переписывает сам, но надпись «смотрите на доске» на нём не помещается: захватить переменную по ссылке fn не может. Для счётчика нужна анонимная функция с use (&$count).
Чем стрелочная функция отличается от анонимной — частый вопрос на собеседовании. Ответ:
fnзахватывает внешние переменные сама, аfunctionвидит только перечисленные вuse;fnзахватывает только по значению, аfunctionумеет иuse (&$x);- тело
fn— одно выражение безreturn, а вfunctionможно написать сколько угодно инструкций.
Замыкание из готовой функции
Иногда нужная функция уже есть, например встроенная strlen. С PHP 8.1 из неё можно сделать замыкание записью strlen(...). Три точки в скобках стоят на месте аргументов и означают «не вызывать, а сделать замыкание».
$len = strlen(...);
$up = strtoupper(...);
echo $len('tea'), ' ', $up('tea'), "\n"; // 3 TEA
echo gettype($len), "\n"; // objectРазберём по шагам:
strlen(...)не вызываетstrlen, а возвращает объектClosure, и он ложится в$len.$len('tea')вызываетstrlen('tea')и возвращает3, а$up('tea')возвращаетTEA.echo gettype($len)печатаетobject, как и для любой анонимной функции.
Строка 'strlen' тоже вызываемое значение, но у записи strlen(...) есть преимущество. Если ошибиться в имени, PHP остановит программу сразу, на строке с (...). Строку с опечаткой PHP проверит, только когда её вызовут или передадут в параметр callable.
Частая ошибка
В стрелочную функцию пытаются записать тело в фигурных скобках:
<?php
$double = fn($n) => { // ✗
return $n * 2;
};
echo $double(7), "\n";Parse error: syntax error, unexpected token "{"После => PHP ждёт одно выражение, а инструкции в фигурных скобках выражением не бывают. Из-за ошибки разбора файл не запускается совсем. Исправление — fn($n) => $n * 2, а для тела из нескольких инструкций — анонимная функция с function.
Главное
fn($n) => выражение— стрелочная функция: одно выражение, значение которого она возвращает безreturn.fnсама захватывает внешние переменные из выражения — по значению, в момент создания.- Захватить переменную по ссылке
fnне может: для этого нужнаfunctionсuse (&$x). strlen(...)(PHP 8.1) делает замыкание из готовой функции, не вызывая её.