xp-framework / compiler

Compiles future PHP to today's PHP.
19 stars 0 forks source link

Tagged unions / sealed classes #107

Open thekid opened 3 years ago

thekid commented 3 years ago

See https://wiki.php.net/rfc/tagged_unions:

// Declaration
enum Distance {
  case Kilometers(public int $km);
  case Miles(public int $miles);
}

// Usage
$walk= Distance::Miles(500);
print $walk->miles; // prints "500"

The famous "Maybe" nomad:

// Declaration
enum Optional {
  case None {
    public function bind(callable $f) { return $this; }
    public function value(): mixed { return null; }
  };

  case Some(private mixed $value) {
    public function bind(callable $f): Optional { return Optional::of($f($this->value)); }
    public function value(): mixed { return $this->value; }
  };

  public static function of($value): self {
    return $value instanceof self ? $value : ($value ? self::Some($value) : self::None);
  }
}

// Usage
$value= Optional::of($record)->bind(fn($r) => $r['distance'] + 1)->value();

These are very similar to Kotlin's sealed classes: In some sense, sealed classes are similar to enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances, each with its own state..

See also https://www.dotnetcurry.com/patterns-practices/1510/maybe-monad-csharp

thekid commented 3 years ago

First implementation idea:

// What the user writes
enum Distance {
  case Miles(public int $miles);
}

// What gets emitted
abstract class Distance implements \TaggedUnion {

  public static function Miles(int $miles): self {
    return new class('Miles', $miles) extends self {
      public $name, $miles;

      private function __construct($name, $miles) {
        $this->name= $name;
        $this->miles= $miles;
      }
    };
  }
}