Skip to content

Instantly share code, notes, and snippets.

@Crell
Last active August 31, 2020 13:14
Show Gist options
  • Save Crell/58f6ca9c3cdb7f86912adca5bc389f4d to your computer and use it in GitHub Desktop.
Save Crell/58f6ca9c3cdb7f86912adca5bc389f4d to your computer and use it in GitHub Desktop.
Noodling with syntax for pattern matching in PHP
<?php
/*
% defines the start of a pattern literal. What follows is a pattern definition, the specifics of which vary by the type
being matched. For arrays, it's an array with literals and placeholder variables. For an enum, it's the enum name and
either positional or named (depending on what the enum can support) values.
:= means "extract into pattern" and evaluates to true/false depending on if the extraction was successful.
*/
// For arrays:
$arr = [1, 2, 3, 4, 5];
// Returns true, and assigns $x = 3
%[1, 2, $x, 4] := $arr;
// Returns true, and assigns $a = 1 and $b = 2.
%[$a, $b, 3] := $arr;
// Returns false, and nothing is assigned.
%[4, $a] := $arr;
$assoc = ['name' => 'Juan', 'age' => 45, 'job' => 'CEO'];
// Returns true, and assigns $age = 45.
// Note that the order is irrelevant because the comparison is done based on keys.
// The 'job' key is also irrelevant and ignored.
%['age' => $age, 'name' => 'Juan'] := $assoc;
// Returns true, but assigns nothing.
%['job' => 'CEO'] := $assoc;
// A match expression matches using === if => is used, or by pattern if :=> is used. (:=> is called T_SMUG_FACE.)
$assoc = ['name' => 'Juan', 'age' => 45, 'job' => 'CEO'];
match ($assoc) {
// This is an exact === match.
['name' => 'Juan', 'age' => 45, 'job' => 'CEO'] => 'It is him',
// This is a pattern-based match.
%['job' => 'CEO'] :=> 'It is the boss',
// This is a pattern-based match with assignment.
%['job' => 'CEO', 'name' => $name] :=> "The boss's name is $name",
}
// For objects
// Only works on properties visible from the calling scope.
// Not sure I like this.
class Employee {
public function __construct(private string $name, private int $age, private string $title) {}
}
%Employee{ name: $name, age: $age, title: 'CEO' } := $person;
match ($employee) {
// Does $employee === $ceo.
$ceo => 'the boss',
// Matches against the title, assigns the name, ignores the age.
%Employee{ name: $name, title: 'CEO' } :=> "The boss is $name",
// Matches against name, assigns nothing, ignores the rest.
%Employee{ name: 'Juan'} :=> "Found Juan",
};
// It would be nice if patterns can be assigned to variables, but... that could lead to utter weirdness like this.
// I don't know if I like this. Maybe only for non-placeholder-having patterns?
$pattern = %Employee{ name: $name, title: 'Manager' };
foreach ($team as $employee) {
if ($pattern := $employee) {
$names[] = $name;
}
}
// For enums.
enum Distance {
case Kilometers(int $num);
case Miles(int $num);
}
$val = Distance::Miles(500);
// Returns true, and sets $x = 500.
%Distance::Miles($x) := $val;
// Returns false.
%Distance::Kilometers($x) := $val;
%Distance::Miles(100) := $val;
// Returns true but sets nothing.
%Distance::Miles(500) := $val;
match ($val) {
%Distance::Miles(100) :=> 'Too short',
%Distance::Miles($x) :=> "Traveled $ miles",
%Distance::Kilometers($x) :=> "Traveled in kilometers, I don't care how many",
}
// Enums can also match on names, if it's a variable.
// This is mainly useful when an Enum is carrying multiple associated values.
%Distance::Miles(num: $x) := $val;
// If an enum only has associated literals, the named form is not allowed.
// This is an Enum.
enum Direction {
case North('up');
case South('down');
case East('right');
case West('left');
}
$dir = Direction::South;
match ($dir) {
// This does $dir === Direction::North, which would be true as there's only literal
// associated values.
Direction::North => 'the north',
// These pattern match and extract.
%Direction::South($d) :=> $d,
%Direction::East($d) :=> $d,
%Direction::West($d) :=> "Don't go that way.",
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment