GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||
GeezerVison is showing wiki.php.net — PHP: rfc:pattern-matching rfc:pattern-matching
PHP RFC: Pattern Matching
Introduction
This RFC introduces pattern matching syntax for PHP. Pattern Matching as a language concept contains two parts: Matching a variable against a potentially complex data structure pattern, and optionally extracting values out of that variable into their own variables. In a sense it serves a similar purpose for complex data structures as regular expressions do for strings. When properly applied, it can lead to very compact but still readable code, especially when combined with conditional structures such as Pattern matching is found in a number of languages, including Python, C#, F# Rust, Scala, Ruby, Swift, Haskell, and ML, among others. The syntax offered here draws inspiration from several of them, but is not a direct port of any. This RFC is part of the Algebraic Data Types Epic. It is a stepping stone toward full Algebraic Data Types (Enums with associated values) but stands on its own as useful functionality. OverviewThis RFC proposes a number of patterns against which to match a value. A complete list is shown below, with minimal detail. Full detail of each option is described in the sections below. //// Core functionality //// // The "is" keyword evaluates to a boolean. if ($var is <pattern>) { // Do stuff } // Basic type matching $var is string; $var is int|float; $var is ?array; $var is (Account&Authenticated)|User; $var is mixed; // Matches anything, effectively a wildcard. // Literal patterns $var is "foo"; $var is 5; $var is 3|5|null; $var is 'heart'|'spade'; // Class constant patterns $var is self::Wild; $var is 'heart'|'spade'|self::Wild; // Numeric comparison patterns $var is <10; $var is >=5; $var is >5 & <10; //// Related syntax enhancement //// // Support patterns in match() statements: $result = match ($somevar) is { Foo => 'foo', Bar => 'bar', Baz|Beep => 'baz', }; //// Structural patterns //// // Object patterns $p is Point(x: 3); // Matches any Point whose $x property is 3. $p is Point(x: 4|5); // Matches any Point whose $x property is 4 or 5. $p is Point(y: 3)|null; // Array sequence patterns $list is [1, 2, 3, 4]; // Exact match. $list is [1, 2, 3, ...]; // Begins with 1, 2, 3, but may have other entries. $list is [1, 2, mixed, 4]; // Allows any value in the 3rd position. $list is [1, 2, 3|4, 5]; // 3rd value may be 3 or 4. // Associative array patterns // Exact key/value match, but order doesn't matter. $assoc is ['a' => 'A', 'b' => 'B']; // Must have a 'b' key with value 'B', and may have other entries. $assoc is ['b' => 'B', ...]; // Must have a 'b' key with any value, and may have other entries. $assoc is ['b' => mixed, ...]; // Array shapes (really just a natural implication of array patterns.) $assoc is ['a' => string, 'b' => int|float, 'c' => 'foo'|'bar']; // Capturing values out of a pattern and binding them to variables if matched. $p is Point(x: 3, y: $y); // If $p->x === 3, bind $p->y to $y and return true. $assoc is ['a' => 'A', 'b' => $b]; // If $assoc['a'] === 'A', bind $assoc['b'] to $b and return true. Proposal
This RFC introduces a new keyword and binary operator: if ($var is <pattern>) { }
The left-hand side of
While patterns may resemble other language constructs, whatever follows
Pattern structureA pattern is a rule that a given value must conform to. That is fairly generic, by design. Each pattern below may be used stand-alone or combined into a compound pattern. The following are all examples of “core patterns,” explained in the next section: // Basic pattern $foo is string; // Compound patterns $foo is int|null; // Combines 2 type patterns. $foo is 'a'|'b'|'c'; // Combines 3 literal patterns. $foo is Account&Authenticated; // Combines 2 type patterns. $foo is Point(x: 5, y: 3|4); // An object pattern, with a compound sub pattern that combines two literal patterns. Core patternsThe core patterns represent the “baseline” for pattern matching. Even if some are not particularly interesting on their own, the pattern matching structure doesn't really work without them. Compound patterns
One or more patterns may be combined into a single pattern using // Combines two type patterns. $foo is int|string; // Combines three type patterns, using DNF conjunctions. $foo is User|(Account&Authenticated)
If both Type pattern
A pattern may be a type signature, including both class and primitive types. In this case, That is, the following are all legal: $foo is string; // Equivalent to is_string($foo) $foo is Request; // Equivalent to $foo instanceof Request $foo is ?array; // Equivalent to is_array($foo) || is_null($foo) $foo is float; // Equivalent to is_int($foo) || is_float($foo), for consistency with types. // These are compound patterns, consisting of two sub-patterns each. $foo is int|float; // Equivalent to is_int($foo) || is_float($foo) $foo is User|int; // Equivalent to $foo instanceof User || is_int($foo) $foo is string|Stringable; // Equivalent to is_string($foo) || $string instanceof Stringable // This is also a compound pattern. It is equivalent to: // $foo instanceof User || ($foo instanceof Account && $foo instanceof Authenticated) $foo is User|(Account&Authenticated) // Iterable is a type, so this is also valid: $foo is iterable; // Equivalent of $foo is \Traversable|array // true, false, and null are now types in their own right, so will also work: // Simple degenerate case patterns. $foo is true; // Equivalent to $foo === true $foo is null; // Equivalent to $foo === null
Type patterns are always evaluated in strict mode, so as to be consistent with
In a sense,
Any type may be used, with the exception of
Of particular note, the Literal pattern
Any scalar literal may be a pattern. When used on its own it is not particularly useful (it's equivalent to // Simple degenerate case patterns. $foo is 5; // Equivalent to $foo === 5 $foo is 'yay PHP'; // Equivalent to $foo === 'yay PHP' // More practical compound example $foo is "beep"|"boop"; // Equivalent to $foo === "beep" || $foo === "boop" // More practical compound examples $foo is array|null; // Equivalent to is_array($foo) || $foo === null $foo is "Aardvark"|"Bear"|null // Equivalent to $foo === "Aardvark" || $foo === "Bear" || $foo === null Valid literals include:
Values that are dynamic at runtime (eg, an interpolated string with a variable in it) are not literal patterns. However, see below on variable pinning in future scope. Class constant patternClass constants may also be used as a pattern: $foo is 'spade'|'heart'|self::Wild; Global constants may not be used directly, as they cannot be differentiated from class names. (See variable pinning in future scope.) Enumeration cases are implemented as class constants, so are supported as well. Numeric comparison patternNumeric values (ints, floats, and numeric strings) may also be compared against another numeric value. Any non-numeric value will always evaluate to false.
A comparison pattern is represented by a simple comparison operator: $X is >5; // True if $x > 5 $X is <=10; // True if $x <= 10 As with many other patterns, they are not super useful on their own but can be combined with other patterns. Additionally, two comparison operations may be ANDed together to effectively produce a range pattern. (See the Object pattern below.) $x is >5 & <10; // True if 5 < $x && $x < 10 $x is >=5 & <=10; // True if 5 <= $x && $x <= 10 $p is Product(price: >10); // True if $p instanceof Product and $p->price > 10 match() enhancement
Pattern matching is frequently used in conjunction with branching structures, in particular with enumerations. To that end, this RFC also enhances the That is, this code: $result = match ($somevar) is { Foo => 'foo', Bar => 'bar', Baz|Beep => 'baz', Qix, Nox => 'qix', }; is equivalent to the following: $result = match (true) { $somevar is Foo => 'foo', $somevar is Bar => 'bar', $somevar is Baz|Beep => 'baz', $somevar is $Qix, $somevar is Nox => 'qix', };
(See “Open Issues” below regarding the syntax for Structure patternsThese are where pattern matching really shines. They are more involved/complex, but have more “bang for the buck” than the basic patterns above. Specifically, there are two kinds of structure patterns: Object property and array patterns. Both can also leverage a third concept, variable binding. Object patternA pattern may also match against scope-accessible properties of an object. The properties must be get-accessible in the scope in which the pattern executes. That is, a pattern evaluated outside the class may only match against public properties; a pattern inside the class may match against public, private, or protected; a pattern in a child class may match against protected properties of its parent but not private; etc. The “value” to match each property against is itself a pattern, so can leverage any of the above pattern combinations. Note that matching against a property's value implies reading that property's value. Therefore, a property match behaves as though the property were read into a temporary variable and then used. That means, for example:
class Point { public function __construct( public int $x, public int $y, public int $z, ) {} } $p = new Point(3, 4, 5); $p is Point(x: 3); // Equivalent to: $p instanceof Point && $p->x === 3; $p is Point(y: 37, x: 2); // Equivalent to: $p instanceof Point && $p->y === 37 && $p->x === 2; // A multi-segment pattern that includes an object pattern. $p is (Point(x: 2))|null // Equivalent to: $p instanceof Point && $p->x === 2 || $p === null; // The $x property is matched against an ORed pattern. $p is Point(x: 2|3) // Equivalent to $p instanceof Point && ($p->x === 2 || $p->x === 3) // x must be 3, and y must be defined and initialized but we don't care what it is. $p is Point(x: 3, y: mixed) // The parens are required to mimic how DNF types work. $p is Product|(Point(x: 3)); Properties may be listed in any order, but must be named, and at least one must be specified. A trailing comma is permitted. (Positional properties are reserved for later use in Tagged Union enums. See Future Scope.) Additionally, the properties in the list are not limited to the those defined by the specified object. That means in the following example, all of the shown patterns are logically equivalent. interface Colorable { public string $color; } class Example implements Colorable { public string $color; public int $age; } class Child extends Example { public string $name; } $e = new Child(); $e is Colorable&Example(color: 'green', age: 4); $e is Colorable(color: 'green')&Example(age: 4); $e is Colorable(color: 'green', age: 4)&Example; // All of these translate to: $e instanceof Colorable && $e instanceof Example && $e->color === 'green' && $e->age === 4; Implementation notesThe syntax for this pattern is tricky, and the syntax chosen is, we believe, the most reasonable option.
In earlier drafts we considered using class C { public $prop = 42 is Foo{}; } But the parser could not tell which meaning is intended:
We also considered splitting the class and property check in two:
That led us to using Array structure patternArray patterns match elements of an array individually against a collection of values. It has two variants: positional or associative. That is, the pattern MUST be entirely positional, or must specify a key for every position. (This is in contrast to array literals, which allow keys to be omitted at random to get an integer assigned.) If an associative pattern is used, the order of keys is explicitly irrelevant. (See Open Questions below.)
A pattern is an associative pattern if keys are specified, regardless of whether they are numeric. So
By default, array matching is exhaustive. That is, the arity of the array and pattern must match. Alternatively, the pattern may include a The value for each array element is itself a pattern. While the most common use case would normally be a literal match, it also supports a type match, ORed pattern, etc. This means that array patterns can function as “array shapes” if desired. This ability becomes more powerful as more future-scope patterns (such as range or regex) are adopted, as they would also be supported for each property.
The Sequential arrays: // Given: $list = [1, 3, 5, 7]; // Degenerate, not very useful case. if ($list is [1, 3, 5, 7]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_is_list($list) && count($list) === 4 && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 && array_key_exists(2, $list) && $list[2] === 5 && array_key_exists(3, $list) && $list[3] === 7 ) { print "Yes"; } if ($list is [1, 3]) { print "Yes"; } // False. Equivalent to: if (is_array($list) && array_is_list($list) && count($list) === 2 && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 ) { print "Yes"; } // All the values exist, but not in the right order, so no match. if ($list is [3, 7, 5, 1]) { print "Yes"; } // False. Equivalent to: if (is_array($list) && array_is_list($list) && count($list) === 4 && array_key_exists(0, $list) && $list[0] === 3 && array_key_exists(1, $list) && $list[1] === 7 && array_key_exists(2, $list) && $list[2] === 5 && array_key_exists(3, $list) && $list[3] === 1 ) { print "Yes"; } if ($list is [1, 3, ...]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_is_list($list) && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 ) { print "Yes"; } if ($list is [1, 3, mixed, 7]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_is_list($list) && count($list) === 4 && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 && array_key_exists(2, $list) && array_key_exists(3, $list) && $list[3] === 7 ) { print "Yes"; } if ($list is [1, 3, 5|6, ...]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_is_list($list) && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 && array_key_exists(2, $list) && ($list[2] === 5 || $list[2] === 6) ) { print "Yes"; } // A sequential "array shape". if ($list is [int, int, int, mixed]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_is_list($list) && count($list) === 4 && array_key_exists(0, $list) && is_int($list[0]) && array_key_exists(1, $list) && is_int($list[1]) && array_key_exists(2, $list) && is_int($list[2]) ) { print "Yes"; } Associative arrays: // Given: $assoc = ['a' => 'A', 'b' => 'B']; // Degenerate, not very useful case. if ($assoc is ['a' => 'A', 'b' => 'B']) { print "Yes"; } // True. Equivalent to: if (is_array($assoc) && count($assoc) === 2 && array_key_exists('a', $assoc) && $assoc['a'] === 'A' && array_key_exists('b', $assoc) && $assoc['b'] === 'B' ) { print "Yes"; } if ($assoc is ['b' => 'B']) { print "Yes"; } // False. Equivalent to: if (is_array($assoc) && count($assoc) === 1 && array_key_exists('b', $assoc) && $assoc['b'] === 'B' ) { print "Yes"; } if ($assoc is ['b' => 'B', ...]) { print "Yes"; } // True. Equivalent to: if (is_array($assoc) && array_key_exists('b', $assoc) && $assoc['b'] === 'B') { print "Yes"; } if ($assoc is ['b' => mixed, ...]) { print "Yes"; } // True. Equivalent to: if (is_array($assoc) && array_key_exists('b', $assoc) ) { print "Yes"; } // An "array shape" pattern. if ($assoc is ['a' => 'A'|'a', 'b' => string]) { print "Yes"; } // True. Equivalent to: if (is_array($assoc) && array_key_exists('a', $assoc) && ($assoc['a'] === 'A' || $assoc['a'] === 'a') && array_key_exists('b', $assoc) && is_string($assoc['b']) ) { print "Yes"; }
Of particular note, the pattern matching approach automatically handles $foo = ['a' => 1, 'b' => 2]; // True $foo is ['b' => 2, 'a' => 1]; // True, because == doesn't consider order. $foo == ['b' => 2, 'a' => 1]; // False, because === does consider order. $foo === ['b' => 2, 'a' => 1]; // False, but no error. $foo is ['a' => 1, 'c' => 3, ...]; // Warning: $foo['c'] is not defined. if ($foo['a'] == 1 && $foo['c'] == 3) { ... } // Warning: $foo['c'] is not defined. if ($foo['a'] === 1 && $foo['c'] === 3) { ... } Variable bindingOne of the prime uses of pattern matching is to extract a value from a larger structure, such as an object (or Enumeration/ADT, in the future). This RFC supports such variable binding by specifying the variable to populate. If the input variable matches the rest of the pattern, then the corresponding value will be extracted and assigned to a variable of that name in the current scope. It will remain in scope as long as normal variable rules say it should. Only local variables may be bound, that is, you cannot bind to a property of an object or a variable-variable. The entire pattern either succeeds or fails. No variables will be bound unless the entire pattern matches. (That also means if a variable exists before the pattern is evaluated, its value will be unchanged if the pattern does not match.) In the currently planned patterns, it is only relevant for object and array pattern matching. Object binding examples: class Point { public function __construct( public int $x, public int $y, public int $z, ) {} } $p = new Point(3, 4, 5); if ($p is Point(x: 3, y: $y)) { print "x is 3 and y is $y."; } // Equivalent to: if ($p instanceof Point && $p->x === 3) { $y = $p->y; print "x is 3 and y is $y."; } if ($p is Point(z: $z, x: 3, y: $y)) { print "x is 3 and y is $y and z is $z."; } // Equivalent to: if ($p instanceof Point && $p->x === 3) { $y = $p->y; $z = $p->z; print "x is 3 and y is $y and z is $z."; } Array binding examples: if ($list is [1, 3, $third, 7]) { print "Yes: $third"; } // True. Equivalent to: if (is_array($list) && count($list) === 4 && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 && array_key_exists(2, $list) && array_key_exists(3, $list) && $list[3] === 7 ) { $third = $list[2]; print "Yes: $third"; } if ($list is [1, 3, $third, ...]) { print "Yes: $third"; } // True. Equivalent to: if (is_array($list) && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 && array_key_exists(2, $list) ) { $third = $list[2]; print "Yes: $third"; } if ($assoc is ['a' => 'A', 'b' => $b]) { print "Yes: $b"; } // True. Equivalent to: if (is_array($assoc) && count($assoc) === 2 && array_key_exists('a', $assoc) && $assoc['a'] === 'A' && array_key_exists('b', $assoc) ) { $b = $assoc['b']; print "Yes: $b"; }
A pattern that includes variable binding may not be ORed with another pattern, as depending on the segment that matches the variable may or may not end up defined, and there's no reliable way to determine that other than // NOT allowed, as its behavior is ambiguous. $p is (Point(x: $x)) | (Circle(r: $radius)) // But this is allowed. $p is Point(x: 3|5, y: $y) // Equivalent to if ($p instanceof Point && ($p->x === 3 || $p->x === 5)) { $y = $p->y; // ... } // This is also allowed: $p is Colorable&Point(x: 3|5, y: $y) 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. That is, the following two examples are exactly equivalent: if ($p is Point(z: $z, x: 3, y: $y)) { print "x is 3 and y is $y and z is $z."; } // Shorthand if ($p is Point(:$z, x: 3, :$y)) { print "x is 3 and y is $y and z is $z."; }
The
Variable binding is especially useful in $result = match ($p) is { // These will match only some Point objects, depending on their property values. Point(x: 3, y: 9, $z) => "x is 3, y is 9, z is $z", Point(:$z, :$x, y: 4) => "x is $x, y is 4, z is $z", Point(x: 5, :$y) => "x is 5, y is $y, and z doesn't matter", // This will match any Point object, but not object of some other type. Point(:$x, :$y, :$z) => "x is $x, y is $y, z is $z", };
Note that in this case, the variables Applying patterns to bound variablesSome 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 For example: if ($foo is Foo(a: 5, b: $b & Point(x: 5))) { print "$b is a Point object with an x property of value 5, and undetermined y."; } // Equivalent to: if ($foo instanceof Foo && $foo->a === 5 && $foo->b instanceof Point && $foo->b->x === 5 ) { $b = $foo->b; print "$b is a Point object with an x property of value 5, and undetermined y."; }
In the above code, the pattern will match only if both if ($params is ['user' => $user & AuthenticatedUser(role: 'admin'), ...]) { print "Congrats, $user->name, you can do admin things!" } // Equivalent to: if (is_array($params) && array_key_exists($params, 'user') && $params['user'] instanceof AuthenticatedUser && $params['user']->role === 'admin' ) { $user = $params['user']; print "Congrats, $user->name, you can do admin things!" }
Similarly, in the above code, The
This does limit the available restrictions to those expressable as patterns, which is not all possible restrictions. However, this behavior is available “for free” by virtue of how Variable pinningVariable pinning is a feature of Ruby's pattern syntax, and also of Swift's (though Swift's syntax is rather wonky). It allows using variables to form the pattern. The proposed syntax is lifted directly from Ruby. It is also the only way to support referencing non-class constants in a pattern, as otherwise they cannot be disambiguated from a class name.
Variables in the current scope may be used in a pattern by prefixing them with
Global constants may also be referenced using
Variables will be compared using the identity ( Expressions may not be pinned, only defined variables. Allowing expressions opens up a whole host of potential side-effects and other weirdness, none of which we want to address. If an expression is needed, calculate it first and assign it to a variable, which can then be pinned.
As with literals, variable pinning is useful mainly in compound patterns and // Simple degenerate case patterns. $foo is ^$bar; // Equivalent to $foo === $bar $foo is ^PHP_VERSION; // Equivalent to $foo === PHP_VERSION // More practical compound patterns $foo is ^Errors::$notFound|^Errors::$invalid; // Equivalent to $foo === Errors::$notFound || $foo === Errors::$invalid // An object pattern with pinned variables. $p is Point(y: 37, x:^$x,); // Equivalent to: $p instanceof Point && $p->y === 37 && $p->x === $x; // An array pattern with pinned variables. if ($assoc is ['a' => 'A', 'b' => ^$b]) { print "Yes"; } // Equivalent to: if (is_array($assoc) && count($assoc) === 2 && array_key_exists('a', $assoc) && $assoc['a'] === 'A' && array_key_exists('b', $assoc) && $assoc['b'] === $b ) { print "Yes"; } Backward Incompatible Changes
A new keyword is added, No other BC breaks are expected. Proposed PHP Version(s)PHP 8.6. RFC ImpactOpen Issuesmatch() "is" placement
The authors are split as to how the syntax for pattern matching $result = match ($somevar) is { Foo => 'foo', Bar => 'bar', Baz|Beep => 'baz', }; $result = match ($somevar) { is Foo => 'foo', is Bar => 'bar', is Baz|Beep => 'baz', };
The former is shorter, and applies pattern matching to all arms. The latter is more explicit, and would allow individual arms to be pattern matched or not depending on the presence of
Of note, among the languages we surveyed, only Ruby uses the same construct for both equality and pattern matching. The Positional array enforcementThe current implementation of array patterns does not technically enforce that a positional pattern is a list. Rather, it implicitly behaves as an associative pattern with numeric sequential keys. That's similar to how PHP arrays behave normally, but does lead to edge cases like this: $a = ['a', 'b', 'c']; $b = [2 => 'c', 0 => 'a', 1 => 'b']; // true, so we know it's in alphabetical order. $a is ['a', 'b', 'c']; // Also true, even though iterating it would NOT return alphabetical order. $b is ['a', 'b', 'c']; foreach ($a as $v) { print $v . ' '; // Prints a b c } // This seems unexpected. foreach ($b as $v) { print $v . ' '; // Prints c a b }
While checking that an array in a positional match is a list (essentially by calling Possible responses include:
The authors are seeking input on this question. Future ScopeNumerous other patterns can be supported in the future. The following additional patterns and use cases are possible future additions for other RFCs. (Please don't bikeshed them here; they are shown as an example of where pattern matching can extend to in the future.) Enum/ADT patternA key goal of this RFC is to lay the groundwork for supporting patterns with Algebraic Data Types, aka, Enums with associated values. We believe that a good pattern matching mechanism is a prerequisite for those being fully usable in the future. The object pattern syntax has been designed to be forward-compatible with future Enum use, through the use of positional pattern values. If this RFC passes, a future ADT RFC would include a new enum-targeted pattern if needed. // Example of what is possible with both pattern matching and ADTs, // All syntax subject to change. enum Move { case TurnLeft; case TurnRight; case Forward(int $amount); } match ($move) is { Move::TurnLeft => $this->orientation--, Move::TurnRight => $this->orientation++, Move::Forward($amount) => $this->distance += $amount, }; enum Option { case None; case Some(mixed $val); } match ($maybe) is { Option::Some($val) => compute_something($val), Option::None => 'default value', } Range patternA more compact alternative to using two numeric comparison patterns would be a dedicated range syntax. $foo is 0..=10; // Equivalent to: $foo >=0 && $foo <= 10; $foo is 0..<10; // Equivalent to: $foo >=0 && $foo < 10; $foo is >10; // Equivalent to: $foo > 10;
Earlier discussion suggested that such a syntax should only be implemented as part of a broader “range” syntax for the whole language. For instance, allowing For that reason we have omitted range for now. A future range RFC, if any, should include a range pattern, possibly modeling on Raku or similar.) Regex pattern
Applicable only to $foo is /^https:\/\/(?<hostname>[^\/]*)/ // Equivalent to: $matches = []; preg_match('/^https:\/\/(?<hostname>[^\/]*)/', $foo, $matches); $hostname == $matches['hostname']; (Note: This pattern is only in the ideation stage, so the syntax has not been fully thought through.) Capturing the array remainder
On array patterns, it would be useful to have a way to capture any otherwise unmatched elements. The most likely way to do so is if a variable name may be listed after the if ($list is [1, 3, ...$rest]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 ) { $rest = array_slice($list, 2); print "Yes"; } // While this will work as described, be aware that its //performance will not be as trivial as it typically is // in functional languages. PHP still needs to create // a new array and replicate values over to it, which // has linear cost. It should be safe to do on its own, // but doing so inside a loop or with recursion is not // advisable. if ($list is [$head, ...$tail]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && array_key_exists(0, $list) ) { $head = $list[0]; $tail = array_slice($list, 1); print "Yes"; } // In this case, there are no other elements // so $tail will be []. if ($list is [1, 3, 5, 7, ...$tail]) { print "Yes"; } // True. Equivalent to: if (is_array($list) && count($list) === 4 && array_key_exists(0, $list) && $list[0] === 1 && array_key_exists(1, $list) && $list[1] === 3 && array_key_exists(2, $list) && $list[2] === 5 && array_key_exists(3, $list) && $list[3] === 7 ) { $tail = array_slice($list, 3); print "Yes"; } if ($assoc is ['b' => 'B', ...$other]) { print "Yes"; } // True. Equivalent to: if (is_array($assoc) && && array_key_exists('b', $assoc) && $assoc['b'] === 'B') { $other = array_diff_key($assoc, ['b' => true]); print "Yes"; } This feature was considered for the initial RFC, but while the design (above) is reasonably straightforward, the implementation is not. We have therefore chosen to postpone it to the future, though it is an addition that should be pursued. Optional array key markerAs described above, array patterns support “this key must be defined and match this pattern” or “I don't care if it's defined or not” (using the ... suffix). However, there is no obvious way to indicate “this key is optional, but if it is defined it must match this pattern.” Such a marker would be useful to include, although we have not yet explored a syntax for it. One possibility would be: // $arr must have a string 'a' key, MAY have a string 'b' key but no other 'b', // and any other keys are irrelevant. $arr is ['a' => string, ?'b' => string, ...] Speculative extensionsThere are a number of other speculative ideas that have been floated but not really developed for how patterns could extend in the future. As they are still not even half-baked ideas, we have omitted them from this RFC for clarity. However, they are available separately for those who want to see the more “creative” ideas. Proposed Voting ChoicesThis is a simple up-or-down vote, requiring 2/3 Yes to pass.
Patches and TestsLinks to any external patches and tests go here. If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed. Make it clear if the patch is intended to be the final patch, or is just a prototype. For changes affecting the core language, you should also provide a patch for the language specification. ImplementationAfter the project is implemented, this section should contain
ReferencesLinks to external references, discussions or RFCs Rejected FeaturesKeep this updated with features that were discussed on the mail lists. rfc/pattern-matching.txt · Last modified: by crell
Table of Contents | ||||||||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |