[PHP-DEV] [RFC] Pattern Matching

Hi

Am 2025-12-02 21:06, schrieb Larry Garfield:

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.

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:

PHP: 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

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:

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.

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

On Wed, Dec 10, 2025, at 12:20 PM, Tim Düsterhus wrote:

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

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.

On Mon, Dec 1, 2025, at 3:36 PM, Larry Garfield wrote:

Hi folks. Ilija and I would like to present our latest RFC endeavor,
pattern matching:

PHP: 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. :slight_smile: (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. :slight_smile:

Happy New Year!

Tim brought up some interesting points around parsing edge cases, which we're still discussing off list. Once we have a proposal (or multiple) to consider, we'll get back to that part.

Meanwhile, there's a few outstanding issues that we do still want/need broader feedback on. Even if you just want to state a bikeshed preference, please do so. (Though an explanation of why is always helpful.)

1. match() statements. There's two ways we could do this: All-branches, or individual branches. This is particularly important as, in practice, we expect match() to be the most common use of patterns.

// All-branches:
$result = match ($somevar) is {
    Foo => 'foo',
    Bar => 'bar',
    Baz|Beep => 'baz',
    Qix, Nox => 'qix',
};

// Individual branches
$result = match ($somevar) {
    is Foo => 'foo',
    is Bar => 'bar',
    is Baz|Beep => 'baz',
    is Qix, Nox => 'qix',
};

The idea of the second being that, if you want just an identity match, you can skip adding `is` on just that one arm. While it would be lovely to do that automatically, we cannot differentiate between a class name and a constant so that's not really feasible.

Of note, most language with this functionality have no "non-pattern" equivalent of match(), so the issue doesn't come up. The only one that does, Ruby, uses a different prefix keyword depending if it's pattern or equality based, BUT you have to use the same keyword for all branches.

2. Positional array patterns. How picky should the pattern matching be for list-esque arrays? There's 3 options listed in the RFC as possible ways forward, so I won't repeat them here but just provide a link:

3. Do we want to have a pattern for "match an object's properties without specifying what type the object has to be?" If so, what syntax should it be? Just omitting the class name (producing `$o is {x: 1}` ) is not possible. We tried. :slight_smile: Options include:

`$o is _(x: 1)`
`$o is object(x: 1)`
// Your suggestion here.

4. The variable pinning syntax. There's been some concerns about it being non-obvious, which is valid. The main argument for it is "it's what Ruby does", which is not the most compelling argument, but it's not invalid.

Is there some other syntax that would work better? Ilija has pondered `{$x}`, which would then potentially allow simple expressions inside the pattern within the {}. I am a little scared of the scope creep potential. :slight_smile: But we're open to having this discussion, so please discuss.

Please do weigh in and help us decide on a way forward for these.

--Larry Garfield

Hi

Am 2026-01-23 20:48, schrieb Larry Garfield:

The idea of the second being that, if you want just an identity match, you can skip adding `is` on just that one arm.

I don't understand what that is trying to say.

2. Positional array patterns. How picky should the pattern matching be for list-esque arrays? There's 3 options listed in the RFC as possible ways forward, so I won't repeat them here but just provide a link:

PHP: rfc:pattern-matching

I expect "list patterns" to be reasonably short, such that `array_is_list()` would effectively be constant-time after checking whether the number of elements matches up. As such I would prefer (1) "Include the list check, accept the cost".

`$o is object(x: 1)`

That would be okay, but no strong feelings.

4. The variable pinning syntax. There's been some concerns about it being non-obvious, which is valid. The main argument for it is "it's what Ruby does", which is not the most compelling argument, but it's not invalid.

Is there some other syntax that would work better? Ilija has pondered `{$x}`, which would then potentially allow simple expressions inside the pattern within the {}. I am a little scared of the scope creep potential. :slight_smile: But we're open to having this discussion, so please discuss.

As I think I said before, I don't need variable pinning at all, but `^` in particular is terrible. `{}` would be okay, but I prefer just not making a decision for now.

Best regards
Tim Düsterhus

On Sat, Jan 24, 2026, at 12:25 PM, Tim Düsterhus wrote:

Hi

Am 2026-01-23 20:48, schrieb Larry Garfield:

The idea of the second being that, if you want just an identity match,
you can skip adding `is` on just that one arm.

I don't understand what that is trying to say.

With a per-arm keyword, one could do this:

$result = match ($somevar) {
    is Foo => 'foo', // This matches against the pattern `Foo`, which is a class name.
    Bar => 'bar', // This matches === against the constant `Bar`, whose value is whatever.
};

The main reason we cannot just auto-detect whether it's a pattern is the confusion between a class name and constant, since a constant is a legal literal value for an identity match. With per-arm, you can opt-in to pattern matching individually, at the cost of having to remember to repeat the keyword on every line. With per-block, you only have to add the keyword once at the cost of not being able to selectively use patterns or identity matching.

Which tradeoff is better is the option question. (Ilija and I disagree on which to go with.) Or, if someone can suggest a way to allow automatic detection reliably, that would also be most welcome. :slight_smile:

--Larry Garfield

On 2026-01-26 07:44, Larry Garfield wrote:

With per-arm, you can opt-in to pattern matching individually, at the cost of having to remember to repeat the keyword on every line. With per-block, you only have to add the keyword once at the cost of not being able to selectively use patterns or identity matching.

Which tradeoff is better is the option question. (Ilija and I disagree on which to go with.) Or, if someone can suggest a way to allow automatic detection reliably, that would also be most welcome. :slight_smile:

If it had to be one or the other, then arms-only would be my choice as the more flexible of the two - and it's the one we have now, so the real question is if "match($foo) is {..." should be added as a shorthand for when all matching is on patterns.

I'm leaning toward "yes", especially as scalar literals would pattern match by value identity anyway. Falling back on per-arm "is" would only be necessary to address those class/constant confusions if and when they arise - but when they do you'll want it to be possible to address them.

But if "is" were required on every pattern match, that's a lot of "is"ing. People would get into the habit of using it like "case" in a switch statement (or putting backticks around every single identifier in a MySQL query), using it even when a value match is sufficient. And then having it bite them precisely in those cases when there is something like a class/constant confusion.

Does it have to be one of the other though? Can’t we have both?

$result = match ($somevar) {
is Foo => 'foo', // This matches against the pattern `Foo`, which is a class name.
Bar => 'bar', // This matches === against the constant `Bar`, whose value is whatever.
};
```php

```php
$result = match ($somevar) is {
Foo => 'foo', // This matches against the pattern `Foo`, which is a class name.
Bar => 'bar', // This matches against the pattern `Bar`, which is also a class name
};
$result = match ($somevar) is {
is Foo => 'foo', // This is an error (?)
};
```php

We already have somewhat similar behaviour with `readonly` (for properties) and `abstract` (for methods) class modifiers. When you specify it on a class, it applies to all members, but you can also specify it on an individual member.

<details class='elided'>
<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>

Best regards,
Bruce Weirdan mailto:[weirdan@gmail.com](mailto:weirdan@gmail.com)

</details>

On 2026-01-26 23:53, Bruce Weirdan wrote:

On Mon, Jan 26, 2026 at 5:48 AM Morgan <weedpacket@varteg.nz <mailto:weedpacket@varteg.nz>> wrote:

> On 2026-01-26 07:44, Larry Garfield wrote:
> >
> > With per-arm, you can opt-in to pattern matching individually, at
> the cost of having to remember to repeat the keyword on every line. > With per-block, you only have to add the keyword once at the cost of
> > not being able to selectively use patterns or identity matching.
> >
> > Which tradeoff is better is the option question. (Ilija and I
> disagree on which to go with.) Or, if someone can suggest a way to
> allow automatic detection reliably, that would also be most welcome. :slight_smile:
> >
> > If it had to be one or the other [...]

Does it have to be one of the other though? Can't we have both?

> > ...so the real question is if "match($foo) is {..." should be added as a shorthand for when all matching is on patterns.

On Mon, Jan 26, 2026, at 7:33 AM, Morgan wrote:

On 2026-01-26 23:53, Bruce Weirdan wrote:

On Mon, Jan 26, 2026 at 5:48 AM Morgan <weedpacket@varteg.nz
<mailto:weedpacket@varteg.nz>> wrote:

> On 2026-01-26 07:44, Larry Garfield wrote:
> >
> > With per-arm, you can opt-in to pattern matching individually, at
> the cost of having to remember to repeat the keyword on every line.
> With per-block, you only have to add the keyword once at the cost of
> > not being able to selectively use patterns or identity matching.
> >
> > Which tradeoff is better is the option question. (Ilija and I
> disagree on which to go with.) Or, if someone can suggest a way to
> allow automatic detection reliably, that would also be most welcome. :slight_smile:
> >
>
> If it had to be one or the other [...]

Does it have to be one of the other though? Can't we have both?

> > ...so the real question is if "match($foo) is {..." should be added
as a shorthand for when all matching is on patterns.

It is technically possible to implement it both ways, I believe. Whether that is too confusing and messy is another question.

--Larry Garfield

On 23 January 2026 19:48:31 GMT, Larry Garfield <larry@garfieldtech.com> wrote:

4. The variable pinning syntax. There's been some concerns about it being non-obvious, which is valid. The main argument for it is "it's what Ruby does", which is not the most compelling argument, but it's not invalid.

As I think I've said before, for the average PHP user, the question isn't going to be "what's the syntax for variable pinning?", it's going to be "how do I tell if this variable is input to or output from the pattern?" In other words, variable binding and variable pinning syntaxes need to be designed as a pair.

Take this example from the RFC:

if ($p is Point(z: $z, x: 3, y: $y))

To a PHP programmer who has never seen pattern matching before, that looks like it's testing three properties, not doing some destructuring assignments. If variable pinning isn't supported, they'll soon learn; but if both interpretations are supported with subtly different syntax, I can see it becoming a major source of bugs. Maybe that's why a lot of languages don't support pinning at all?

The only way I can think to make this less painful is to have some pair of syntaxes that signals "in" and "out", or "read" and "set", in some way. For example, this looks rather ugly, and may not even be parseable, but it signals the behaviour fairly well without having to read the manual:

if ($p is Point(z: $z=, x: 3, y: $y=))
if ($p is Point(z: ==$z, x: 3, y: ==$y))
if ($p is Point(z: ==$z, x: 3, y: $y=))

Regards,

Rowan Tommins
[IMSoP]

Hi,

  1. match() statements. There’s two ways we could do this: All-branches, or individual branches. This is particularly important as, in practice, we expect match() to be the most common use of patterns.

I prefer to have one syntax. The most flexible one is individual arms. And to me it also looks more clean from the functional side.

If we support both we should also disallow the use of mixing all arms and individual arms. Thus this syntax should return an error.

$result = match ($somevar) is { // Error!
    is Foo => 'foo',
    is Bar => 'bar',
    is Baz|Beep => 'baz',
};

Positional array patterns. How picky should the pattern matching be for list-esque arrays? There’s 3 options listed in the RFC as possible ways forward, so I won’t repeat them here but just provide a link:

PHP: rfc:pattern-matching

From a cost perspective it would be best to keep it to a minimum. But array’s exist in different styles where it matters to have at least a few edge-cases covered. Especially when converting between data formats (Serialise, JSON, ..)

I would personally discourage to return true with the following cases:

Given:

$a = ['a', 'b', 'c'];
$a is [2 => 'c', 0 => 'a', 1 => 'b'];

Should return false, because when converting to other data formats. The style will change from numerical to key’ed (associative style). The same applies to the inverse.

Though given: