Tagged Unions in PHP (similar to Rust)

In the previous article I wrote about adding enums in PHP8.1. The voting was successful, so the issue can be considered settled.







However, that enums implementation is only part of the global plan . Today we will consider the next item, tagged unions, in Russian it translates as "type-sum".







It has not yet been voted on , but it is proposed to include it in PHP 8.1 as well.







All these terms "algebraic data types", "sum-type" sound scary, but in reality everything is quite simple.







Why is all this necessary?







Result as in Rust



If you've written in Rust, you've probably come across the built-in enum Result. In Rust, Go, etc. there is no exception mechanism, since these languages ​​consider explicit error handling to be much more reliable. The language forces you to explicitly work out all variants of events, and not throw an exception in the hope that someone at the top knows about it and knows how to handle it correctly. (Let's not blame here, on the topic of exceptions vs return types, everyone has their own opinion). Speaking specifically about Rust, the result of a function call that can generate an error is often made a Result value.







Result consists of two variants (cases in PHP enum terminology): Ok and Err. We could make variations using the previous enum functionality, or even constants, but we also need to return the values ​​themselves. Moreover, in case of success, the value can be a string, and in case of an error, some other type. For example, integer (HTTP response status).







How it will look in PHP if the vote is successful:







enum Result {
    case Ok(public string $json);
    case Err(public int $httpStatus);
}

function requestApi($url): Result {
    //
}
      
      





now we can transfer this answer somewhere else, and knowledge about the error and its type will never be lost.







As I wrote in the previous article , enum is essentially a class, it can have methods, etc. In the case of a type-sum, methods can be either general for the entire enum, or for a specific case.







Here's an example implementation of the Maybe monad (example from RFC):







The Maybe Monad



(In Rust, this type is called 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.







If you are interested in similar articles about development, in particular, what will happen next with the matching pattern, subscribe to the Cross Join telegram channel !








All Articles