Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
--
Larry Garfield
larry@garfieldtech.com
Hi folks. Ilija and I would like to present our latest RFC endeavor,
pattern matching:https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had
this one in-progress for 5 years. :-) (Though it was inactive for many of
those years, in fairness.) Pattern matching was intended as the next
follow up to Enums, as it's a stepping stone toward full ADT support.
However, we also feel it has enormous benefit on its own for simplifying
complex comparisons.This RFC has been through numerous iterations, including a full
implementation rewrite just recently that made a number of features much
easier. We have therefore included two patterns that were previously
slated for later inclusion but turned out to be trivially easy in the new
approach. (Variable pinning and numeric comparison.)Nonetheless, there are two outstanding questions on which we are looking
for feedback.Naturally given the timing, we will not be calling a vote until at least
late January, regardless of how the discussion goes. So, plenty of time to
express your support. :-)
Hi Larry,
Looking decent so far.
match() seems a bit clunky in current state. Is it just your code example,
and you could come with another one?
The match(true) and repeating of $somevar over and over is the clunky part.
Normal match($somevar) .. this is clean and works like switch() so people
are happy adopting it .. whereas match() and match() is, look similar but
you have to use them differently ?
Can you clarify?
Many thanks,
Paul
--
Larry Garfield
larry@garfieldtech.com
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
Hi Larry,
Looking decent so far.
match() seems a bit clunky in current state. Is it just your code
example, and you could come with another one?The match(true) and repeating of $somevar over and over is the clunky part.
Normal match($somevar) .. this is clean and works like switch() so
people are happy adopting it .. whereas match() and match() is, look
similar but you have to use them differently ?Can you clarify?
I think you're misinterpreting the example.
The first block under "match() enhancement" is the syntax we propose to have.
The second block is what it would decompose to, aka, an explanation of what it means. While you could write the second block, you really shouldn't when the first block is available and better.
--Larry Garfield
On Mon, Dec 1, 2025, 9:39 PM Larry Garfield larry@garfieldtech.com
wrote:Hi folks. Ilija and I would like to present our latest RFC endeavor,
pattern matching:https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had
this one in-progress for 5 years. :-) (Though it was inactive for many of
those years, in fairness.) Pattern matching was intended as the next
follow up to Enums, as it's a stepping stone toward full ADT support.
However, we also feel it has enormous benefit on its own for simplifying
complex comparisons.This RFC has been through numerous iterations, including a full
implementation rewrite just recently that made a number of features much
easier. We have therefore included two patterns that were previously
slated for later inclusion but turned out to be trivially easy in the new
approach. (Variable pinning and numeric comparison.)Nonetheless, there are two outstanding questions on which we are
looking for feedback.Naturally given the timing, we will not be calling a vote until at
least late January, regardless of how the discussion goes. So, plenty of
time to express your support. :-)Hi Larry,
Looking decent so far.
match() seems a bit clunky in current state. Is it just your code
example, and you could come with another one?The match(true) and repeating of $somevar over and over is the clunky
part.Normal match($somevar) .. this is clean and works like switch() so
people are happy adopting it .. whereas match() and match() is, look
similar but you have to use them differently ?Can you clarify?
I think you're misinterpreting the example.
The first block under "match() enhancement" is the syntax we propose to
have.The second block is what it would decompose to, aka, an explanation of
what it means. While you could write the second block, you really
shouldn't when the first block is available and better.
Hi Larry,
You are correct. Ignore my comment I thought it was showing the opposite.
match() looks good 👍
Many thanks,
Paul
--Larry Garfield
Hey Larry,
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
Thanks for bringing pattern matching up for discussion again.
I'd like to note that the class-access is very ugly.
// Shorthand
if ($p is Point(:$z, x: 3, :$y)) {
print "x is 3 and y is $y and z is $z.";
}
The RFC gives as reasoning that the colon prefix is needed for support
of positional parameters in ADTs. Sure. That's fine to anticipate these.
But what's not fine is using an inconsistent syntax for variable
bindings across different contexts. In arrays binding is just a bare
variable. In objects it suddenly needs a colon? What.
Also, a colon is very prone to being missed in the future with ADTs.
Point::2D($y, $x) vs Point::2D(:$y, :$x). Means something completely
different, but if you mess up just having the colon there or not, is a
serious problem.
Can we instead find some solution, which satisfies both and still
delivers consistency?
An earlier iteration of the RFC had the following very nice construction:
$p is Point&{ $z, x: 3, $y }
This just worked. It's a Point class, and then it matches the properties
of the object. Nice.
This also works for future ADTs. Move::Forward&{ $amount }. Then, if
there's a desire to actually positionally match an object. Then it's
logical to use a parenthesized expression, for a tuple. I.e.:
$move is Move::Forward($a)
Where $a is assigned the first value passed to Move::Forward.
Similarly for destructuring without class name no longer works:
$json = json_decode($myInput);
if ($json is stdClass(type: "store", :$value)) {
// why do I need to know/specify that it's a stdclass?! I'm just
interested in the properties.
}
vs.
if ($json is { type: "store", $value }) {
//
}
This satisfies the requirements of keep the language clear and intuitive:
- Any standalone variable is bound. No weird colon shenanigans. The
syntax is consistent. - Positional binding is quite intuitively using parenthesis - you
construct the enum with Foo::Bar($var) and you read it back on the right
hand side with Foo::Bar($var). - It naturally allows destructuring without class name.
- It makes it hard to accidentally write something totally different to
what was meant.
(Also, it's likely more intuitive to users from other languages, like
rust, which also has {} for named stuff and () for positional stuff.)
Further this particular syntax works nicely with a future scope of
object destructuring, akin to array destructuring. As an example:
function addVec(Point $p, Vec $v) {
Point(:$px, :$py) = $p; // I already know this is a Point, why do I
need to repeat it. It also looks ugly and quite a bit like a left-hand
function call. Like... assigning something to a returned reference?
// Or would you do {$px, $py} for object destructuring? Well that's
now truly inconsistent.
Vec(:$vx, :$vy) = $v;
return new Point($px + $vx, $py + $vy);
}
vs.
function addVec(Point $p, Vec $v) {
{$px, $py} = $p; // Plain and simple. Perfectly straightforward.
{$vx, $vy} = $v;
return new Point($px + $vx, $py + $vy);
}
I've also heard a consideration about "Foo::Bar & { $var }" being
ambiguous with respect to "is Foo::Bar now a const or an ADT class".
This may be resolved in the VM. I don't consider this a major issue, and
is simply something which can be disambiguated at optimizer-time or
run-time, depending on what type of symbol it is.
I'm deeply unsatisfied by the handling of object properties:
"Note that matching against a property's value implies reading that
property's value", "If the property is uninitialized, an error will be
thrown." and "If the property is undefined and none of the above apply,
it will evaluate to null and a Warning will be issued."
This is wildly inconsistent with arrays:
"Of particular note, the pattern matching approach automatically handles
array_key_exists() checking. That means a missing array element will not
trigger a warning, whereas with a traditional if ($foo['bar'] === 'baz')
approach missing values must be accounted for by the developer manually."
Sure, a pattern match will read an objects property. Just like it reads
an arrays entry.
I assume the goal is "let's warn when an object property is typoed". But
it just makes for two tiers. arrays get key_exists(), properties do not
get property_exists(). I welcome surprises.
From my point of view, pattern matching is an "is" operation. Thus it
ought expressing isset-like semantics. I.e. the approach for arrays is
correct, and should be mirrored to objects.
I definitely think the approach of "let's warn about typos" is laudable,
but consistency is important.
It also means that uninitialized properties forcibly throw. It also has
subtle ordering implications on the semantics, given that the
implementation internally short-circuits. E.g. (assuming something like
"class ResponseOrError { string $type; Exception $e; string $response; }"):
if ($obj is ResponseOrError { type: "error", exception: $e }) { throw $e; }
does not throw if $exception is uninitialized. and $type is not error.
But $obj is ResponseOrError { exception: $e, type: "error" } will
certainly throw.
It further means that there needs to be some internal checked and you
cannot simply write:
if ($obj is ResponseOrError { exception: $e }) { throw $e; }
This is bad design and takes a lot of flexibility, just for being typo-safe.
There are better approaches towards typo-safety, e.g. in future (PHP 9)
we could change isset() and all other similar checks (coalesce and this
proposal) to immediately throw when a property is checked for existence,
whose name does not exist on a class which is not marked
#[\AllowDynamicProperties].
We should make use of that instead of shoe-horning this into this proposal.
Open questions:
-
match() "is" placement:
I prefer match() is {} rather than an "is" inside the construct.
Simpler to me, but I think either choice is fine. -
Positional array enforcement:
It's relatively simple to intentionally get positional arrays via
array_values(). I also don't think it's unexpected. That's just how
PHP's arrays work. Enforcing positional arrays however will be quite
surprising if e.g. an entry was removed:
$a = [1, 2, 3];
unset($a[1]);
if ($a is [1, 3]) {
// huh? It's [1, 2 => 3], not [1, 3].
}
Thanks,
Bob
Hi Bob
I'd like to note that the class-access is very ugly.
...
But what's not fine is using an inconsistent syntax for variable bindings across different contexts. In arrays binding is just a bare variable. In objects it suddenly needs a colon? What.
...
This also works for future ADTs. Move::Forward&{ $amount }. Then, if there's a desire to actually positionally match an object. Then it's logical to use a parenthesized expression, for a tuple. I.e.:
We'd argue that using () and {} to differentiate between positional and named parameters is no more intuitive than a : prefix. To look at preexisting concepts in the language: Named vs. positional arguments aren't determined by the type of braces, but by whether they are preceded by the argument name. (:$param) can be viewed as an extension of (param: $param) where the redundant argument name is dropped, while { $param } looks like something completely different. (Technnically, adding this shorthand to named argument calls would be possible, though we are not proposing it here.)
We also recently discovered that this is exactly the approach Dart takes:
https://dart.dev/language/patterns#destructuring-class-instances
So there is prior art. Also, this is only a question for the shorthand syntax. If using the full version, there is no room for confusion.
Additionally, as noted in the RFC, we ran into issues for {} both when using and omitting &. Without a &, {} is confused for a hook when in the default property value position. With a &, runtime disambiguation is required for ADTs and a class constants, which is inconsistent with the rest of the language. (Unless you can suggest a way to resolve that ambiguity, in which case we can consider it.) More on this topic below.
Positional binding is quite intuitively using parenthesis - you construct the enum with Foo::Bar($var) and you read it back on the right hand side with Foo::Bar($var).
Yes, this is precisely the syntax we had in mind for ADTs. So we're in agreement here.
It naturally allows destructuring without class name.
This is an upside of the split syntax, yes. However, as noted, that led to parsing issues, which is why we moved away from it.
While a bit less visually pleasing, it would be possible to allow _ or *, or even object as a pseudo-class name to indicate "any class." That would be a much simpler solution to the "I don't care about the object type" question. Eg: $p is _(x: 5).
I've also heard a consideration about "Foo::Bar & { $var }" being ambiguous with respect to "is Foo::Bar now a const or an ADT class". This may be resolved in the VM. I don't consider this a major issue, and is simply something which can be disambiguated at optimizer-time or run-time, depending on what type of symbol it is.
PHP is very explicit when it comes to distinguishing member types syntactically. For example, in many languages, foo.bar could access a field, static field, constant, function reference, static function reference, subtype, etc. PHP doesn't do that, it uses $foo->bar, foo::$bar, foo::bar, $foo->bar(...), foo::bar(...), foo\bar, etc. It goes out of its way to make it obvious what kind of member is being accessed. This makes a lot of sense for PHP, because each PHP file is compiled in a fully isolated context where we frequently don't know what the class Foo looks like. This avoids a lot of guesswork for the engine.
There are very few exceptions to this rule, one being Foo::bar(), which is normally a static call but can also be an instance call when Foo is an ancestor of the class of the current instance. This has already caused some issues in the past. We tried to automatically make closures static that don't use $this, which is unsound because of these hidden instance call. We feel it's wise to avoid adding more such cases.
An alternative would be to disallow matching against an ADT's case name entirely, allowing $p is Point & {$x} but not $x is Option::Some & {$val}. The latter would have to be positional only, $x is Option::Some($y). That seems like a rather arbitrary and unexpected restriction, however.
I'm deeply unsatisfied by the handling of object properties:
"Note that matching against a property's value implies reading that property's value", "If the property is uninitialized, an error will be thrown." and "If the property is undefined and none of the above apply, it will evaluate to null and a Warning will be issued."
This is wildly inconsistent with arrays:
The rationale for treating objects and arrays differently is that objects are almost always structured (meaning we know and should be able to rely on which properties it defines), while arrays are frequently not. The obvious exception you already mentioned is stdClass, although how useful or common that is in practice at this point is debatable. (I cannot recall the last time I saw json_decode() called without the flag to use an associative array instead.) stdClass should not be fundamental in shaping how this pattern works. You can also efficiently cast stdClass to array (because that's how stdClass is implemented anyway) to use the array pattern.
That said, this isn't a hill either of us wants to die on, so if the consensus is to swallow such cases and just return false, we will go with that. (Meaning, other people, please weigh in here.)
It also means that uninitialized properties forcibly throw.
With fairly few exceptions, an uninitialized property on an object that is passed back to a caller is a sign of a design flaw. Even if the construction process is multi-step -- as it is in Crell/AttributeUtils or deserializers, factories, ORMs, etc. -- everything is initialized by the time the object is returned to the caller. Leaving properties uninitialized, or unsetting them manually implicitly adds an undocumented "undefined" type to your type union. There's absolutely no indication that accessing a property may be unsafe, but it is. There are niche cases where unsetting properties is useful (e.g. breaking cycles, as you've mentioned privately), but such objects shouldn't escape to users with a half-initialized state. So there's a 99% chance that if the developer pattern matches against an uninitialized property, it's a developer error and should be corrected, not silently suppressed.
The other issue is that $p is Point(:$x) looks like an infallible pattern (assuming $p is an instance of Point) where we just want to extract $x, but if $point->x is uninitialized this pattern would fail. Given this is almost certainly a mistake, it seems better to inform the user about it rather than silently returning false.
E.g. (assuming something like "class ResponseOrError { string $type; Exception $e; string $response; }"):
This is a classic example of when ADTs would be useful.
Enforcing positional arrays however will be quite surprising if e.g. an entry was removed:
$a = [1, 2, 3];
unset($a[1]);
if ($a is [1, 3]) {
// huh? It's [1, 2 => 3], not [1, 3].
}
I think you misunderstood the question here. [1, 2] and [0 => 1, 1 => 2] should always be equivalent in terms of key behavior. What we weren't in agreement about is whether [1, 2] should have additional guarantees about order, i.e. about whether [1, 2] need to appear in that order, or whether [1 => 2, 0 => 1] is also acceptable. Normally, values are accessed by key which makes order mostly irrelevant, but there are cases where the user might expect to see values in a specific order when iterating over the array. Implementing this order-consistency check in a performant way is also somewhat tricky, and it would always add some overhead no matter what.
--Larry Garfield
I would just like to point something out regarding this sentence in
the RFC on the "Variable pinning" section:
This particular syntax was chosen as it is the same as in Ruby, the only other language we know of that has this functionality.
I don't know how helpful this info is, but I'll provide it anyway.
Elixir and Erlang also have the functionality and its syntax is the
same, using the ^ character.
https://hexdocs.pm/elixir/pattern-matching.html#the-pin-operator
Hi
Am 2025-12-10 14:37, schrieb Vinicius Dias:
This particular syntax was chosen as it is the same as in Ruby, the
only other language we know of that has this functionality.I don't know how helpful this info is, but I'll provide it anyway.
Elixir and Erlang also have the functionality and its syntax is the
same, using the ^ character.https://hexdocs.pm/elixir/pattern-matching.html#the-pin-operator
AFAICT this is not quite right. It is supported in Elixir, but not in
Erlang. There is a proposal to add it to Erlang, but the corresponding
PR is still open, which indicates that it is not accepted yet:
https://www.erlang.org/eeps/eep-0055
It's not surprising to me that Elixir supports it, since Elixir is
heavily inspired / influenced by Ruby.
Best regards
Tim Düsterhus
Hi
Am 2025-12-10 14:37, schrieb Vinicius Dias:
This particular syntax was chosen as it is the same as in Ruby, the
only other language we know of that has this functionality.I don't know how helpful this info is, but I'll provide it anyway.
Elixir and Erlang also have the functionality and its syntax is the
same, using the ^ character.https://hexdocs.pm/elixir/pattern-matching.html#the-pin-operator
AFAICT this is not quite right. It is supported in Elixir, but not in
Erlang. There is a proposal to add it to Erlang, but the corresponding
PR is still open, which indicates that it is not accepted yet:
https://www.erlang.org/eeps/eep-0055It's not surprising to me that Elixir supports it, since Elixir is
heavily inspired / influenced by Ruby.Best regards
Tim Düsterhus
Still, it's good information to have. That means two languages using the ^ character, and no one using anything else instead. We went with ^ largely because "well, that's what Ruby does, and it's easy to implement that way." So if it's more of a common standard (the way |> is the standard spelling of pipe), that's another argument to follow suit and not complicate things.
Thanks, Vinicius!
--Larry Tarfield
Hi
Am 2025-12-10 20:06, schrieb Larry Garfield:
Still, it's good information to have. That means two languages using
the ^ character, and no one using anything else instead.
I wouldn't call it "two languages" due to the close relationship of
Elixir and Ruby. It would probably not entirely wrong to say that Elixir
is Ruby running on BEAM. It's somewhat equivalent to saying that two
languages support file inclusion #include, just because C and C++ both
do [1].
Best regards
Tim Düsterhus
[1] Yes, yes. Technically it's the preprocessor that does.
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
--
Larry Garfield
larry@garfieldtech.com
Hi Larry,
Is there a specific reason why less than/etc comparison (and the future scope of ranges) is limited to numerics?
'A' < 'K' comparisons are already in the language, and range() accepts string arguments to produce an array of bytes.
Cheers
Stephen
Hi Larry,
Is there a specific reason why less than/etc comparison (and the future
scope of ranges) is limited to numerics?'A' < 'K' comparisons are already in the language, and
range()accepts
string arguments to produce an array of bytes.Cheers
Stephen
Because neither of us ever use comparisons on strings, as it's not super reliable, so it never came up. :-) We'd prefer to keep it numeric, as the logic otherwise could get confusing. (Personally I'd prefer to even exclude numeric strings, but that ends up being more work.)
--Larry Garfield
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
--
Larry Garfield
larry@garfieldtech.com
Thank you for the RFC and that you included option with match and individual IS’s, I think it would be preferable option not to break the match construction.
Regarding the patterns, is there a way to declare and reuse the patterns? Assign it to a variable and use it as “is $pattern” or something like that. Maybe as a separate class “$pattern->match($var)”.
While it’s not reusable it looks like it has a limited scope to use it, otherwise regular replacements preferred.
--
Best regards,
Dmitrii Derepko.
@xepozz
Regarding the patterns, is there a way to declare and reuse the patterns? Assign it to a variable and use it as “is $pattern” or something like that. Maybe as a separate class “$pattern->match($var)”.
There is an annex document on more speculative extensions linked in the
RFC that mentions this:
https://github.com/Crell/php-rfcs/blob/master/pattern-matching/future.md
I was going to raise the thought of using these patterns to constrain
function parameters (despite the heavy overlap of "$var is type" and
"type $var"), but found that already included in the annex as well.
Thank you for the RFC and that you included option with match and
individual IS’s, I think it would be preferable option not to break the
match construction.Regarding the patterns, is there a way to declare and reuse the
patterns? Assign it to a variable and use it as “is $pattern” or
something like that. Maybe as a separate class “$pattern->match($var)”.
While it’s not reusable it looks like it has a limited scope to use it,
otherwise regular replacements preferred.
Not currently, no. It's something we've discussed, but it's also kinda hard, and I don't believe any other languages do it.
What you could do is just wrap it in a short closure.
$pattern = fn($x) => $x is some|Pattern|null;
$pattern($something);
I'd love to have a way to shorten that, but so far we haven't come up with one. Suggestions welcome, as I'd love to be able to do something like this (obviously not exactly this):
$filtered = array_filter($arr, is Point(x: <10));
--Larry Garfield
$filtered = array_filter($arr, is Point(x: <10));
Btw, it looks incorrect and short closure is preferable here
array_filter($arr, fn($x) => $x is Point())
Or if it works with DFA
array_filter($arr, ? is Point())
If your example illustrated a pattern as an argument I think it’s not valid case, because a pattern is something after “is”? The point, enumerated types or values. And “is” here is an action/function/intention to match left and right operands.
But kotlin style is perfectly matched here
array_filter($arr, { $it is Point() })
--
Best regards,
Dmitrii Derepko.
@xepozz
$filtered = array_filter($arr, is Point(x: <10));
Btw, it looks incorrect and short closure is preferable here
array_filter($arr, fn($x) => $x is Point())
As I said, that's not actually a syntax I'm proposing. Just spitballing that it would be nice to be able to remove the fn($x) => $x boilerplate at some point. But that's not for this RFC; for now, the fact that you can wrap a pattern up into a short closure is sufficient for reuse.
Or if it works with DFA
array_filter($arr, ? is Point())If your example illustrated a pattern as an argument I think it’s not
valid case, because a pattern is something after “is”? The point,
enumerated types or values. And “is” here is an
action/function/intention to match left and right operands.But kotlin style is perfectly matched here
array_filter($arr, { $it is Point() })
Ilija would agree with you, and has been arguing for a { $0 + 3 } type syntax for extra-short closures for a while now. :-) But that's not in scope for the moment.
--Larry Garfield
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
--
Larry Garfield
larry@garfieldtech.com
One thing I didn't see covered: accessing static variables in a class:
class Point {
public static $origin = new Point(0,0,0);
}
How do we pattern match to this? Is it the obvious Point::$origin? Same with class constants?
— Rob
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
https://wiki.php.net/rfc/pattern-matching
You may note the date on the RFC is from 2020. Yes, we really have had this one in-progress for 5 years. :-) (Though it was inactive for many of those years, in fairness.) Pattern matching was intended as the next follow up to Enums, as it's a stepping stone toward full ADT support. However, we also feel it has enormous benefit on its own for simplifying complex comparisons.
This RFC has been through numerous iterations, including a full implementation rewrite just recently that made a number of features much easier. We have therefore included two patterns that were previously slated for later inclusion but turned out to be trivially easy in the new approach. (Variable pinning and numeric comparison.)
Nonetheless, there are two outstanding questions on which we are looking for feedback.
Naturally given the timing, we will not be calling a vote until at least late January, regardless of how the discussion goes. So, plenty of time to express your support. :-)
--
Larry Garfield
larry@garfieldtech.comOne thing I didn't see covered: accessing static variables in a class:
class Point {
public static $origin = new Point(0,0,0);
}How do we pattern match to this? Is it the obvious Point::$origin? Same with class constants?
— Rob
I literally saw it just after sending the email and missed the "undo send" by milliseconds. Sorry for the noise.
— Rob
Hi Larry and Ilija,
Le lun. 1 déc. 2025 à 22:37, Larry Garfield larry@garfieldtech.com a écrit :
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
Thanks for this RFC, it looks really great!
I see that resources are never mentioned. I know that a lot of
resources are being converted to opaque classes in recent versions,
but they still exist. Would it be possible to use the "resource" type
with pattern matching, e.g. $r is resource? I would assume that the
answer is yes because the RFC mentions the support of $foo is iterable.
— Alexandre Daubois
Hi Larry and Ilija,
Le lun. 1 déc. 2025 à 22:37, Larry Garfield larry@garfieldtech.com a écrit :
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
Thanks for this RFC, it looks really great!
I see that resources are never mentioned. I know that a lot of
resources are being converted to opaque classes in recent versions,
but they still exist. Would it be possible to use the "resource" type
with pattern matching, e.g.$r is resource? I would assume that the
answer is yes because the RFC mentions the support of$foo is iterable.
iterable is already a type used elsewhere. resource is not something you can specify anywhere in code right now. Making pattern matching the one place you can actually type check against resource, which is not an inconsistency we want. Also, resources are on their way out, gradually, so we'd just need to remove support from pattern matching again in the future sometime.
In all, it's just not worth the effort and inconsistency.
--Larry Garfield
On Mon, Dec 1, 2025 at 11:39 PM Larry Garfield larry@garfieldtech.com
wrote:
Hi folks. Ilija and I would like to present our latest RFC endeavor,
pattern matching:
Great work, very detailed and well written.
-
There are mentions that any type present in a function parameter
signature and return value is a valid pattern matching.
But are there any plans to extend the pattern-matching-syntax to parameter
types and return values?
Of course, with some restrictions: without variable pinning.And without
variable binding on return type, but that could work on parameter types.
If that's possible or already planned, I think it's worth mentioning it in
the future scope section. -
Maybe some parts of the RFC could be separated to reduce complexity.
Thinking about variable pinning. -
For match() "is" placement, I would go with the second form as it's more
flexible, but I think allowing both forms might be nice if the complexity
is not too high.
Or go only with the second form now, and add the first form later in a
smaller RFC.
--
Alex
Hi folks. Ilija and I would like to present our latest RFC endeavor, pattern matching:
Great work, very detailed and well written.
Thanks!
- There are mentions that any type present in a function parameter
signature and return value is a valid pattern matching.
But are there any plans to extend the pattern-matching-syntax to
parameter types and return values?Of course, with some restrictions: without variable pinning.And without
variable binding on return type, but that could work on parameter types.
If that's possible or already planned, I think it's worth mentioning it
in the future scope section.
See the linked "speculative extensions" document. It's mentioned there, but it hasn't gone beyond "Larry thinks it would be kinda cool to write public int $x is >0. That's definitely out of scope for now, but I'm very open to discussing it in the future.
- Maybe some parts of the RFC could be separated to reduce complexity.
Thinking about variable pinning.
Actually, with the new implementation variable pinning turned out to be stupidly easy. That was a convenient bonus, which is why we included it in the initial RFC. It was originally in future scope, but the diff to add it was like 10 lines or something, so we included it now. The future-scopes that are still in future-scope are all "harder than they look" or "deciding what exactly to do will be a lot of discussion", which is why they're there and not in the base release.
- For match() "is" placement, I would go with the second form as it's
more flexible, but I think allowing both forms might be nice if the
complexity is not too high.
Or go only with the second form now, and add the first form later in a
smaller RFC.
Noted! I think that's one vote for a single is, and one vote for a separate one for each arm. :-)
--Larry Garfield
On Tue, Dec 2, 2025 at 10:09 PM Larry Garfield larry@garfieldtech.com
wrote:
- There are mentions that any type present in a function parameter
signature and return value is a valid pattern matching.
But are there any plans to extend the pattern-matching-syntax to
parameter types and return values?Of course, with some restrictions: without variable pinning.And without
variable binding on return type, but that could work on parameter types.
If that's possible or already planned, I think it's worth mentioning it
in the future scope section.See the linked "speculative extensions" document. It's mentioned there,
but it hasn't gone beyond "Larry thinks it would be kinda cool to write
public int $x is >0. That's definitely out of scope for now, but I'm
very open to discussing it in the future.
That is not exactly what I meant.
Since all types are valid patterns, can't we enlarge the type set to
include more things from the pattern set? in future RFCs.
Something like this: function process(Point(x: 3|4, y: $y) $p): ['a' => int, 'b' => float & >0].
--
Alex
- There are mentions that any type present in a function parameter
signature and return value is a valid pattern matching.
But are there any plans to extend the pattern-matching-syntax to
parameter types and return values?Of course, with some restrictions: without variable pinning.And without
variable binding on return type, but that could work on parameter types.
If that's possible or already planned, I think it's worth mentioning it
in the future scope section.See the linked "speculative extensions" document. It's mentioned there, but it hasn't gone beyond "Larry thinks it would be kinda cool to write
public int $x is >0. That's definitely out of scope for now, but I'm very open to discussing it in the future.That is not exactly what I meant.
Since all types are valid patterns, can't we enlarge the type set to
include more things from the pattern set? in future RFCs.
Something like this:function process(Point(x: 3|4, y: $y) $p): ['a' => int, 'b' => float & >0].--
Alex
Yes, that's also in speculative extensions, under the "Parameter or return guards" section. :-)
I think it would be cool (especially if combined with Rob's type aliases), but that's out of scope for now.
--Larry Garfield
Hi
Am 2025-12-02 21:06, schrieb Larry Garfield:
- Maybe some parts of the RFC could be separated to reduce
complexity.
Thinking about variable pinning.Actually, with the new implementation variable pinning turned out to be
stupidly easy. That was a convenient bonus, which is why we included
it in the initial RFC. It was originally in future scope, but the diff
to add it was like 10 lines or something, so we included it now. The
future-scopes that are still in future-scope are all "harder than they
look" or "deciding what exactly to do will be a lot of discussion",
which is why they're there and not in the base release.
There are different dimensions to complexity. Your reply focuses on just
“implementation complexity”, but there's also “documentation complexity”
or “deciding-how-the-syntax-should-look complexity”. I feel that the
syntax for “variable pinning” specifically has the weakest arguments in
favor of it, with the main argument being “Ruby has it” rather than “it
makes sense”. The other syntax choices either are “obviously correct” or
have good arguments backing the choice.
Best regards
Tim Düsterhus
Hi
Am 2025-12-01 22:36, schrieb Larry Garfield:
Hi folks. Ilija and I would like to present our latest RFC endeavor,
pattern matching:
Thank you. I've already provided some feedback on the PR (and in Ilija's
DMs) after seeing it “show up” in php-src, but I promised to put it
on-list. I'm doing that now. This email is not a full review of the RFC,
just the parts that I already noted. I think I also noted them before in
the previous on-list “pre RFC” discussion.
I'd like to see $foo is $bar with a single top-level variable binding
on the right side disallowed at compile time. This pattern is just an
elaborate way of writing an assignment, thus (almost) never useful, but
possibly confusing to folks coming from Python or just generally
unfamiliar with PHP's pattern matching. I'd be okay with allowing $foo is ($bar) with the explicit parentheses, since this is an established
pattern to indicate “yes, I meant it like this” when using assignments
inside of conditionals, such as while (($row = $statement->fetch()))
as a short form of while (($row = $statement->fetch()) !== false).
I'd like to see required parentheses around “combinator patterns”
(specifically & and |). The RFC currently claims:
While patterns may resemble other language constructs, whatever follows
is is a pattern, not some other instruction.
which is false. “Whatever follows is a pattern, unless it no longer is a
pattern” would be more accurate, which is not very helpful to
intuitively determine the end of a pattern when visually scanning the
code, particularly with the spacing around the combinators that the RFC
suggests.
The main issue is that pattern matching embeds a DSL with its own syntax
inside of PHP, but without having a clear “pattern ends here delimiter”.
I've also looked at other programming languages with pattern matching
and most only support pattern matching at a small number of “special
locations” (e.g. as part of a match() construct or only within a
function signature) and also do not have a concept of “union” or
“intersection” patterns. PHP allows to embed patterns (and the
associated DSL) into arbitrary (complex) expressions, which is not
something that is commonly seen as far as I can tell. C# appears to
support it as well, but the overall syntax for pattern matching is quite
different there, which makes it hard to directly compare it.
The “atomic patterns” as a top-level are fine, since they are either a
single “word” without any spaces or already have clear delimiters (such
as the array pattern).
To give an example: Consider line wrapping within a single pattern (e.g.
because the class names get long or because the pattern matching
expression is deeply indented). The most natural way I can come up with,
without introducing parentheses, is the following:
if (
$foo is Foo(some: "stuff", :$here)
& Bar(with: "more_stuff")
) { }
I find it incredibly non-obvious that & Bar still belongs to the
pattern. And with just a single non-whitespace character change it
becomes something entirely different, but also valid:
if (
$foo is Foo(some: "stuff", :$here)
&& Bar(with: "more_stuff")
) { }
I believe it is not unlikely that the former is interpreted as a typo
for the latter by someone inexperienced with pattern matching.
For $foo is $bar&?User there is some ambiguity if that should have
been ($foo is $bar) ? User : … (i.e. a ternary with a constant User
in the “then” part). It might be clear grammar-wise, but not necessarily
immediately to a reader.
The proposed pattern matching semantics are extremely powerful, but do
not come with any integrated guardrails, which make them extremely
complex and easy to “hold wrong”. I find this a step backwards from the
recent developments making PHP safer and more predictable without
forcing users to learn all rules by heart.
We already had issues with “gobble up everything until you can't” with
short closures and pipes in 8.5. I fear the same happening with pattern
matching. As an example the (future scope) range pattern $foo is 1..=10 can easily be typoed or misremembered as $foo is 1...10 which
to my understanding would be valid PHP code equivalent to ($foo is 1.0) . (0.10) (which is not useful, but valid). Taking into account possible
guardrails right away can help ensure that future additions can be added
in a way that is consistent with existing “look and feel”.
Best regards
Tim Düsterhus