Skip to content

Instantly share code, notes, and snippets.

@Crell

Crell/enums.php Secret

Last active August 30, 2020 23:06
Show Gist options
  • Save Crell/dc25cd173b6195af3940299651c28f5a to your computer and use it in GitHub Desktop.
Save Crell/dc25cd173b6195af3940299651c28f5a to your computer and use it in GitHub Desktop.
Just noodling
<?php
// This is an Enum.
enum Direction {
// This is an Enum Member.
// Specifically, these are Unit Members, because they have no associated data.
case North;
case South;
// Like class properties can be single-lined, so can Unit Members.
case East, West;
}
// Unqualified Enum Members are never permitted.
// This may sometimes be a bit more verbose, as in this case,
// but is probably better for the parser ad overall understandability.
function move(Direction $dir = Direction::North): string
{
// Because Unit Members are singletons, they are always === to themselves.
// Thus match() works on them as is with no further changes.
return match ($dir) {
Direction::North => "forward",
Direction::South => "backward",
Direction::East => "right",
Direction::West => "left",
};
// If the value being matched is an enum, you can explicitly toggle on exhausive checks:
return match enum ($dir) {
Direction::North => "forward",
Direction::South => "backward",
Direction::East => "right",
};
// This will error out, as PHP knows it should be matching an enum and therefore be
// exhaustive. That allows us to not have to make match() magically smart about what
// it's matching, which sounds hard to me.
}
function list_moves()
{
// Direction::list() is a static method. I realized that getIterator() only makes sense
// on objects, and Direction is not an object. So we need a special method.
// Part of me isn't sure we even need this, frankly.
foreach (Direction::list() as $direction) {
// By default, prints the name of the Enum Member as a string.
// So one of North, South, East, West.
// I know Anthony argued that it should have an explicit value() method,
// but __toString is well established and Python uses its equilvalent.
print $direction;
}
}
// Another possibility is to make Enums have to opt-in to being enumerable, and then
// parse fail if any of the members take discriminated values.
#[Enumerable]
enum Direction { case North, South, East, West; }
// This would parse fail.
#[Enumerable]
enum Distance {
case Kilometers(int $num);
case Miles(int $num);
}
// Enums can have fixed Associated Values connected with them. In that case, the Member is still a singleton, and thus
// === still holds, and it is iterable (in whatever way we do that, as above).
// There are two ways to make it possible to read the associated value: Pattern matching or requiring named parameters
// in the declaration. I've shown both options here:
// Pattern matching.
enum Suit {
case Hearts('H');
case Diamonds('D');
case Clubs('C');
case Spades('S');
public function color() {
return match ($this) {
Suit::Hearts, Suit::Diamonds => 'Red',
Suit::Clubs, Suit::Spades: 'Black'
};
}
}
match enum ($suit) {
Suit::Hearts($a) => $a,
Suit::_($a) => $a, // Means any Member of Suit.
}
enum Tree {
// This implicitly puts a public $val on an instance of Tree::Leaf.
// It can be made private by using Leaf(private $val), compact-constructor style.
case Leaf(protected $val) {
// I'd love to make this a short function syntax. Separate RFC.
public function fmap(callable $f)
{
return $f($this->val);
}
};
// Ideally, enums should be able to have recursive values. Off hand I see no
// reason why this wouldn't be straightforward.
case Node(Tree $left, Tree $right) {
// In practice, whether a method is best implemented separately in each Member or
// as a single method with match() is up to the user. I'd likely recommend "single
// method if it's all one-liners, separate methods if they are too big for the single-line
// requirement of match()."
public function fmap(callable $f): Tree
{
// This poses an interesting question. Do you create a new instance of an enum with `new`,
// or is it necessarily a static constructor? And if a static constructor, do we
// allow multiple static constructors for a single Enum Member, or do we call that
// "Use a damned class, buddy" territory? My thinking is that there's a single static method
// and if you need anything else, use a class.
return Tree::Node($f($this->left), $f($this->right));
}
};
// Forces both Enum Members to have this method. I am still undecided about how tightly to control
// method consistency between Members.
abstract public function fmap($callable $f);
public function value()
{
return match($this) {
Tree::Leaf => $this->value,
Tree::Node => [...$left->value(), ...$right-value()],
};
}
}
// For fun, here's what it would look like with all comments removed and all possible shortening.
enum Tree {
case Leaf(protected $val) {
public function fmap(callable $f) => $f($this->val);
};
case Node(Tree $left, Tree $right) {
public function fmap(callable $f) => Tree::Node($f($this->left), $f($this->right));
};
abstract public function fmap($callable $f);
public function value() => match($this) {
Tree::Leaf => $this->val,
Tree::Node => [...$left->value(), ...$right-value()],
};
}
enum Optional {
// This is a Unit Member.
case None;
// This is a Member with Associated Value.
case Some(private $val);
public function value(): mixed
{
return match ($this) {
Optional::None => throw new Exception(),
Optional::Some => $this->val,
};
}
// Note that the return type can be the Enum itself, thus restricting the return
// value to one of the enumerated types.
public function fmap(callable $f): Optional
{
return match ($this) {
Optional::None => $this,
Optional::Some => Optional::Some($f($this->val)),
};
}
// I would still love to turn this into a magic method with dedicated operator,
// but that's a separate issue.
// A return type of "static" means Optional in this case. If the method were defined inside
// Some instead, it would mean Some.
public function bind(callable $f): static
{
return match ($this) {
Optional::None => $this,
Optional::Some => $f($this->val),
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment