В PHP 7.4 войдут стрелочные функции (сокращенная запись анонимных функций)

    Голосование по сокращенному синтаксису для функций завершено (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 в подкасте "Цинковый прод", так что не забудьте подписаться.


    Ссылка на RFC

    Поддержать автора
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 66

      +4
      «fn» наверно чтоб совсем на JS не похоже было
        0

        Статью не читай — сразу комментируй!

          0
          В статье недостаточно ясное обоснование: «невозможно реализовать, путаница с элементами массивов в некоторых случаях».
            0
            Кажется вполне очевидно:

            $x = 1;
            $y = 2;
            $arr = [
                ($x) => $x * $y, // дословная копипаста из примера
            ];
            // что мы получим в итоге?
            $arr == [1 => 2]; // сейчас будет этот вариант
            $arr == [0 => callable]; // так хотите?
              –1
              Да, я ожидаю второй вариант.
              А вот от этого $arr = [(($x) => $x * $y)()] — первый.
                +2
                Бред какой-то, если по-вашему это стрелочная функция, то вы здесь вызываете функцию от одного аргумента, не передав ей ни одного. Её результат будет соответствовать ключу 0 в массиве.

                А теперь подумайте и скажите, по какому контексту транслятор сможет отличать пару от лямбды, кроме случаев, когда ключ — константа?
                  –1
                  Ну, хорошо: $arr = [(($x) => $x * $y)($x)].
                  А теперь подумайте и скажите, по какому контексту транслятор сможет отличать пару от лямбды, кроме случаев, когда ключ — константа?

                  А, точно. Пардон, я всё время на ECMAScript перескакиваю.
                    0

                    комментарий был удалён

                  0
                  Почему первый? — По вашему варианту, по идее должно получится:
                  $arr == [0 => 2];

                  Как минимум — это ломает обратную совместимость. Такой вариант нельзя было впихнуть в 7.4 из-за этого.
                    0
                    Потому что я вижу функцию, но не вижу её вызова. Но, да, => в PHP уже занято же.
                      0
                      Всмысле, вы же сами нарисовали две скобочки — вызов функции. Или что вы имели в виду?
                        0
                        А, индекс 0. Да, я это и имел в виду.
                          0
                          Да, только вы уже сами запутались/ошиблись. Вот чтобы избежать такой неприятности, — так делать не будут.
          –3
          Очень заметно, когда разработчики языка стараются сделать что-то лучше, но ночью втайне ходят кодить к любов^w^w на JS. А когда-то PHP был простым скриптовым языком, чем многих и цеплял.
            +4
            А ничего что очень похожий синтаксис используется для лямбд в Java/Scala? Вообще, мне кажется что PHP в очень большой степени вдохновляется именно Java, чего стоит хотя бы Symfony (не скрывается что источник вдохновения — Spring), Doctrine до боли похожая на Hibernate, многие конструкции самого языка (try… catch… finally) и т.д.
              +1
              try catch finally, если я ничего не путаю, ведут свою родословную из C++ как и в JavaScript / Java / тыщи их
                +2
                Если я ничего не путаю, finally в C++ раньше не было (может быть в новых стандартах появился, я не слишком слежу за ними).
              0
              PHP/FI вас тоже цеплял? Он был очень простой и понятный.
              –21
              Мертвому припарки
                –16

                Хоть как-то пытаются воскресить или сделать лучше язык. Это радует, что разработчик языка его не забыли

                  +3
                  Жалко, что не с той стороны подходят. Все сахарка подкидывают. Лучше бы enum добавили и дженериков.
                    +4
                    Вот, кстати, всегда было интересно понять, зачем людям нужны дженерики в языке с динамической типизацией?
                      +4
                      1) в php можно включить строгую проверку типов аргументов функций (проверка в рантайме). Ну и IDE тебе подскажет, где ты неправ
                      2) для читаемости. Т.е. Collection читается хуже, чем Collection<User>
                        0

                        1) При указании типа аргумента/возврата — этот тип жестко прописывается в атрибутах функции на этапе компиляции и проверяется достаточно дешево. Для дженериков придется полностью переделывать механизм проверки, что однозначно не в лучшую сторону повлияет на производительность.
                        IDE же отлично работает с phpdoc комментариями User[] $users.
                        2) Не очень удачный пример для языка с убертипом array


                        Не то, чтоб я был против дженериков, но объем работы для их добавления, по моему скромному мнению, не сопоставим с полезным выхлопом.

                          +1

                          Да мне кажется, дженерики тоже можно на этапе компиляции проверять. Просто запрещать передавать переменные без указания типа. Типизация же, зачем они нужны. Если надо, можно сделать функцию-обертку, которая будет принимать типизированные аргументы и передавать дальше в дженерик. Чтобы обертки не плодить, можно какой-нибудь оператор типизации придумать, типа $x as User или (User)$x, рантайм-проверка будет в нем происходить.

                    +9
                    А он что, мертв? Не то чтобы я любил PHP, но язык очень широко используется и не только в вебе.
                      –20

                      По ходу я оскорбил чувства PHPшников)

                        +6
                        Увы, не оскорбил. Но хабр позиционируется как ресурс для профессионалов и культурных людей. А потому троллинг и вбросы тут не приветствуются. Если у вас есть мнение почему похапэ такой плохой, то есть 2 варианта: написать аргументированное мнение или не писать совсем.
                          –10
                          да таких аргументированных мнений уже столько было написано — в том числе и здесь на хабре. Смысл все это повторять
                            +4
                            Таких неаргументированных мнений и вбросов знаете сколько было написано, в том числе и здесь. Смысл всё это повторять?
                        +5

                        Ну да, фейсбук, википедия, вконтакт, Baidu, tumblr, digg, совсем мертвый. И это если не считать всякие вордпрессы и опенкарты.

                      +6
                      // так сделано в Rust, но читабельность спорна
                      |$x| => $x * $y

                      Спорное утверждение :-)
                        0

                        Ага, вот как сделано в Rust:


                        |x| x * y 
                          0
                          Для php-шника будет просто непривычно это написание, а не то чтобы нечитаемо. Мы привыкли к скобкам обычным.
                          –5
                          Мне кажется с синтаксическим сахаром начинается потихоньку перегиб. Объём синтаксического сахара повышает порог вхождения в язык, которым всегда так подкупал PHP.
                            +4
                            Так-то стрелочные функции — это фича, которую очень долго ждали, поскольку классический монструозный синтаксис анонимных функций ну прям вот раздражал жутко.

                            array_map(function($x) {return $x*$x;}, $arr);

                            против
                            array_map(fn($x) => $x*$x, $arr);

                            Прям глаз радуется.

                            Да и в любом случае это не совсем сахар — таки замыкание по умолчанию.
                              +2
                              array_map(function($x) {
                                  return $x * $x;
                              }, $arr);


                              Честно? — Совсем не раздражает. Это сугубо субъективно, — не имеет смысла обсуждать. Факт в том, что вводя миллион правильных способов сделать одно и тоже — мы получим то, что весь миллион способов будет использоваться. Соответственно для интеграции нового программиста в язык — ему нужно будет учить весь миллион способов.

                              Обратите внимание, мой изначальный комментарий вообще никак не задевает именно стрелочные функции. Я говорю о вреде самой тенденции роста синтаксического сахара в языке. Решения принимаемые голосованием не обязательно являются верными.
                                0

                                Язык развивается, к людям приходит понимание, что некоторые изначальные решения были так себе и можно сделать лучше и удобнее. Необходимость поддерживать обратную совместимость — это бич любого уважающего себя языка, сильно затрудняющий его развитие, но не являющийся поводом совсем его не развивать.


                                Синтаксический сахар, когда он уместен — это крайне полезная и правильная вещь. Посмотрите, например, на успех Kotlin, где сахарок возведен фактически в абсолют. Что касается PHP, то чистого сахара в нем еще поискать надо. Так, на вскидку, только краткий синтаксис массивов вспоминается, скажите еще, что его зря вводили.

                                  –1
                                  Зачем мне говорить, что зря вводили краткий синтаксис массивов? Слово «зря», в этой ветке комментариев, я первый раз употребил только в этом сообщении… Я нигде ничего подобного не утверждал.

                                  Попробуйте прочитать заново мои комментарии. В них написано ровно то, что написано, а не то что вы себе придумали.

                                  Про легаси вы совершенно не попали, — никто старый синтаксис лямбд или массивов не собирается объявлять устаревшим, это были решения не «так себе», как вы выразились.
                                    0

                                    Потому, что это тоже синтаксический сахар.

                                –3
                                Кстати, я чего ворчу. Лучше бы придумали, как foreach со ссылками подружить.
                                Так-то вариант с foreach вполне адекватен:
                                foreach($arr as &$x) $x *= $x;
                                unset($x);

                                Если избавиться от unset, то по количеству символов будет даже короче стрелочной функции. И тут возникнет вопрос, а нужны ли стрелочные функции…
                                  –2
                                  Просто не нужно использовать ссылки в foreach
                              –1
                              // нереализуемо, путаница с получением свойств объекта
                              ($x) -> $x * $y
                              Всегда было интересно, почему в одних языках могут сделать одинаковую работу с внутренностями объекта (независимо от того, указатель это или объект сам), например через точку, а в других языках приходится придумывать несколько разных вариантов?
                              А в данном случае, что, реально может быть свойство с именем `$x * $y`?
                                +2
                                Нет, чтобы было свойство $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);

                                Разве не у всех подобное есть в продакшне? =)
                                  0
                                  чтобы было свойство $x * $y — нужно использовать фигурные скобки
                                  А, ну да, я что-то протупил.
                                    0
                                    Свойство $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
                                0
                                $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, =>, и компактнее код совсем не стал. Нафига козе боян. Простой и понятный язык превращают непонятно во что, только потому что модно, и в других языках есть.
                                  +6
                                  $result = 0;
                                  for ($v = 1; $v < 3; $v++)
                                      $result += $v * 2;
                                  


                                  И чем это лучше чем

                                  $result = 6;
                                  


                                  Вообще ничем, лишний for, ++ и *, и компактнее код совсем не стал. Нафига козе боян. Простой и понятный язык превращают непонятно во что, только потому что модно, и в других языках есть.
                                    0
                                    Именно, что плодить сущности — не всегда хорошо. Можно ведь и так сделать:
                                     Collection::fromOneAndTwo()
                                        ->mapQuadFunctionThenReduceToSum()

                                    Вопрос лишь в том, когда пора остановиться. Нельзя плодить сущности безнаказанно.
                                      0
                                      Так вот и я про тоже, зачем усложнять когда можно упростить. И вообще примеры надо приводить подчеркивающие нужность концепции, а не опровергающие её. только вот есть ли они, эти примеры?
                                    0

                                    Вот опять — вроде отличная идея, а реализация вносит еще больше хаоса в и без того сумбурный язык. Теперь есть два синтаксиса для анонимных функций: один (как я понял) только для выражений, автоматически захватывает scope и использует одно ключевое слово (fn), другой — для утверждений, захватывает явно и использует другое ключевое слово — function. Оккам смотрит на это решение с недоумением.


                                    Еще очень удивило "слишком сложно для текущего парсера". С каких пор это весомая причина для того, чтобы отказаться от хорошего, проверенного решения в дизайне языка?

                                      0
                                      Вот я за это тоже хотел написать коммент, но долистал до Вашего. Чтобы вводить новое ключевое слово, нужны более веские причины, чем ленивость набрать 6 лишних символов («unctio»). Сделали бы уже таки как в JS, но я там не спец, вроде просто (x) => x + 5 или типа того, если так уж париться за короткий синтаксис анонимных функций. Мне увлеченность синтаксическим сахаром кажется манией, желанием делать что-то с языком чисто для видимости. Есть конечно полезные нововведения (отказ от расширения mysql, нормальное наконец разыменование), но вот это вот всё уже онанизм какой-то :D
                                      ИМХО.
                                        0

                                        Там не 6 лишних символов, а больше. Есть же ещё return, фигурные скобки и конструкция use.

                                      0
                                      Может я что-то пропустил, но как быть, если функция многострочная? Или если мне не надо, чтобы она что-то возвращала? Если тело можно обернуть в фигурные скобки, то ОК. А так идея давно назрела, часто проще и быстрее использовать функции типа map/reduce, нежели что-то циклами обходить, но синтаксис передачи функции в качестве аргумента был слишком громоздский.
                                        0
                                        Многострочные функции — старым синтаксисом, если я правильно понимаю.
                                          0
                                          да, пока что старым синтаксисом
                                            0
                                            Вот это самое печальное… Старый синтаксис меня в общем-то не особо напрягал, меня в нем напрягало явно биндить переменные из родительского скоупа, вот с ними периодически все превращалось в полный ад…
                                              +3
                                              Если вам регулярно нужно биндить много переменных из родительского скоупа — я думаю это «запах». Мне обычно приходится биндить не больше 2-3 переменных. И то что они указываются явно — считаю плюсом.
                                        +3
                                        так сделано в языке Hack; но слишком сложно для текущего парсера


                                        Вообще-то нет, LALR всё это позволяет. Аргументация была другой, что это дополнительные нагрузки на lookahead парсинг (т.е. неоднозначность грамматики), тогда как PHP чувствителен к скорости парсинга, а значит профитнее добавлять префикс, вместо суффикса.
                                          0
                                          кто-то вообще в курсе что это за запись?
                                          `fn&($x) => $x;`
                                          объясните плз
                                            0
                                            Returning by reference
                                            0

                                            Жесть, ну и синтаксис.
                                            Напоминает МАКРО-11, где можно было


                                            • mov 10,r0 — помещает в регистр PC+10
                                            • mov #10,r0 — помещает в регистр 10
                                            • mov @10,r0 — помещает в регистр значение по адресу PC+10
                                            • mov @#10,r0 — помещает в регистр значение по адресу 10

                                            и плюс ещё кучу вариантов комбинаций этого с другими символами при переносе из регистра в регистр/память с преинкрементом, предекрементом, постинкрементом и постдекрементом.

                                              0

                                              Довольно логичный синтаксис, кстати. Куда лучше всех этих word ptr [...]. Хотя я бы 10 и #10 поменял местами.

                                                0

                                                Ну пойдёт, не считая того, что регистр справа, а не слева, как в большинстве. Лично моё ИМХО — мнемонику проще в освоении, чем у Z80, ещё пока не придумали :-)

                                                  0

                                                  А i8080 чем не нравится? :)

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое