<?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