On Mon, Dec 1, 2025 at 4:38 PM Larry Garfield <[email protected]>
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. :-)
>
> --
>   Larry Garfield
>   [email protected]


Thanks for putting this together! It's clear a lot of thought went into
this. I am not a regular on this list but there are a few thoughts I had
reading through it that I wanted to share.

One of the stated prime use cases of this RFC is destructuring, but the
main behavior is to resolve to a boolean. It feels like these two ideas,
destructuring and validating, are at odds with each other at times.

> The is operator is technically a comparison operator, and always returns
a boolean true or false.
> The entire pattern either succeeds or fails. No variables will be bound
unless the entire pattern matches

When the only failure signal is a false return, every destructuring site
has to check it manually. 9 times out of 10 when you are destructuring, you
already know the shape of your data. With the proposed implementation,
outside of match(), that looks like:

if (!($point is ['x' => $x, 'y' => $y])) {
    throw new InvalidArgumentException();
}
Or more simply:
$point is ['x' => $x, 'y' => $y] ?: throw new InvalidArgumentException();

This will get quite repetitive. Most languages I've looked at provide at
least one form where a failed match cannot pass silently.

- Elixir/Erlang, Scala, and Haskell all error when structure doesn't match
- Rust and Swift's bind-into-scope forms require an else block that must
exit the scope (`let ... else` and `guard let ... else`)
- Ruby allows for silently failing with `in`, and throwing with `=>`
- C#, the exception, relies on compiler definite-assignment analysis to
prevent code from ever reading a maybe-unbound variable, which is a safety
net that PHP doesn't have

I propose we follow Ruby in this, and allow both. Perhaps on failure `is`
returns false, while something like `is!` throws (exact syntax aside, as I
recognize ! means not in PHP). In addition to reducing repetition, an
engine-thrown error can say exactly which part of the pattern failed. That
behavior can't be reasonably reproduced with a manually thrown error.

> For object patterns (only), if the variable name to extract to is the
same as the name of the property, then the property name may be omitted.

I like the :$id shorthand, but since object patterns mirror named argument
syntax (albeit extracting rather than passing values), this effectively
introduces argument punning syntax into PHP. Whatever is decided here
likely becomes the standard for punning in calls (new Point(:$x)), since
anything else would be inconsistent. So this feels like a separate
named-argument decision being settled inside a pattern-matching RFC. I'd
suggest that it instead belongs in its own punning proposal that naturally
applies to pattern matching.

That aside, would an equivalent shorthand be available with variable
pinning, such as :^$x?

> Some languages support a dedicated syntax to apply additional
restrictions (pattern based or otherwise) to a matched variable. For
example, only match if a given string property is one of a particular set
of values, but if it is, then bind that value to the variable.
>
> Because the proposed syntax supports full DNF pattern combinations, such
behavior is achievable without a dedicated syntax. Specifically, a bind
pattern and a filtering pattern may be combined with an &.

This approach falls apart with union types and DNF. Binding a value while
filtering it against a union type requires $x & (int|string), which isn't
valid DNF. The DNF form, ($x & int) | ($x & string), is explicitly
forbidden in the RFC as a binding cannot be nested within a union.

Other languages use @ for this, which is already taken in PHP. My
suggestion would be to use `is` when validating and binding, such as $p is
Point(x: $x is int, y: $y). This feels natural, although it does differ
from the base usage of `is`, since the left operand goes from a tested
value to a binding target. Perhaps a dedicated keyword like `of` ($x of
int) avoids that at the cost of reserving another word.

> By default, array matching is exhaustive. That is, the arity of the array
and pattern must match

The "array shape" example with patterns (['a' => 'A'|'a', 'b' => string])
omits the count($assoc) === 2 check in its stated equivalent, while the
sequential shape example ([int, int, int, mixed]) includes one. Is the
associative example missing the arity check, or is exhaustiveness
intentionally relaxed there?

Reply via email to