[Разработка веб-сайтов, PHP, Программирование] Tagged Unions в PHP (примерно как в Rust)

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
09-Фев-2021 21:30

В предыдущей статье я писал про добавление enums в PHP8.1. Голосование прошло успешно, так что можно считать, что вопрос решенный.
Однако та реализация enums — лишь часть глобального плана. Сегодня мы рассмотрим следующий пункт, tagged unions, по-русски это переводится как "тип-сумма".
Голосования по нему пока не проходило, но предлагается также включить его в PHP 8.1.
Все эти термины "алгебраические типы данных", "тип-сумма" звучат страшно, но на деле всё довольно просто.
Зачем всё это вообще нужно?
Result как в Расте
Если вы писали на языке Rust, то наверняка встречали встроенный enum Result. В Rust, Go и т.д. нет механизма exception, так как в этих языках считается, что явная обработка ошибок гораздо надёжнее. Язык вынуждает тебя явно проработать все варианты событий, а не кидать исключение в надежде, что кто-то наверху знает о нём и умеет правильно обрабатывать. (Не будем здесь холиварить, на тему exceptions vs return type, у каждого своё мнение). Если говорить конкретно про Раст, то результатом вызова функции, которая может породить ошибку, часто делают значение типа Result.
Result состоит из двух вариантов (case-ов в терминологии enum в PHP): Ok и Err. Варианты мы могли бы сделать и с помощью предыдущего функционала enum, или даже констант, но нам нужно возвращать еще и сами значения. Причем, в случае успеха значением может быть строка, а в случае ошибки какой-нибудь другой тип. Например, integer (статус HTTP-ответа).
Как это будет выглядеть в PHP, если голосование будет успешным:
enum Result {
    case Ok(public string $json);
    case Err(public int $httpStatus);
}
function requestApi($url): Result {
    //
}

теперь мы этот ответ можем передать куда-то еще, и знания об ошибке и ее типе никогда не пропадут.
Как я писал в предыдущей статье, enum — это по сути класс, в нём могут быть методы и т.д. В случае тип-суммы методы могут быть как общими на весь enum, так и на конкретный case.
Вот пример реализации монады Maybe (пример из RFC):
Монада Maybe
(В Расте такой тип называется Option)
enum Maybe {
  // This is a Unit Case.
  case None {
    public function bind(callable $f)
    {
      return $this;
    }
  };
  // This is a Tagged Case.
  case Some(private mixed $value) {
    // Note that the return type can be the Enum itself, thus restricting the return
    // value to one of the enumerated types.
    public function bind(callable $f): Maybe
    {
      // $f is supposed to return a Maybe itself.
      return $f($this->value);
    }
  };
  // This method is available on both None and Some.
  public function value(): mixed {
    if ($this instanceof None) {
      throw new Exception();
    }
    return $this->val;
  }
}

Как вы видите, в этом енаме два варианта: Some и None, причем Some имеет привязанное значение, а None — просто None. У каждого из вариантов может быть своя реализация метода bind. И также общий метод value()
В RFC не описано, но вызываться методы должны примерно также
$a = Maybe::Some("blabla");
// или $a = Maybe::None
$a->bind();

Естественно, просто передавать туда-сюда Result или Maybe смысла нет, нужно где-то всё же обработать это дело. Для этого хорошо подойдет pattern matching, на который уже есть RFC, но он сыроватый. Если включить фантазию, то выглядеть это будет примерно так:
$result = requestApi($url);
if ($result is Result::Some {%$json}) {
    // выводим $json
}
if ($result is Result::Err {%$httpStatus}) {
    // выводим $httpStatus
}

или примерно тоже самое, но с оператором match.
Еще пример
Есть куча вещей, которые будет удобнее программировать с tagged unions. Например, если кто-то писал парсер, то знает, что первым этапом идет tokenizer (scanner), который разбивает исходный код на токены. Вот тут прямо очень хорошо ложится: есть некий ограниченный набор видов токенов, который можно поместить в enum. Причем некоторые токены, например, строковый литерал или идентификатор, будут содержать еще и значение. В коде будет всё очень удобно и наглядно. Примерно так:
enum Token {
   case Comma;
   case LeftBrace;
   case RightBrace;
   case StringLiteral(public string $str);
   case Identifier(public string $identifier);
   // и т.д.
}

Что дальше?
Неизвестно, примут этот RFC на голосовании или нет, возможно посчитают, что это переусложнит язык и не нужно. Однако тенденция на усиление типизации, а также на перенятие синтаксических "фишек" из других языков налицо. Если примут tagged unions, то я готов поспорить, примут и pattern matching.
Если вам интересны подобные статьи про разработку, в частности, что будет дальше с паттерн матчингом, подписывайтесь на телеграм-канал Cross Join!
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_php, #_programmirovanie (Программирование), #_php, #_tagged_unions, #_tipsumma (тип-сумма), #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_php, #_programmirovanie (
Программирование
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 05-Окт 22:44
Часовой пояс: UTC + 5