PHP Compile Time Generics: да или нет?

от автора

Это небольшой разбор поста от PHP Foundation: Compile time generics: yay or nay?, пропитанный личным мнением.

Я сторонник того, что пыхе родные дженерики не очень то и нужны.
Джентльменских дженериков вполне хватает.
Мы, джентльмены, верим друг другу на слово: написан дженерик в аннотации — прекрасно! Стат. анализ рассудит.

Однако команда PHP Foundation проделала колоссальную работу в вопросе добавления дженериков в PHP и сейчас хочет получить обратную связь от сообщества, прежде чем вливать огромные ресурсы в продолжение темы.

Знаю и понимаю, что некоторым из нас хочется дженериков настолько, что без разницы, как именно, ведь “что-то” уже лучше, чем “ничего”.

image.png

Но давайте попробуем не выкрикивать сразу “ДА!” а сначала разберёмся.

Мономорфизация

Мы привыкли к дженерикам в формате коллекции: есть интерфейс Collection<T>; создаём коллекцию типа Collection<User> и понимаем, что из этой коллекции кроме User нам ничего не вывалится. Для нас, разработчиков, Collection<User> — что-то на уровне метаданных. А что под капотом?

Мономорфизация — это техника компиляции дженериков, при которой создаётся отдельная специализированная версия класса (или функции) для каждого конкретного типа, с которым дженерик используется.
Применительно к нашей коллекции это означает, что в рантайме будут существовать конкретные типы Collection<User>, Collection<Post> и так далее для каждого варианта использования коллекции.

В RFC на текущем этапе заявлена только “ручная мономорфизация”:
разработчику необходимо создавать отдельный класс на каждый подтип коллекции;
записи вида $users = new Collection<User>(); пока не поддерживаются.

interface Collection<T> {}  abstract class BaseCollection<T: Entity> {}  final class Users implements BaseCollection<User> {} final class Posts implements BaseCollection<Post> {} final class Comments implements BaseCollection<Comment> {}

Кажется, мы и сейчас примерно так можем делать 🤔

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

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

Вопросы и ответы

Дженерики в параметрах

Уже возможно, но в простом варианте (без Union Types):

class DataProcessor {     public function __construct(private Repository<UserEntity> $repo) {} }

Вариантность

Базово дженерики подразумеваются инвариантными. Для ковариантности и контрвариантности рассматривается синтаксис из Kotlin и C#:

interface EventProcessor<in Event, out Result, Context> {    public function process(Event $event): Result;    public function updateContext(Context $context): void;    public function getCurrentContext(): Context; }

Вывод типов

class Car<Driver> {     public function __construct(private Driver $driver) {} }  // PHP понимает, что $car имеет тип Car<StudentDriver> $car = new Car(new StudentDriver());

Это сложно и не вписывается в концепт, ведь требует дженерики в рантайме.

Трейты

Как трейты будут в это вписываться пока не ясно. Скорее всего как-то так:

trait Tools<T> {     public function useful(T $param): int { ... } }  class C {     use Tools<Book>; }

однако, подводных камней много.

Перечисления (enum)

Про них опять забыли или просто не написали. А это, поверьте, отдельная головная боль.

Union Types

Можно забыть про Union внутри дженерика Collection<User|Post>, если нам важна производительность. Но когда дженерик — часть составного типа Repository<UserEntity>|null — в будущем возможно.

iterable<T>

Этот вид дженериков относится к дженерикам рантайма.

Функции

Любители фасадов сразу захотят дженерики в функции collect(). Что-ж, теоретически что-то такое должно работать:

function collect<T>(T ...$items): Collection<T> { /* ... */ }  $users = collect<User>(User::fetchAll());

Вложенные дженерики

В статье отсутствует упоминание типов вида Repository<Collection<BlogPost>> и ограничения глубины вложенности.

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

Eval

Ограничения на генерирование дженериков на лету не упоминаются. Вероятно, с этим проблем не будет.

Я уверен, что дай эту имплементацию сообществу, и в Доктрину тут же добавят генерацию классов GenericCollection под каждую ToMany связь. Больше применять этот подход, вроде, негде.

Когда?

Реализация первой части концепта возможна к версии PHP 8.6.

Мои выводы

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

Просто представьте, что весь вендор обмазан дженериками. Разработчику теперь необходимо их заполнять в своём коде; либо нам делают вывод типов в рантайме и прощай производительность.
Или переложим эту ответственность на IDE?

Джентльменские же дженерики не приносят вреда тем, кто не в клубе джентльменов и не пользуется стат. анализом.

Мне кажется, что этот концепт не выйдет из эксперименталки: уж больно много минусов. Но если выйдет, то хотелось бы иметь возможность переключать через что-то вроде declare(generics=1) в стартовом скрипте. Стираемые или отключаемые дженерики — подходящая опция для меня.

Если принимать такой RFC в работу, то только после полного понимания всей дорожной карты: как мы придём к полноценным дженерикам, которые не будут мешать и будут помогать.
Добавление подобного рода функционала — путь в один конец. Назад будет уже не вертануть.

И самое главное в таком деле — не допускать разработчиков Symfony до ядра PHP. Хватило уже сбрасываемых readonly свойств.

Другие мои статьи, не вписывающиеся в формат хабра, можно найти здесь или в Телеграм-канале PHP Fart Time.


Также вам может быть интересно:

Сейчас compile-time дженерики находятся в экспериментальной стадии и работы еще немерено.
А вы проголосовали бы за то, чтобы втащить такие дженерики?

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

yay or nay? (да или нет?)

50%yay (да)2
50%nay (нет)2

Проголосовали 4 пользователя. Воздержались 2 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/934044/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *