3v4l.org

run code in 300+ PHP versions simultaneously
<?php $f = function(int $i) { var_dump($i); }; // Some::new() // Some::match() final class Mangling { private $mangle = []; public function __construct(...$args) { foreach ($args as $arg) { if ($arg !== null) { if (is_callable($arg)) { $this->mangle[] = serialize($arg); } else { $this->mangle[] = (string) $arg; } } else { $this->mangle[] = '_'; } } } public static function new(...$args): self { return new self(...$args); } public function implode(string $glue): string { return implode($glue, $this->mangle); } } const _ = null; interface MatchingInterface { public function matches(array $cases): void; } interface DestructureInterface { public function destruct(): array; } trait MatchingTrait { private $mangling; private $values = []; public function __construct(...$values) { $this->mangling = new Mangling(...$values); $this->values = $values; } public static function new(...$values): self { return new self($values); } private static function into(Mangling $mangling): string { return sprintf('%s(%s)', static::class, $mangling->implode(',')); } public static function match(...$values): string { return self::into(new Mangling(...$values)); } private static function unserialize(string $s) { return @unserialize($s); } private function getPatternMatchWith(array $pattern): array { $cp = count($pattern); $mangle = []; $params = []; foreach ($this->values as $index => $value) { if ($index >= $cp) { $params[] = $value; continue; } if (array_key_exists($index, $pattern) && $pattern[$index] !== _) { $pat = $pattern[$index]; if ($pat !== _) { $closure = self::unserialize($pat); if (is_callable($closure) && !$closure($value)) { return ['mangle' => _, 'params' => []]; } } $params[] = $value; $mangle[] = $value; } else { $mangle[] = _; } } return ['mangle' => self::into(new Mangling(...$mangle)), 'params' => $params]; } private static function intoPattern(string $mangling): array { $pattern = sprintf('/%s\((.+)\)/i', static::class); if (preg_match($pattern, $mangling, $matches)) { return array_map(function(string $param) { return trim($param) === '_' ? _ : $param; }, preg_split('/,\s*/', $matches[1])); } return []; } private function intoMangling(array $pattern): string { $output = []; foreach ($pattern as $index => $value) { if ($value !== _ && self::unserialize($value) !== false) { $output[] = $this->values[$index]; } else { $output[] = $value; } } return self::into(new Mangling(...$output)); } public function matches(array $cases): void { foreach ($cases as $mangling => $closure) { $pattern = self::intoPattern($mangling); ['mangle' => $mangle, 'params' => $params] = $this->getPatternMatchWith($pattern); if ($this->intoMangling($pattern) === $mangle) { $closure(...$params); break; } } } public function toString(): string { return sprintf('%s(%s)', static::class, $this->mangling->implode(',')); } public function __toString(): string { return $this->toString(); } } trait DestructureTrait { public function destruct(): array { return $this->values; } } interface ADTInterface extends MatchingInterface, DestructureInterface { } trait ADTTrait { use MatchingTrait; use DestructureTrait; } final class TypeClosure { private $name; private $typecheck; private $value; public function __construct(string $name, callable $typecheck, &$value) { $this->name = $name; $this->typecheck = $typecheck; $this->value = &$value; } public function __invoke($param): bool { $this->value = ($this->typecheck)($param) ? $param : _; return $this->value !== _; } } function typeof(string $name, callable $typecheck, &$value): TypeClosure { return new TypeClosure($name, $typecheck, $value); } function int(&$value = null): TypeClosure { return typeof('int', 'is_int', $value); } function any(&$value = null): TypeClosure { return typeof('any', function() { return true; }, $value); } function string(&$value = null): TypeClosure { return typeof('string', 'is_string', $value); } final class LetBinding { private $destructure; public function __construct(DestructureInterface $destructure) { $this->destructure = $destructure; } public function be(...$closures): bool { $params = $this->destructure->destruct(); foreach ($closures as $index => $closure) { if (array_key_exists($index, $params) && is_callable($closure)) { $param = $params[$index]; if (!$closure($param)) { return false; } } } return true; } } function let(DestructureInterface $destructure): LetBinding { return new LetBinding($destructure); } interface Option { } final class Some implements Option, ADTInterface { use ADTTrait; } $opt = new Some(42, 23); print '#1: '; $opt->matches([Some::match(_, 23) => $f]); print '#2: '; $opt->matches([Some::match(_) => $f]); print '#3: '; if (let($opt)->be(int($x), int($y))) { var_dump($x); var_dump($y); } print '#4: '; if (let($opt)->be(_, int($y))) { var_dump($y); } interface Gender { } final class Male implements Gender, ADTInterface { use ADTTrait; } final class Female implements Gender, ADTInterface { use ADTTrait; } final class Other implements Gender, ADTInterface { use ADTTrait; } function gender1(Gender $gender): void { print '#5: '; $gender->matches([Male::match(string($name), _) => function(string $name) { var_dump($name); }]); } function gender2(Gender $gender): void { print '#5: '; $gender->matches([Male::match(_) => function(string $name) { var_dump($name); }]); } $gender = new Male('Hans', 'Franz'); gender1($gender); gender2($gender);

preferences:
50.92 ms | 402 KiB | 5 Q