Занятное мини-интервью с основными контрибьюторами PHP 8

    Несколько недель назад русскоязычное PHP-сообщество проводило стрим по случаю выхода мажорной версии языка. По ходу трансляции зрители могли задать вопрос Никите Попову и Дмитрию Стогову, — а в конце те подключились и ответили на часть из них (остальные ответы мы опубликуем письменно, просто не успели уложить почти 100 вопросов в 40 минут — следите за постами pronskiy).



    Вы можете посмотреть видеоверсию интервью тут.

    Часть ответов уже разлетелась по чатам в виде цитат («Я все языки не люблю, но меньше других — Rust», «Когда вcе заговорили о PHP++, я задумался о PHP+-»), а остальные яркие моменты мы решили сложить в этот пост.

    Каждый подзаголовок — это кратко переформулированный вопрос. Местами ответы приведены в усеченном виде, но без потери смысла.

    Про нативные enum’ы в PHP


    Никита Попов: Надеюсь, они будут в PHP 8.1: по крайней мере, есть план, как это будет выглядеть, и люди, которые над этим работают. Изначально будут достаточно простые, стандартные enum, которые будут имплементированы как специальные объекты. Каждый член enum — это отдельный класс.

    Дмитрий Стогов: Зачем для этого классы? Почему не взять enum как набор констант и объединить их в один тип?

    Никита Попов: Классы — чтобы можно было их как-то типизировать. Если enum – это числа 1, 2 ,3, то нельзя отличить числа от членов enum. Думаю, возможность типизации это главное, если ее нет, то можно просто использовать класс с константами.

    Мы хотели бы все-таки, чтобы тип проверялся. Например, есть какой-то enum параметр, ты туда посылаешь просто integer или string — и тогда получаешь ошибку.

    А не сделать ли PHP компилируемым?


    Дмитрий Стогов: Никто не мешает сделать компилируемым язык с динамической типизацией. Просто он будет работать точно так же, как интерпретатор: проверять, какой у нас тип, и иметь ветки кода для каждого возможного типа.

    По сути, у нас есть режим function для JIT, который можно рассматривать как ahead of time компилятор. Если включите его, то все, что загружается, будет сразу компилироваться. Но большого смысла я не вижу: вам все равно потребуется вся виртуальная машина, все библиотеки. Хотя, можно как в GraalVM – cделать какой-то урезанный пак.

    Но это большая работа, а увеличения производительности не будет. Tracing JIT показывает лучшие результаты, а он возможен только на этапе выполнения.

    Если вы заинтересованы не в производительности, а хотите код скомпилировать и давать людям только бинарник, чтобы они код не видели, – это другая задача. Я не вижу смысла делать open source продукт для людей, которые будут зарабатывать деньги, скрывая свои сорцы.

    Перспективы асинхронности в PHP


    Дмитрий Стогов: Большого выигрыша от асинхронности для всех в PHP я не вижу: это сложность, это очень узкое применение для определенного круга задач.

    Добавить async/await – нет никаких проблем, это синтаксический сахар. Добавить файберы и корутины? В принципе, думаю, не так уж сложно.

    Вопрос — кто этим займется и доведет ли он это до ума.


    Я готов помогать.

    Никита Попов: Там как раз кто-то опять работает над этой темой. Но основная проблема в куче синхронного кода: это же причина того, почему в том же Китае так популярен Swoole, который заменяет PDO и так далее версией, которая работает асинхронно. Но я как-то не совсем знаю, хотим ли мы это иметь на уровне ядра. Потому что это точно сделает все более сложным.

    Дмитрий Стогов: Вероятно, нам потребуется редизайнить stream layer. Если с ним разобраться, то все остальное будет проще. Анатоль Бельский пытался за это браться, но, потратив некоторое время, решил: «Ну его нафиг».

    Есть уже планы, что добавить в 9-ку?


    Никита Попов: Скорее, хотелось бы многое убрать. Например, reference если убрать, то все станет намного проще.

    Дмитрий Стогов: Преобразование типов налету, сравнение строки с числом — вот от этого неплохо уйти уже в PHP 9, допустим.

    Есть и мысли, что добавить. Например, вместо include’ов и require было бы хорошо сделать систему модулей настоящих, чтобы было понятно, какие классы и переменные откуда у нас приходят.

    Это было бы очень круто, но это гигантская работа: просто понять, как все это должно выглядеть даже — в Jave модули выглядят ужасно, на мой взгляд. В Python… можно было бы сделать что-то такое, но с учетом, что у нас все динамично. Но может быть где-то есть что-то лучше.

    Есть еще один момент, для меня достаточно интересный.

    Если сейчас посмотреть на боттлнеки в PHP-парсере, например, это все обращения к массивам.

    В JavaScript можно встретить такое: единый тип массив, но в многих случаях он может работать более оптимально, если его представлять тем или иным способом. То есть это адаптивный массив. Например, если есть массив целых чисел, то его можно так и представлять. Но как только туда вставляется что-то другое, он должен преобразоваться во что-то другое.

    Это поможет, если мы обрабатываем матрицу какую-то на PHP: она будет плоской, как в C это представлено. Соответственно, работать с ней будет намного быстрее, особенно если мы используем это в JIT. Ничего сложного в этом нет, все «закладки» для этого уже заложены.

    Для пользователя при этом ничего не изменится, просто будет работать быстрее.

    Сейчас много реализаций PSR-7. Может вам сделать что-то одно и затащить в ядро?


    Никита Попов: Я считаю, что в ядро надо затаскивать вещи, которые важны для производительности. Это не тот случай.

    Думаю, лучше, если вопросы request-response будут решаться в userland, — там можно соревноваться, делать какие-то изменения. Если мы загрузим это в ядро, тогда это навсегда: а если выяснится, что там что-то не то, сложно будет всем.

    p.s. Спасибо Роману Пронскому за помощь в редактуре расшифровки. Читайте продолжение интервью, которое осталось за кадром стрима, в одном из следующих постов.
    Skyeng
    Крутейшая edtech-команда страны. Удаленная работа

    Comments 24

      –3
      enum’ы

      Самая бесполезная вещь в том виде, в котором это предлагается. Ничем, по сути, от классов не отличается, только все усложняет.
        +4

        Вы просто не понимаете зачем это надо, а это шаг в сторону реализации алгебраического типа данных. Если правильно сделают, то вы сами через некоторое время будете пользоваться этой фичей повсеместно (по крайней мере опыт Rust это демонстрирует).

          0
          Вы просто не понимаете зачем это надо, а это шаг в сторону реализации алгебраического типа данных.

          Я вот тоже совершенно не понимаю, зачем это надо. Мантры «шаг в сторону реализации алгебраического типа данных» («алгебраического типа данных» произносится шепотом и с придыханием) — тоже нифига не достаточно. Алгебраический тип данных сам по себе — как артефакт — никакой ценности не представляет.


          Опыт rust — это лучше, чем ничего, но бесконечно мало, для того, чтобы делать такие заявления. Сопоставление с образцом для извлечения данных — это уже теплее, но алгебраические типы тут ни при чем (так умеет даже физический триод, и то, что алгебраические типы — тоже, пока не делает их лучше). Гошная кондовая проверка на ошибку — тоже сопоставление с образцом, в какой-то мере. switch в js.


          Так что (если вы действительно понимаете, что в них хорошего) — было бы неплохо лицезреть пример — без баззвордов и апелляций к конкретным языкам. Потому что вот опыт лиспа говорит о другом, смолтолка — о третьем, а уж опыт эрланга (где вообще типов нет, и оттого все прямо прекрасно) — вообще об обратном.

            +2

            Конечно речь об увеличении строгости типизации, в эту сторону двигается PHP.


            Алгебраический тип данных — это довольно простая штука: у вас в языке обычно есть записи (то есть структуры или классы) — и это тип-произведения (типы в записи объединяются образуя новый тип и каждый объект данного типа содержит значения сразу всех объединенных типов). Если его дополнить тип-суммой — то есть вариантом, типом-перечислением других типов (среди которых могут быть опять типы-суммы или типы-произведения), когда объект типа-перечисления имеет значение одного из указанных типов, то получим АТД.


            Вот предлагаемый enum и есть тип-сумма, то есть перечисление других типов:


            enum Color {
                case Red;
                case Orange;
                case Yellow;
                case Green;
                case Blue;
                case Indigo;
                case Violet;
            }

            Аналог на Rust:


            enum Color {
                Red,
                Orange,
                Yellow,
                Green,
                Blue,
                Indigo,
                Violet,
            }

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


            // PHP
            $color = Color::Yellow;

            // Rust
            let color = Color::Yellow;

            Теперь, в качестве вариантов могут быть не только единичные типы (без полей), но и типы-произведения:


            // PHP
            enum Color {
                case Red;
                case Orange;
                case Yellow;
                case Green;
                case Blue;
                case Indigo;
                case Violet;
                case Rgb(public $r, public $g, public $b);
            }

            // Rust
            enum Color {
                Red,
                Orange,
                Yellow,
                Green,
                Blue,
                Indigo,
                Violet,
                Rgb {
                    r: u8,
                    g: u8,
                    b: u8,
                }
            }

            То есть, теперь вы цвет можете задать еще и покомпонентно:


            // PHP
            $color_a = Color::Yellow;
            $color_b = Color::Rgb(r: 100, g: 25, b: 25);

            // Rust
            let color_a = Color::Yellow;
            let color_b = Color::Rgb { r: 100, g: 25, b: 25 };

            Фактически вариант Rgb — это отдельная структура, и в Rust ее действительно можно отделить:


            struct Red;
            
            struct Orange;
            
            struct Rgb {
                r: u8,
                g: u8,
                b: u8,
            }
            
            enum Color {
                Red(Red),
                Orange(Orange),
                Yellow,
                Green,
                Blue,
                Indigo,
                Violet,
                Rgb(Rgb)
            }
            
            let rgb = Rgb { r: 100, g: 25, b: 25 };
            let color = Color::Rgb(rgb);

            Это чтобы было понятно, что имена вариантов — это просто метки типа Color вводимые для некоторых других типов, которые тип-перечисление в себе объединяет.


            Теперь, для типа Color можно создать общие методы, которые будут присутствовать во всех его вариантах:


            // PHP
            enum Color {
                case Red;
                case Orange;
                case Yellow;
                case Green;
                case Blue;
                case Indigo;
                case Violet;
                case Rgb(public $r, public $g, public $b);
            
                public function get_r(): mixed {
                    return match type ($this) {
                        Color::Red => 255,
                        Color::Orange => 255,
                        Color::Yellow => 255,
                        Color::Green => 0,
                        Color::Blue => 0,
                        Color::Indigo => 75,
                        Color::Violet => 139,
                        Color::Rgb => $this->r,
                    };
                }
            }
            
            $colors = [Color::Yellow, Color::Rgb(r: 100, g: 25, b: 25)];
            foreach ($colors as $color) {
                echo $color->get_r();
            }

            // Rust
            enum Color {
                Red,
                Orange,
                Yellow,
                Green,
                Blue,
                Indigo,
                Violet,
                Rgb {
                    r: u8,
                    g: u8,
                    b: u8,
                }
            }
            
            impl Color {
                fn get_r(&self) -> u8 {
                    match self {
                        Color::Red => 255,
                        Color::Orange => 255,
                        Color::Yellow => 255,
                        Color::Green => 0,
                        Color::Blue => 0,
                        Color::Indigo => 75,
                        Color::Violet => 139,
                        Color::Rgb { r, .. } => r,
                    }
                }
            }
            
            let colors = [Color::Yellow, Color::Rgb { r: 100, g: 25, b: 25 }];
            for color in colors {
                println!("{}", color.get_r());
            }

            Как это применять? Существует огромное число случаев, в которых удобно использовать тип-сумму (перечисление) и АТД. Например, вместо булевых флагов для обозначения состояний (которых может быть больше двух). При этом еще в каждом состоянии могут храниться свои наборы данных особого типа. АТД привносит преимущества нестрогой типизации в строго-типизированные языки, не выходя за пределы строгой типизации.

              –3

              Ух. Я столько кода даже на ревью не осилю.


              Конечно речь об увеличении строгости типизации, в эту сторону двигается PHP.

              Неясно, почему это аксиоматично хорошо. Но допустим.


              Алгебраический тип данных — это довольно простая штука [...]

              Я в курсе, что это такое, спасибо.


              Существует огромное число случаев, в которых удобно использовать тип-сумму (перечисление) и АТД.

              Как я уже заметил в том комментарии, на который вы отвечаете, ваши нужды конкретно в этом вопросе покрываются сравнением с образцом. Вы приписываете эти чудо-свойства АТД просто потому, что в АТД оно используется, но это не единственный (и не лучший) вариант его использования.


              вместо булевых флагов для обозначения состояний

              Это смешной аргумент. Есть миллиард ничуть не худших способов сделать то же самое. Фабрика и приватный конструктор в банальной джаве умеют это с 1996 года.


              АТД привносит преимущества нестрогой типизации в строго-типизированные языки, не выходя за пределы строгой типизации.

              Так если у нестрогой типизации есть преимущества, может надо ее и использовать, а не тащить их повсюду?




              Есть правильное решение описанной проблемы (зависимые типы). К сожалению, ни раст, ни хаскель его никогда не увидят, хотя надежда, конечно, умирает последней. Есть рабочее, типа протоколов в эликсире. А есть многословные, и неуклюжие алгебраические типы. Зачем втискивать их в языки, в которых и без них не протолкнешься от синтаксического избыточного многообразия — непонятно (точнее, понятно: по хайпу, но это так себе аргумент).

                +2
                ваши нужды конкретно в этом вопросе покрываются сравнением с образцом

                Это как? Сопоставление не может ограничить множество типов, которое может принимать переменная. Этим занимется тип-сумма.


                Есть миллиард ничуть не худших способов сделать то же самое. Фабрика и приватный конструктор в банальной джаве умеют это с 1996 года.

                напишите конкретную реализацию АТД с помощью фабрики, и я вам покажу, в чем ее проблема.

                  –1
                  Сопоставление не может ограничить множество типов, которое может принимать переменная.

                  Переменная? При чем тут вообще переменная? Переменных в языке может вообще не быть (если не рассматривать хаскелевский синтаксический сахар let in как переменную, конечно) — и в языках с нативными функциями высшего порядка их обычно и нет.


                  напишите конкретную реализацию АТД с помощью фабрики, и я вам покажу, в чем ее проблема.

                  Эка невидаль, это я и сам покажу. Моя мысль (и оратора, стартовавшего ветку) заключалась как раз в том, что АТД вообще не нужны. Ну хорошо, иногда нужны, но не такие, и не в PHP, и не просто ради того, чтобы они были, потому что нравятся трем с половиной людям.

              +1
              а уж опыт эрланга (где вообще типов нет, и оттого все прямо прекрасно)

              Опечатался в названии атома — и всё, актор уже не ловит нужные сообщения, потому что там-то код без опечатки. Очень удобно, да.

                0

                У меня более, чем тридцатилетний опыт разработки, и я ни разу не видел в продакшене код, который сбоит из-за того, что кто-то где-то опечатался (даже в руби, где можно object.send(:method)). Есть миллиарды причин, по которым типы действительно могут в чем-то помочь, но вот этим бессмысленным аргументом с опечаткой неимоверно достали, если честно.

                  +1
                  У меня более, чем тридцатилетний опыт разработки, и я ни разу не видел в продакшене код, который сбоит из-за того, что кто-то где-то опечатался

                  Я бы сказал, что логично, что не видели.

              0

              Справедливости ради, АТД можно строить не только на тип-суммах, но и на тип-объединениях (как в typescript), которые языку с исходно-динамической типизацией подошли бы больше.

              +1
              wiki.php.net/rfc/enumerations_and_adts

              enum OvenStatus {
               
                case Off {
                  public function turnOn() { return OvenStatus::On; }
                };
               
                case On {
                  public function turnOff() { return OvenStatus::Off; }
                  public function idle() { return OvenStatus::Idle; }
                };
               
                case Idle {
                  public function on() { return OvenStatus::On; }
                };
              }
              0
              > Каждый член enum — это отдельный класс.
              Возможно ни чего такого ввиду не имелось, но для тех, кто мимо проходил может звучать странно:
              — создаем какое — то перечисление SomeEnum
              — указываем одним из значений SOME_VALUE
              — раз SOME_VALUE класс, значит можно породить потомка?
                +2

                фишка в том что Enum как в C — никому не нужен, поэтому что это нельзы будет никак контролировать в сигнатуре функции.
                когда у нас набор констант, типа
                class Enum {
                public const VAL_ONE = 1;
                public const VAL_TWO = 2;
                }
                function foo(int $constValue) : void

                мы не можем на тайпхинтингом контролировать что никто не засунет в принимающую функцию любой другой int, максимум указать в phpdoc, который поймет psalm и еще пару анализаторов.


                все хотят чтоб было
                enum Values {
                case ONE = 1;
                case TWO = 2;
                }
                function bar(Values $value) : void

                чтоб никакая сволочь не просунула в функцию, например, значение 65536.


                скорей всего под капотом Никита видит это как порезанный класс без возможности ручного инстанцинирования (а-ля Closure или Generator) и его объекты/наследников, чтоб сохранялась возможность проверки true === Values::ONE instanceof Values

                  +1

                  В вашем примере каждый член enum это экземпляр класса, если я правильно понимаю. А смущает именно фраза "каждый член enum это отдельный класс"


                  Вообще в php давно пользуюсь https://github.com/myclabs/php-enum
                  который как раз позволяет в контроль тайпхинтингом

                    +2

                    Все правильно, каждый член перечисления должен быть отдельным типом. Вот пример на Rust (откуда эту фичу тянут в PHP):


                    enum Foo {
                        Unit,           // Единичный тип
                        First(i32),     // Целое 32-х разрядное число
                        Second(i32),    // Другое целое число
                        Text(String),   // Строка
                        Point { x: f32, y: f32 },   // Тип-произведение (запись)
                    }

                    Каждый вариант имеет свой собственный тип, который может строиться как составной тип на основе существующих типов и который сам существует только в пределах типа-перечисления.

                      0
                      Я не большой знаток rust, но в нем элементами перечисления могут быть просто значения, кортежи и структуры (да поправят меня знатоки раст, если я ошибаюсь).
                      Самые близкие аналоги в пыхе это скаляры, индексированные и ассоциированные массивы.
                      Может стоит обернуть их, чтобы в коде можно было писать, например:
                      enum Foo {
                          Unit,           // Единичный тип
                          First(int),     // Целое 32-х разрядное число
                          Second(int),    // Другое целое число
                          Text(string),   // Строка
                          Point(x: float, y: float),   // Тип-произведение (запись)
                      }
                      
                      Foo $my_var=Foo::Point(x: 1, y: 2);
                      print($my_var.x);
                      

                      и бросать ексепшн, если $my_var не содержит в текущем значении этого свойства?
                      Отдельный синтаксис, отдельная реализация, и точно ни какого влияния на текущую реализацию class-a.
                        0
                        Я не большой знаток rust, но в нем элементами перечисления могут быть просто значения, кортежи и структуры

                        То, что вы называете "просто значения" — это значения дискриминанта, которые есть всегда. То есть, допустим у нас есть перечисление:


                        enum Foo {
                            First(i32),
                            Second(i32),
                        }

                        Оба варианта — это кортежные структуры с анонимным полем типа i32. Сколько места должен занимать тип Foo? Кажется, что 4 байта, но ведь в объекте типа Foo еще нужно хранить информацию о том, какой именно вариант выбран (First или Second). Эта дополнительная информация хранится в так называемом дискриминанте, который добавляет веса типу:


                        println!("{}", std::mem::size_of::<Foo>());  // 8

                        4 дополнительных байта занял дискриминант. Но ведь это просто число! Почему бы не позволить пользователю переопределять его? В случае


                        enum Number {
                            Zero,
                            One,
                            Two,
                        }

                        У нас здесь только единичные типы, которые вообще не занимают места в памяти. Но дискриминант остался. Мы можем его увидеть если скастим вариант перечисления к целочисленному типу:


                        println!("zero is {}", Number::Zero as i32); // zero is 0
                        println!("one is {}", Number::One as i32);   // one is 1

                        А теперь сконструируем enum, состоящий из единичных типов, но с явно указанными значениями дискриминанта:


                        enum Color {
                            Red = 0xff0000,
                            Green = 0x00ff00,
                            Blue = 0x0000ff,
                        }
                        
                        println!("roses are #{:06x}", Color::Red as i32);   // roses are #ff0000
                        println!("violets are #{:06x}", Color::Blue as i32);// violets are #0000ff

                        Вот и вся магия. Типы в перечислении отличаются от обычных типов Rust только тем, что у их вариантов появляется число-дискриминант.

                          –1

                          А потом появляется Option<&T>, и вся эта красивая картина разлетается вдребезги. Я уж молчу о том, что варианты перечислений в Rust не являются типами.

                            0

                            Да нет, не разлетается. Просто сишный указатель логично представлять как тип сумму: один вариант — это ненулевое целочисленное значение, другой вариант — это значение типа null (примерно так и устроено, допустим, в Java: там любая ссылка — это тип-сумма с null). Поэтому Option<&T> мапится на указатель однозначно и дополнительного места под дискриминант не требуется.

                              0

                              Это я к тому, что явно выделенного дискриминанта в Rust enum может и не быть.

                                +1

                                Не то, чтобы, это как-либо портит красивую картинку. Просто в некоторых (NonZero) случаях нет необходимости отдельно тратить память на дискриминант, где всё можно компактно и однозначно представить.

                              0
                              варианты перечислений в Rust не являются типами

                              Да, это правда.

                              0
                              В своем комментарии я имел ввиду что — то такое (псвдокод, максимально близкий к php):
                              # Запись такого вида (можно рассматривать как предложение синтаксиса)
                              enum Foo {
                                  Unit,
                                  SomeValue = 111,
                                  First(int),
                                  Text(string),
                                  Point(x: float, y: float),
                              }
                              # При разборе могла бы разворачиваться в такую реализацию
                              # Возможно, это будет существовать только в виде AST в памяти
                              enum Foo {
                                  private enum FooKind {
                                      Unit,
                                      SomeValue,
                                      ...
                                  } __kind,
                                  # Размер array фиксируется наибольшей длиной списка аргументов функций
                                  private array __value,
                              
                                  public static function Unit(): Foo {...}
                                  public static function SomeValue(int v): Foo {...}
                                  ...
                                  public static function Point(x: float, y: float): Foo {...}
                              }
                              

                  Only users with full accounts can post comments. Log in, please.