Голосование по сокращенному синтаксису для функций завершено (51 "за", 8 "против").
Было:
$result = array_filter($paths, function ($v) use ($names) {
return in_array($v, $names);
});
Стало:
$result = array_filter($paths, fn($v) => in_array($v, $names));
Подробности под катом
Новый синтаксис такой:
Синтаксис
fn(список_параметров) => возвращаемое_выражение
В сигнатуре стрелочной функции, как и в обычной функции, можно указывать типы, дефолты и прочее
fn(array $x) => $x;
fn(): int => $x;
fn($x = 42) => $x;
fn(&$x) => $x;
fn&($x) => $x;
fn($x, ...$rest) => $rest;
Внимание! Появилось новое ключевое слово fn
, а это означает обратную несовместимость!
Другие (отброшенные) идеи по синтаксису
Рассматривались варианты:
// невозможно реализовать, путаница с элементами массивов в некоторых случаях
($x) => $x * $y
// так можно сделать, но слишком много фигурных скобок, особенно для вложенных функций
{ ($x) => $x + $y }
// так сделано в языке Hack; но слишком сложно для текущего парсера
($x) ==> $x * $y
// нереализуемо, путаница с получением свойств объекта
($x) -> $x * $y
// сейчас парсер это понимает как $x-- > $x*$y
$x --> $x * $y
// так сделано в Rust, но читабельность спорна
|$x| => $x * $y
и некоторые другие
Замыкание переменных
Важно! В отличие от предыдущих версий php, где надо было явно задавать замыкаемые переменные оператором use, стрелочная функция неявно замыкает на себе весь родительский скоуп.
Вот эквивалентные записи:
$y = 1;
$fn1 = fn($x) => $x + $y;
$fn2 = function ($x) use ($y) {
return $x + $y;
};
Переменная $this замыкается точно также, как и любая другая переменная. Если это нежелательное поведение, можно его запретить ключевым словом static.
class Test {
public function method() {
$fn = fn() => var_dump($this);
$fn(); // object(Test)#1 { ... }
$fn = static fn() => var_dump($this);
$fn(); // Error: Using $this when not in object context
}
}
Замыкание переменных в стрелочных функциях происходит по значению (в отличие от языка Go, например). Т.е. изменение переменных внутри функции не приведет к изменению переменной в родительском скоупе.
Выводы
Код стал значительно компактнее, и хотя не такой компактный, как в javascript и некоторых других языках, но всё же писать будет значительно приятнее:
$result = Collection::from([1, 2])
->map(fn($v) => $v * 2)
->reduce(fn($tmp, $v) => $tmp + $v, 0);
echo $result; //6
В отличие от некоторых других языков стрелочные функции в php не поддерживают несколько statements, разделенных через символ ;
, потому что это (по мнению авторов RFC) противоречит идее сокращенного синтаксиса. Возможно это будет пересмотрено в будущем.
Мы обязательно детально обсудим стрелочные функции в php в подкасте "Цинковый прод", так что не забудьте подписаться.
Комментарии (48)
SleepingLion
06.05.2019 01:02+1Очень заметно, когда разработчики языка стараются сделать что-то лучше, но ночью втайне ходят кодить к любов^w^w на JS. А когда-то PHP был простым скриптовым языком, чем многих и цеплял.
Akela_wolf
06.05.2019 09:39А ничего что очень похожий синтаксис используется для лямбд в Java/Scala? Вообще, мне кажется что PHP в очень большой степени вдохновляется именно Java, чего стоит хотя бы Symfony (не скрывается что источник вдохновения — Spring), Doctrine до боли похожая на Hibernate, многие конструкции самого языка (try… catch… finally) и т.д.
inoyakaigor
06.05.2019 17:05try catch finally, если я ничего не путаю, ведут свою родословную из C++ как и в JavaScript / Java / тыщи их
Akela_wolf
06.05.2019 19:02+2Если я ничего не путаю, finally в C++ раньше не было (может быть в новых стандартах появился, я не слишком слежу за ними).
GBK
06.05.2019 10:36Хоть как-то пытаются воскресить или сделать лучше язык. Это радует, что разработчик языка его не забыли
xenmayer
06.05.2019 10:40+2Жалко, что не с той стороны подходят. Все сахарка подкидывают. Лучше бы enum добавили и дженериков.
rjhdby
06.05.2019 12:38+3Вот, кстати, всегда было интересно понять, зачем людям нужны дженерики в языке с динамической типизацией?
varanio Автор
06.05.2019 12:581) в php можно включить строгую проверку типов аргументов функций (проверка в рантайме). Ну и IDE тебе подскажет, где ты неправ
2) для читаемости. Т.е. Collection читается хуже, чем Collection<User>rjhdby
06.05.2019 13:211) При указании типа аргумента/возврата — этот тип жестко прописывается в атрибутах функции на этапе компиляции и проверяется достаточно дешево. Для дженериков придется полностью переделывать механизм проверки, что однозначно не в лучшую сторону повлияет на производительность.
IDE же отлично работает с phpdoc комментариямиUser[] $users
.
2) Не очень удачный пример для языка с убертипом array
Не то, чтоб я был против дженериков, но объем работы для их добавления, по моему скромному мнению, не сопоставим с полезным выхлопом.
michael_vostrikov
07.05.2019 08:12+1Да мне кажется, дженерики тоже можно на этапе компиляции проверять. Просто запрещать передавать переменные без указания типа. Типизация же, зачем они нужны. Если надо, можно сделать функцию-обертку, которая будет принимать типизированные аргументы и передавать дальше в дженерик. Чтобы обертки не плодить, можно какой-нибудь оператор типизации придумать, типа
$x as User
или(User)$x
, рантайм-проверка будет в нем происходить.
Akela_wolf
06.05.2019 11:14+2А он что, мертв? Не то чтобы я любил PHP, но язык очень широко используется и не только в вебе.
GBK
06.05.2019 12:37-4По ходу я оскорбил чувства PHPшников)
xotey83
07.05.2019 11:25+3Увы, не оскорбил. Но хабр позиционируется как ресурс для профессионалов и культурных людей. А потому троллинг и вбросы тут не приветствуются. Если у вас есть мнение почему похапэ такой плохой, то есть 2 варианта: написать аргументированное мнение или не писать совсем.
superyateam
07.05.2019 16:29-6да таких аргументированных мнений уже столько было написано — в том числе и здесь на хабре. Смысл все это повторять
xotey83
07.05.2019 18:03+1Таких неаргументированных мнений и вбросов знаете сколько было написано, в том числе и здесь. Смысл всё это повторять?
Tagire
06.05.2019 14:20+1Ну да, фейсбук, википедия, вконтакт, Baidu, tumblr, digg, совсем мертвый. И это если не считать всякие вордпрессы и опенкарты.
dbelka
06.05.2019 11:10+1// так сделано в Rust, но читабельность спорна
|$x| => $x * $y
Спорное утверждение :-)
Tatikoma
06.05.2019 12:20-1Мне кажется с синтаксическим сахаром начинается потихоньку перегиб. Объём синтаксического сахара повышает порог вхождения в язык, которым всегда так подкупал PHP.
rjhdby
06.05.2019 12:59+3Так-то стрелочные функции — это фича, которую очень долго ждали, поскольку классический монструозный синтаксис анонимных функций ну прям вот раздражал жутко.
array_map(function($x) {return $x*$x;}, $arr);
против
array_map(fn($x) => $x*$x, $arr);
Прям глаз радуется.
Да и в любом случае это не совсем сахар — таки замыкание по умолчанию.Tatikoma
06.05.2019 13:54array_map(function($x) { return $x * $x; }, $arr);
Честно? — Совсем не раздражает. Это сугубо субъективно, — не имеет смысла обсуждать. Факт в том, что вводя миллион правильных способов сделать одно и тоже — мы получим то, что весь миллион способов будет использоваться. Соответственно для интеграции нового программиста в язык — ему нужно будет учить весь миллион способов.
Обратите внимание, мой изначальный комментарий вообще никак не задевает именно стрелочные функции. Я говорю о вреде самой тенденции роста синтаксического сахара в языке. Решения принимаемые голосованием не обязательно являются верными.rjhdby
06.05.2019 14:23Язык развивается, к людям приходит понимание, что некоторые изначальные решения были так себе и можно сделать лучше и удобнее. Необходимость поддерживать обратную совместимость — это бич любого уважающего себя языка, сильно затрудняющий его развитие, но не являющийся поводом совсем его не развивать.
Синтаксический сахар, когда он уместен — это крайне полезная и правильная вещь. Посмотрите, например, на успех Kotlin, где сахарок возведен фактически в абсолют. Что касается PHP, то чистого сахара в нем еще поискать надо. Так, на вскидку, только краткий синтаксис массивов вспоминается, скажите еще, что его зря вводили.
Tatikoma
06.05.2019 14:29Зачем мне говорить, что зря вводили краткий синтаксис массивов? Слово «зря», в этой ветке комментариев, я первый раз употребил только в этом сообщении… Я нигде ничего подобного не утверждал.
Попробуйте прочитать заново мои комментарии. В них написано ровно то, что написано, а не то что вы себе придумали.
Про легаси вы совершенно не попали, — никто старый синтаксис лямбд или массивов не собирается объявлять устаревшим, это были решения не «так себе», как вы выразились.
Tatikoma
06.05.2019 14:40Кстати, я чего ворчу. Лучше бы придумали, как foreach со ссылками подружить.
Так-то вариант с foreach вполне адекватен:
foreach($arr as &$x) $x *= $x; unset($x);
Если избавиться от unset, то по количеству символов будет даже короче стрелочной функции. И тут возникнет вопрос, а нужны ли стрелочные функции…
Revertis
06.05.2019 13:59// нереализуемо, путаница с получением свойств объекта
Всегда было интересно, почему в одних языках могут сделать одинаковую работу с внутренностями объекта (независимо от того, указатель это или объект сам), например через точку, а в других языках приходится придумывать несколько разных вариантов?
($x) -> $x * $y
А в данном случае, что, реально может быть свойство с именем `$x * $y`?Tatikoma
06.05.2019 14:06Нет, чтобы было свойство $x * $y — нужно использовать фигурные скобки. В этом же варианте, — сначала будет получено свойство $x объекта $x, результат которого будет умножен на $y.
И да, это вполне рабочий код:
class Test{ public $test = 2; public function __toString(){ return 'test'; } } $x = new Test; $y = 1; $result = ($x) -> $x * $y; var_dump($result);
Разве не у всех подобное есть в продакшне? =)Revertis
06.05.2019 14:11чтобы было свойство $x * $y — нужно использовать фигурные скобки
А, ну да, я что-то протупил.Tatikoma
06.05.2019 14:15Свойство $x * $y тоже вполне может быть, проблемы будут только с конвертацией объекта в число.
class Test{ public function __get($attr){ return '5'; } public function __toString(){ return '2'; } } $x = new Test; $y = 1; $result = ($x) -> {$x * $y}; // Notice: Object of class Test could not be converted to int var_dump($result); // 5
pin2t
06.05.2019 16:06$result = Collection::from([1, 2]) ->map(fn($v) => $v * 2) ->reduce(fn($tmp, $v) => $tmp + $v, 0);
И чем это лучше чем
$result = 0; for ($v = 1; $v < 3; $v++) $result += $v * 2;
Вообще ничем, лишние слова map, reduce, Collection, fn, лишний синтаксис, fn, =>, и компактнее код совсем не стал. Нафига козе боян. Простой и понятный язык превращают непонятно во что, только потому что модно, и в других языках есть.scalavod
06.05.2019 16:22+3$result = 0; for ($v = 1; $v < 3; $v++) $result += $v * 2;
И чем это лучше чем
$result = 6;
Вообще ничем, лишний for, ++ и *, и компактнее код совсем не стал.Нафига козе боян. Простой и понятный язык превращают непонятно во что, только потому что модно, и в других языках есть.Tatikoma
06.05.2019 18:26Именно, что плодить сущности — не всегда хорошо. Можно ведь и так сделать:
Collection::fromOneAndTwo() ->mapQuadFunctionThenReduceToSum()
Вопрос лишь в том, когда пора остановиться. Нельзя плодить сущности безнаказанно.
pin2t
06.05.2019 19:46Так вот и я про тоже, зачем усложнять когда можно упростить. И вообще примеры надо приводить подчеркивающие нужность концепции, а не опровергающие её. только вот есть ли они, эти примеры?
impwx
06.05.2019 16:48Вот опять — вроде отличная идея, а реализация вносит еще больше хаоса в и без того сумбурный язык. Теперь есть два синтаксиса для анонимных функций: один (как я понял) только для выражений, автоматически захватывает scope и использует одно ключевое слово (
fn
), другой — для утверждений, захватывает явно и использует другое ключевое слово —function
. Оккам смотрит на это решение с недоумением.
Еще очень удивило "слишком сложно для текущего парсера". С каких пор это весомая причина для того, чтобы отказаться от хорошего, проверенного решения в дизайне языка?
vdem
06.05.2019 22:53Вот я за это тоже хотел написать коммент, но долистал до Вашего. Чтобы вводить новое ключевое слово, нужны более веские причины, чем ленивость набрать 6 лишних символов («unctio»). Сделали бы уже таки как в JS, но я там не спец, вроде просто (x) => x + 5 или типа того, если так уж париться за короткий синтаксис анонимных функций. Мне увлеченность синтаксическим сахаром кажется манией, желанием делать что-то с языком чисто для видимости. Есть конечно полезные нововведения (отказ от расширения mysql, нормальное наконец разыменование), но вот это вот всё уже онанизм какой-то :D
ИМХО.mayorovp
07.05.2019 08:41Там не 6 лишних символов, а больше. Есть же ещё return, фигурные скобки и конструкция use.
higgsbison
06.05.2019 19:46Может я что-то пропустил, но как быть, если функция многострочная? Или если мне не надо, чтобы она что-то возвращала? Если тело можно обернуть в фигурные скобки, то ОК. А так идея давно назрела, часто проще и быстрее использовать функции типа map/reduce, нежели что-то циклами обходить, но синтаксис передачи функции в качестве аргумента был слишком громоздский.
SerafimArts
07.05.2019 08:51так сделано в языке Hack; но слишком сложно для текущего парсера
Вообще-то нет, LALR всё это позволяет. Аргументация была другой, что это дополнительные нагрузки на lookahead парсинг (т.е. неоднозначность грамматики), тогда как PHP чувствителен к скорости парсинга, а значит профитнее добавлять префикс, вместо суффикса.
leomrakobes
07.05.2019 14:33кто-то вообще в курсе что это за запись?
`fn&($x) => $x;`
объясните плзvaranio Автор
07.05.2019 15:20Returning by reference
leomrakobes
07.05.2019 15:51спасибо,
положу это здесь может кому понадобится:
www.php.net/manual/ru/language.references.return.php
Vasily_T
«fn» наверно чтоб совсем на JS не похоже было
t_kanstantsin
Статью не читай — сразу комментируй!