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

Комментарии (48)


  1. Vasily_T
    06.05.2019 00:59
    +2

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


    1. t_kanstantsin
      09.05.2019 00:22

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


  1. SleepingLion
    06.05.2019 01:02
    +1

    Очень заметно, когда разработчики языка стараются сделать что-то лучше, но ночью втайне ходят кодить к любов^w^w на JS. А когда-то PHP был простым скриптовым языком, чем многих и цеплял.


    1. Akela_wolf
      06.05.2019 09:39

      А ничего что очень похожий синтаксис используется для лямбд в Java/Scala? Вообще, мне кажется что PHP в очень большой степени вдохновляется именно Java, чего стоит хотя бы Symfony (не скрывается что источник вдохновения — Spring), Doctrine до боли похожая на Hibernate, многие конструкции самого языка (try… catch… finally) и т.д.


      1. inoyakaigor
        06.05.2019 17:05

        try catch finally, если я ничего не путаю, ведут свою родословную из C++ как и в JavaScript / Java / тыщи их


        1. Akela_wolf
          06.05.2019 19:02
          +2

          Если я ничего не путаю, finally в C++ раньше не было (может быть в новых стандартах появился, я не слишком слежу за ними).


    1. assembled
      07.05.2019 06:58

      PHP/FI вас тоже цеплял? Он был очень простой и понятный.


  1. superyateam
    06.05.2019 10:14
    +1

    Мертвому припарки


  1. GBK
    06.05.2019 10:36

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


    1. xenmayer
      06.05.2019 10:40
      +2

      Жалко, что не с той стороны подходят. Все сахарка подкидывают. Лучше бы enum добавили и дженериков.


      1. rjhdby
        06.05.2019 12:38
        +3

        Вот, кстати, всегда было интересно понять, зачем людям нужны дженерики в языке с динамической типизацией?


        1. varanio Автор
          06.05.2019 12:58

          1) в php можно включить строгую проверку типов аргументов функций (проверка в рантайме). Ну и IDE тебе подскажет, где ты неправ
          2) для читаемости. Т.е. Collection читается хуже, чем Collection<User>


          1. rjhdby
            06.05.2019 13:21

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


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


            1. michael_vostrikov
              07.05.2019 08:12
              +1

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


    1. Akela_wolf
      06.05.2019 11:14
      +2

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


      1. GBK
        06.05.2019 12:37
        -4

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


        1. xotey83
          07.05.2019 11:25
          +3

          Увы, не оскорбил. Но хабр позиционируется как ресурс для профессионалов и культурных людей. А потому троллинг и вбросы тут не приветствуются. Если у вас есть мнение почему похапэ такой плохой, то есть 2 варианта: написать аргументированное мнение или не писать совсем.


          1. superyateam
            07.05.2019 16:29
            -6

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


            1. xotey83
              07.05.2019 18:03
              +1

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


      1. Tagire
        06.05.2019 14:20
        +1

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


  1. dbelka
    06.05.2019 11:10
    +1

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

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


    1. freecoder_xx
      09.05.2019 18:42

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


      |x| x * y 


  1. Tatikoma
    06.05.2019 12:20
    -1

    Мне кажется с синтаксическим сахаром начинается потихоньку перегиб. Объём синтаксического сахара повышает порог вхождения в язык, которым всегда так подкупал PHP.


    1. rjhdby
      06.05.2019 12:59
      +3

      Так-то стрелочные функции — это фича, которую очень долго ждали, поскольку классический монструозный синтаксис анонимных функций ну прям вот раздражал жутко.

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

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

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

      Да и в любом случае это не совсем сахар — таки замыкание по умолчанию.


      1. Tatikoma
        06.05.2019 13:54

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


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

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


        1. rjhdby
          06.05.2019 14:23

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


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


          1. Tatikoma
            06.05.2019 14:29

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

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

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


            1. rjhdby
              06.05.2019 15:18

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


      1. Tatikoma
        06.05.2019 14:40

        Кстати, я чего ворчу. Лучше бы придумали, как foreach со ссылками подружить.
        Так-то вариант с foreach вполне адекватен:

        foreach($arr as &$x) $x *= $x;
        unset($x);

        Если избавиться от unset, то по количеству символов будет даже короче стрелочной функции. И тут возникнет вопрос, а нужны ли стрелочные функции…


        1. VokaMut
          07.05.2019 08:43
          -1

          Просто не нужно использовать ссылки в foreach


  1. Revertis
    06.05.2019 13:59

    // нереализуемо, путаница с получением свойств объекта
    ($x) -> $x * $y
    Всегда было интересно, почему в одних языках могут сделать одинаковую работу с внутренностями объекта (независимо от того, указатель это или объект сам), например через точку, а в других языках приходится придумывать несколько разных вариантов?
    А в данном случае, что, реально может быть свойство с именем `$x * $y`?


    1. 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);

      Разве не у всех подобное есть в продакшне? =)


      1. Revertis
        06.05.2019 14:11

        чтобы было свойство $x * $y — нужно использовать фигурные скобки
        А, ну да, я что-то протупил.


        1. 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


  1. 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, =>, и компактнее код совсем не стал. Нафига козе боян. Простой и понятный язык превращают непонятно во что, только потому что модно, и в других языках есть.


    1. scalavod
      06.05.2019 16:22
      +3

      $result = 0;
      for ($v = 1; $v < 3; $v++)
          $result += $v * 2;
      


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

      $result = 6;
      


      Вообще ничем, лишний for, ++ и *, и компактнее код совсем не стал. Нафига козе боян. Простой и понятный язык превращают непонятно во что, только потому что модно, и в других языках есть.


      1. Tatikoma
        06.05.2019 18:26

        Именно, что плодить сущности — не всегда хорошо. Можно ведь и так сделать:

         Collection::fromOneAndTwo()
            ->mapQuadFunctionThenReduceToSum()

        Вопрос лишь в том, когда пора остановиться. Нельзя плодить сущности безнаказанно.


      1. pin2t
        06.05.2019 19:46

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


  1. impwx
    06.05.2019 16:48

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


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


    1. vdem
      06.05.2019 22:53

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


      1. mayorovp
        07.05.2019 08:41

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


  1. higgsbison
    06.05.2019 19:46

    Может я что-то пропустил, но как быть, если функция многострочная? Или если мне не надо, чтобы она что-то возвращала? Если тело можно обернуть в фигурные скобки, то ОК. А так идея давно назрела, часто проще и быстрее использовать функции типа map/reduce, нежели что-то циклами обходить, но синтаксис передачи функции в качестве аргумента был слишком громоздский.


    1. Akela_wolf
      06.05.2019 21:39

      Многострочные функции — старым синтаксисом, если я правильно понимаю.


      1. varanio Автор
        06.05.2019 21:54

        да, пока что старым синтаксисом


  1. SerafimArts
    07.05.2019 08:51

    так сделано в языке Hack; но слишком сложно для текущего парсера


    Вообще-то нет, LALR всё это позволяет. Аргументация была другой, что это дополнительные нагрузки на lookahead парсинг (т.е. неоднозначность грамматики), тогда как PHP чувствителен к скорости парсинга, а значит профитнее добавлять префикс, вместо суффикса.


  1. leomrakobes
    07.05.2019 14:33

    кто-то вообще в курсе что это за запись?
    `fn&($x) => $x;`
    объясните плз


    1. varanio Автор
      07.05.2019 15:20

      Returning by reference


      1. leomrakobes
        07.05.2019 15:51

        спасибо,
        положу это здесь может кому понадобится:
        www.php.net/manual/ru/language.references.return.php