rfc:sealed_classes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:sealed_classes [2022/03/01 02:56] azjezzrfc:sealed_classes [2022/04/01 08:21] (current) – closed vote azjezz
Line 1: Line 1:
 ====== PHP RFC: Sealed Classes ====== ====== PHP RFC: Sealed Classes ======
   * Date: 2021-04-24   * Date: 2021-04-24
-  * Author: Saif Eddin Gmati <azjezz@protonmail.com>, Joe Watkins +  * Author: Saif Eddin Gmati <azjezz@protonmail.com>, Joe Watkins <krakjoe@php.net> 
-  * Status: Draft+  * Status: Decline
   * Target Version: PHP 8.2   * Target Version: PHP 8.2
   * First Published at: https://wiki.php.net/rfc/sealed_classes   * First Published at: https://wiki.php.net/rfc/sealed_classes
Line 49: Line 49:
   {       {    
     return match(true) {     return match(true) {
-      Result\Success => $result->value(), +      $result instanceof Success => $result->value(), 
-      $result instanceof Result\Failure => throw $result->error(),+      $result instanceof Failure => throw $result->error(),
     }; // no need for default, it's not possible.     }; // no need for default, it's not possible.
   }   }
Line 142: Line 142:
 sealed class Bar permits { } sealed class Bar permits { }
 </code> </code>
 +
 +==== Why not composite type aliases ====
 +
 +Some have suggested that the use of composite type aliases could solve the same problem, such as:
 +
 +<code php>
 +<?php
 +
 +final class Success { ... }
 +final class Failure { ... }
 +
 +type Result = Success | Failure;
 +
 +function foo(): Result { ... }
 +
 +$result = foo();
 +if ($result instanceof Success) {
 +  // $result is Success
 +} else {
 +  // $result is Failure
 +}
 +</code>
 +
 +However, a type alias of N types, is not the same as a sealed class that permits N sub-types, as sealing offers 2 major differences.
 +
 +1. shared functionality
 +
 +Sealed classes feature allow you to implement functionalities in the parent class, such as:
 +
 +<code>
 +<?php
 +
 +sealed abstract class Result permits Success, Failure
 +{
 +  public function then(Closure $success, Closure $failure): Result
 +  {
 +      try {
 +        $result = $this instanceof Success ? $success($this->value) : $failure($this->throwable);
 +
 +        return new Success($result);
 +      } catch(Throwable $e) {
 +        return new Failure($e);
 +      }
 +  }
 +
 +  public function catch(Closure $failure): Result
 +  {
 +    return $this->then(fn($value) => $value, $failure);
 +  }
 +
 +  public function map(Closure $success): Result
 +  {
 +    return $this->then(
 +      $success,
 +      fn($exception) => throw $exception
 +    );
 +  }
 +}
 +
 +final class Success extends Result {
 +  public function __construct(
 +    public readonly mixed $value,
 +  ) {}
 +}
 +
 +final class Failure extends Result {
 +  public function __construct(
 +    public readonly Throwable $throwable,
 +  ) {}
 +}
 +</code>
 +
 +2. the N+1 type.
 +
 +Unlike a type alias, a sealed class is by itself a type.
 +
 +Considering the following code which uses type aliases:
 +
 +<code php>
 +final class B {}
 +final class C {}
 +
 +type A = B|C;
 +</code>
 +
 +When you have a function defined as:
 +
 +<code php>
 +function consumer(A $instance): void
 +{
 +  echo $instance::class;
 +}
 +</code>
 +
 +The output can only be either `"B"` or `"C"`.
 +
 +However, considering the following code which uses sealed classes feature:
 +
 +<code php>
 +sealed class A permits B, C {}
 +final class B extends A {}
 +final class C extends A {}
 +</code>
 +
 +The output of `consumer` could be either `"A"`, `"B"`, or `"C"`, as `A` is a non-abstract class, it is possible to do `consumer(new A())`.
  
 ===== Syntax ===== ===== Syntax =====
  
-Some people might be against introducing a new keyword into the language, which will lead to `sealed` and `permits`+Some people might be against introducing a new keywords into the language, which will lead to `sealed` and `permits`
 not being a valid class names anymore, therefor, a second vote will take place to decide which syntax should be used. not being a valid class names anymore, therefor, a second vote will take place to decide which syntax should be used.
  
Line 179: Line 284:
 trait Corge for Grault, Garply {} trait Corge for Grault, Garply {}
 </code> </code>
 +
 +
 +===== FAQ's =====
 +
 +== Wouldn't a sealed class without permits clauses be considered final? ==
 +
 +No, a sealed class will always have a `permits` clauses, if a sealed class is defined without a `permits` clauses, it's considered
 +a compile error.
 +
 +
 +== Would PHP check if permitted classes exists when loading a sealed class? ==
 +
 +No, when loading a sealed class, PHP would treat just like any other class, and store the permitted types list to check against
 +later when another type tries to inherit from it.
 +
 +== What if the permitted types don't actually extend the sealed type ==
 +
 +Example:
 +
 +<code php>
 +sealed interface A permits B {}
 +
 +class B {}
 +</code>
 +
 +This code would not produce any errors, as another type ( e.g: `C` ) could exist, in which it inherits from both `B`, and `A`, therefor, an instance of `A&B` could still exist.
 +
 +== What if the permitted types don't actually extend the sealed type, and are final ==
 +
 +Example:
 +
 +<code php>
 +sealed interface A permits B {}
 +
 +final class B {}
 +</code>
 +
 +In this case, we would end up with an interface 'A', but with no possible instance of it, however, due to the behavior stated above of only checking permitted types on inheritance and not when loading the sealed type, this is allowed, and is considered a small inconvenience.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 206: Line 349:
  
  
-===== Proposed Voting Choices =====+===== Vote =====
  
 As this is a language change, a 2/3 majority is required. As this is a language change, a 2/3 majority is required.
  
-===== Patches and Tests ===== +Voting started on 2022-03-17 and will end on 2022-03-31.
-Links to any external patches and tests go here.+
  
-If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed.+<doodle title="Accept sealed classes RFC?" auth="azjezz" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
  
-Make it clear if the patch is intended to be the final patch, or is just a prototype.+A Second vote for the preferred syntax choice.
  
-For changes affecting the core language, you should also provide a patch for the language specification.+<doodle title="Which syntax option do you prefer?" auth="azjezz" voteType="single" closed="true"> 
 +   * `sealed` + `permits` 
 +   * `permits` only 
 +   * `for
 +</doodle>
  
 +===== Patches and Tests =====
 +
 +Prototype patch using `for` syntax: https://github.com/php/php-src/compare/master...azjezz:sealed-classes
  
 ===== References ===== ===== References =====
Line 226: Line 378:
   * [[https://kotlinlang.org/docs/sealed-classes.html|Sealed classes in Kotlin]]   * [[https://kotlinlang.org/docs/sealed-classes.html|Sealed classes in Kotlin]]
  
 +===== Changelog =====
 +
 +
 +  * 1.1: added comparison to composite types.
 +  * 1.2: added FAQ's section.
  
rfc/sealed_classes.1646103418.txt.gz · Last modified: 2022/03/01 02:56 by azjezz