# [RFC] Pattern Matching

- Thread: https://externals.io/message/129490
- Original archive: https://news-web.php.net/php.internals/129490
- Started by Larry Garfield on 2025-12-01T21:36:21+00:00
- Messages: 40
- Page 1 of 2: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/129490.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #129490 — Larry Garfield — 2025-12-01T21:36:21+00:00

URL: https://externals.io/message/129490#129490

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

---

## #129491 — Paul Dragoonis — 2025-12-01T21:57:25+00:00

URL: https://externals.io/message/129490#129491 · In reply to: #129490

> 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

---

## #129494 — Larry Garfield — 2025-12-01T22:21:15+00:00

URL: https://externals.io/message/129490#129494 · In reply to: #129491

> 
> > […]
>
> 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

---

## #129496 — Bob Weinand — 2025-12-01T23:04:49+00:00

URL: https://externals.io/message/129490#129496 · In reply to: #129490

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

---

## #129497 — Paul Dragoonis — 2025-12-01T23:12:41+00:00

URL: https://externals.io/message/129490#129497 · In reply to: #129494

> 
> > […]
> wrote:
> > […]
> pattern matching:
> > […]
> 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.
> > […]
> 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.)
> > […]
> looking for feedback.
> > […]
> least late January, regardless of how the discussion goes.  So, plenty of
> time to express your support. :-)
> > […]
> part.
> > […]
>
> 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

---

## #129498 — Stephen Reay — 2025-12-02T01:24:43+00:00

URL: https://externals.io/message/129490#129498 · In reply to: #129490

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

---

## #129500 — Dmitry Derepko — 2025-12-02T06:26:08+00:00

URL: https://externals.io/message/129490#129500 · In reply to: #129490

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

---

## #129502 — Rob Landers — 2025-12-02T06:51:28+00:00

URL: https://externals.io/message/129490#129502 · In reply to: #129490

> 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

---

## #129503 — Rob Landers — 2025-12-02T06:52:14+00:00

URL: https://externals.io/message/129490#129503 · In reply to: #129502

> 
> > […]
> 
> 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

I literally saw it just after sending the email and missed the "undo send" by milliseconds. Sorry for the noise.

— Rob

---

## #129504 — Alexandre Daubois — 2025-12-02T09:13:36+00:00

URL: https://externals.io/message/129490#129504 · In reply to: #129490

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:
>
> https://wiki.php.net/rfc/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

---

## #129507 — Morgan — 2025-12-02T11:58:19+00:00

URL: https://externals.io/message/129490#129507 · In reply to: #129500

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

---

## #129508 — drealecs@gmail.com — 2025-12-02T12:28:07+00:00

URL: https://externals.io/message/129490#129508 · In reply to: #129490

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:
>
> https://wiki.php.net/rfc/pattern-matching
>
>
>
Great work, very detailed and well written.

1. 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.

2. Maybe some parts of the RFC could be separated to reduce complexity.
Thinking about variable pinning.

3. 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

---

## #129513 — Larry Garfield — 2025-12-02T20:00:32+00:00

URL: https://externals.io/message/129490#129513 · In reply to: #129500

> 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

---

## #129514 — Larry Garfield — 2025-12-02T20:06:15+00:00

URL: https://externals.io/message/129490#129514 · In reply to: #129508

> 
> > […]
>
> Great work, very detailed and well written.

Thanks!

> 1. 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.

> 2. 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.

> 3. 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

---

## #129516 — Larry Garfield — 2025-12-02T20:40:00+00:00

URL: https://externals.io/message/129490#129516 · In reply to: #129498

> 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

---

## #129517 — Larry Garfield — 2025-12-02T21:17:33+00:00

URL: https://externals.io/message/129490#129517 · In reply to: #129504

> Hi Larry and Ilija,
>
> Le lun. 1 déc. 2025 à 22:37, Larry Garfield <larry@garfieldtech.com> a écrit :
> > […]
>
> 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

---

## #129521 — Dmitry Derepko — 2025-12-03T04:32:02+00:00

URL: https://externals.io/message/129490#129521 · In reply to: #129513

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

---

## #129522 — drealecs@gmail.com — 2025-12-03T08:21:29+00:00

URL: https://externals.io/message/129490#129522 · In reply to: #129514

On Tue, Dec 2, 2025 at 10:09 PM Larry Garfield <larry@garfieldtech.com>
wrote:

>
>
> > […]
>
> 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

---

## #129525 — Larry Garfield — 2025-12-03T15:53:46+00:00

URL: https://externals.io/message/129490#129525 · In reply to: #129521

> > […]
>
> 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

---

## #129526 — Larry Garfield — 2025-12-03T15:56:15+00:00

URL: https://externals.io/message/129490#129526 · In reply to: #129522

> 
> > […]
>
> 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

---

## #129545 — tim@bastelstu.be — 2025-12-04T12:34:54+00:00

URL: https://externals.io/message/129490#129545 · In reply to: #129514

Hi

Am 2025-12-02 21:06, schrieb Larry Garfield:
> > […]
> 
> 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

---

## #129571 — tim@bastelstu.be — 2025-12-08T15:43:12+00:00

URL: https://externals.io/message/129490#129571 · In reply to: #129490

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:
> 
> https://wiki.php.net/rfc/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.

1.

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)`.

2.

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

---

## #129576 — Larry Garfield — 2025-12-09T17:22:31+00:00

URL: https://externals.io/message/129490#129576 · In reply to: #129496

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

---

## #129580 — Vinicius Dias — 2025-12-10T13:37:45+00:00

URL: https://externals.io/message/129490#129580 · In reply to: #129576

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

---

## #129584 — tim@bastelstu.be — 2025-12-10T18:20:12+00:00

URL: https://externals.io/message/129490#129584 · In reply to: #129580

Hi

Am 2025-12-10 14:37, schrieb Vinicius Dias:
> > […]
> 
> 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

---

Next page: https://externals.io/message/129490.md?page=2
