rfc:named_params
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:named_params [2020/06/23 12:05] – Fix typos theodorejb | rfc:named_params [2022/05/26 15:30] – Add alternatives section iquito | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Named Arguments | + | ====== PHP RFC: Stricter implicit boolean coercions |
- | * Date: 2013-09-06, significantly updated 2020-05-05 | + | * Version: 1.7 |
- | * Author: | + | * Date: 2022-05-16 |
+ | * Author: | ||
* Status: Under Discussion | * Status: Under Discussion | ||
- | * Target Version: PHP 8.0 | + | * First Published at: http://wiki.php.net/rfc/stricter_implicit_boolean_coercions |
- | * Implementation: | + | |
===== Introduction ===== | ===== Introduction ===== | ||
- | Named arguments allow passing arguments to a function based on the parameter name, rather than the parameter position. This makes the meaning of the argument self-documenting, | + | When not using strict_types in PHP, scalar type coercions have become less lossy/ |
- | To give a simple example: | + | Some examples where this might lead to unexpected outcomes: |
- | + | ||
- | <code php> | + | |
- | // Using positional arguments: | + | |
- | array_fill(0, | + | |
- | + | ||
- | // Using named arguments: | + | |
- | array_fill(start_index: | + | |
- | </ | + | |
- | + | ||
- | The order in which the named arguments are passed does not matter. The above example passes them in the same order as they are declared in the function signature, but any other order is possible too: | + | |
- | + | ||
- | <code php> | + | |
- | array_fill(value: | + | |
- | </ | + | |
- | + | ||
- | It is possible to combine named arguments with normal, positional arguments and it is also possible to specify only some of the optional arguments of a function, regardless of their order: | + | |
- | + | ||
- | <code php> | + | |
- | htmlspecialchars($string, | + | |
- | // Same as | + | |
- | htmlspecialchars($string, | + | |
- | </ | + | |
- | + | ||
- | ===== What are the benefits of named arguments? ===== | + | |
- | + | ||
- | ==== Skipping defaults ==== | + | |
- | + | ||
- | One obvious benefit of named arguments can be seen in the last code sample (using '' | + | |
- | + | ||
- | This is also possible with the [[rfc: | + | |
- | + | ||
- | <code php> | + | |
- | htmlspecialchars($string, | + | |
- | // vs | + | |
- | htmlspecialchars($string, | + | |
- | </ | + | |
- | + | ||
- | Seeing the first line you will not know what the '' | + | |
- | + | ||
- | ==== Self-documenting code ==== | + | |
- | + | ||
- | The benefit of making code self-documenting applies even when you are not skipping optional arguments. For example, compare the following two lines: | + | |
- | + | ||
- | <code php> | + | |
- | array_slice($array, | + | |
- | // vs | + | |
- | array_slice($array, | + | |
- | </ | + | |
- | + | ||
- | If I wasn't writing | + | |
- | + | ||
- | ==== Object Initialization ==== | + | |
- | + | ||
- | The [[rfc: | + | |
<PHP> | <PHP> | ||
- | // Part of PHP AST representation | + | function |
- | class ParamNode extends Node { | + | { |
- | public | + | |
- | public string $name, | + | |
- | public ExprNode $default = null, | + | |
- | public TypeNode $type = null, | + | |
- | public | + | |
- | | + | |
- | Location $startLoc = null, | + | |
- | Location $endLoc = null, | + | |
- | ) { | + | |
- | | + | |
- | } | + | |
} | } | ||
- | </ | ||
- | Constructors in particular often have a larger than average number of parameters whose order has no particular significance, | + | toBool(' |
- | + | toBool(-0); // bool(false) | |
- | There have been multiple attempts to make object construction more ergonomic, such as the [[rfc: | + | toBool(' |
- | + | toBool(0.0); | |
- | Named arguments solve the object initialization problem as a side-effect, in a way that integrates well with existing language semantics. | + | toBool(' |
- | + | toBool(0.1); // bool(true) | |
- | < | + | toBool(-37593); // bool(true) |
- | new ParamNode(" | + | toBool(' |
- | // becomes: | + | toBool(' |
- | new ParamNode(" | + | |
- | + | ||
- | new ParamNode($name, null, null, $isVariadic, | + | |
- | // or was it? | + | |
- | new ParamNode($name, null, null, $passByRef, $isVariadic); | + | |
- | // becomes | + | |
- | new ParamNode($name, variadic: $isVariadic, | + | |
- | // or | + | |
- | new ParamNode($name, byRef: $passByRef, variadic: $isVariadic); | + | |
- | // and it no longer matters! | + | |
</ | </ | ||
- | |||
- | The benefit of named arguments for object initialization is on the surface the same as for other functions, it just tends to matter more in practice here. | ||
- | |||
- | ==== Type-safe and documented options ==== | ||
- | |||
- | One of the common workarounds for the lack of named arguments, is the use of an options array. The previous example could be rewritten to use an options array as follows: | ||
- | |||
- | <PHP> | ||
- | class ParamNode extends Node { | ||
- | public string $name; | ||
- | public ExprNode $default; | ||
- | public TypeNode $type; | ||
- | public bool $byRef; | ||
- | public bool $variadic; | ||
- | |||
- | public function __construct(string $name, array $options = []) { | ||
- | $this-> | ||
- | $this-> | ||
- | $this-> | ||
- | $this-> | ||
- | $this-> | ||
- | |||
- | parent:: | ||
- | $options[' | ||
- | $options[' | ||
- | ); | ||
- | } | ||
- | } | ||
- | |||
- | // Usage: | ||
- | new ParamNode($name, | ||
- | new ParamNode($name, | ||
- | </ | ||
- | |||
- | While this works, and is already possible today, it has a quite a range of disadvantages: | ||
- | |||
- | * For constructors in particular, it precludes usage of constructor promotion. | ||
- | * The available options are not documented in the signature. You have to look at the implementation or phpdoc to find out what is supported and what types it requires. Phpdoc also provides no universally recognized way to document this. | ||
- | * The type of the option values is not validated unless manually implemented. In the above example, the types will actually be validated due to the use of property types, but this will not follow usual PHP semantics (e.g. if the class declaration uses strict_types, | ||
- | * Unless you go out of your way to protect against this, passing of unknown options will silently succeed. | ||
- | * Use of an options array requires a specific decision at the time the API is introduced. If you start off without one, but then add additional optional parameters and realize that using an options array would be cleaner, you cannot perform the switch without breaking existing API users. | ||
- | |||
- | Named parameters provide the same functionality as options arrays, without any of the disadvantages. | ||
- | |||
- | ==== Attributes ==== | ||
- | |||
- | The use of named arguments in phpdoc annotations is already wide-spread in the ecosystem. While the [[rfc: | ||
- | |||
- | For example, the Symfony '' | ||
- | |||
- | <PHP> | ||
- | /** | ||
- | * @Route("/ | ||
- | */ | ||
- | public function show(int $id) { ... } | ||
- | |||
- | // Might become: | ||
- | |||
- | << | ||
- | public function show(int $id) { ... } | ||
- | </ | ||
- | |||
- | Introducing named arguments in the same version as attributes would allow retaining exactly the same structure as before: | ||
- | |||
- | <PHP> | ||
- | << | ||
- | public function show(int $id) { ... } | ||
- | </ | ||
- | |||
- | Some changes would still be necessary due to the lack of support for nested annotations, | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | ==== Syntax ==== | + | In coercive typing mode, limit the allowed scalar values for typed boolean arguments, boolean return types and boolean class properties to the following: |
- | Named arguments are passed by prefixing the value with the parameter name followed by a colon: | + | * 0 (and -0) integer (= false) |
+ | * 0.0 (and -0.0) float (= false) | ||
+ | * " | ||
+ | * "" | ||
+ | * 1 integer (= true) | ||
+ | * 1.0 float (= true) | ||
+ | * " | ||
- | < | + | Any other integers, floats and strings are always coerced to true (no behavior change) but will emit an '' |
- | callAFunction(paramName: $value); | + | |
- | </ | + | |
- | It is possible | + | * For coercions from string the deprecation notice |
- | + | * For coercions from int the deprecation notice is: Implicit conversion from int %d to true, only 0 or 1 are allowed | |
- | < | + | * For coercions from float the deprecation notice is: Implicit conversion from float %f to true, only 0 or 1 are allowed |
- | array_foobar(array: $value); | + | |
- | </ | + | |
- | The parameter name must be an identifier, it's not possible to specify it dynamically: | + | These would be the notices generated for the examples in the introduction: |
<PHP> | <PHP> | ||
- | // NOT supported. | + | toBool(' |
- | function_name($variableStoringParamName: | + | toBool(-0); |
+ | toBool(' | ||
+ | toBool(0.0); | ||
+ | toBool(' | ||
+ | toBool(0.1); | ||
+ | toBool(-37593); | ||
+ | toBool(' | ||
+ | toBool(' | ||
</ | </ | ||
- | This syntax is not supported | + | In the long-term these deprecations should be raised |
- | Some syntax alternatives that are technically feasible are: | + | ===== Rationale ===== |
- | < | + | This RFC boils down to these questions: |
- | function_name(paramName: $value); | + | |
- | function_name(paramName => $value); | + | |
- | function_name(paramName = $value); | + | |
- | function_name(paramName=$value); | + | |
- | function_name($paramName: | + | |
- | function_name($paramName => $value); // (5) | + | |
- | </ | + | |
- | It should be noted that the following syntax is not possible, because | + | * Are you losing information when you reduce a value like -375, " |
+ | * Would you want to know when a value like -375, " | ||
+ | * How likely is it that such a coercion is unintended? | ||
+ | * What about other boolean coercions in PHP? (this is handled in the next section) | ||
- | < | + | The main motivation for this RFC is to reduce the possibility of errors when using the boolean type in a similar way that you cannot give a typed int a non-number string - if you provide " |
- | function_name($paramName = $value); | + | |
- | </ | + | |
- | A previous version of this RFC proposed '' | + | * Avoid losing information when an unusual value is coerced |
- | + | * Make the boolean type and the type juggling system safer and more consistent | |
- | ==== Constraints ==== | + | * Setting up only 7 scalar |
- | + | ||
- | It is possible to use positional and named arguments in the same call, however the named arguments must come after the positional arguments: | + | |
- | + | ||
- | < | + | |
- | // Legal | + | |
- | test($foo, param: $bar); | + | |
- | // Compile-time error | + | |
- | test(param: $bar, $foo); | + | |
- | </ | + | |
- | + | ||
- | Passing the same parameter multiple times results in an '' | + | |
- | + | ||
- | < | + | |
- | function test($param) { ... } | + | |
- | + | ||
- | // Error: Named parameter $param overwrites previous argument | + | |
- | test(param: 1, param: 2); | + | |
- | // Error: Named parameter $param overwrites previous argument | + | |
- | test(1, param: 2); | + | |
- | </ | + | |
- | + | ||
- | The first case is trivially illegal, because it specifies the same named argument twice. The second case is also illegal, because the positional argument and the named argument refer to the same parameter. | + | |
- | + | ||
- | With the exception of variadic functions discussed below, specifying an unknown parameter name results in an '' | + | |
- | + | ||
- | < | + | |
- | function test($param) { ... } | + | |
- | + | ||
- | // Error: Unknown named parameter $parma | + | |
- | test(parma: "Oops, a typo" | + | |
- | </ | + | |
- | + | ||
- | ==== Variadic functions | + | |
- | + | ||
- | Functions declared as variadic using the '' | + | |
- | + | ||
- | <code php> | + | |
- | function test(...$args) { var_dump($args); | + | |
- | + | ||
- | test(1, 2, 3, a: ' | + | |
- | // [1, 2, 3, " | + | |
- | </ | + | |
- | + | ||
- | The '' | + | |
- | + | ||
- | <code php> | + | |
- | $params = [' | + | |
- | array_fill(...$params); | + | |
- | </ | + | |
- | + | ||
- | Any value with a string key is unpacked as a named argument. Integers keys are treated as normal positional arguments (with the integer value being ignored). Keys that are neither integers or strings (only possible for iterators) result in a '' | + | |
- | + | ||
- | Argument unpacking is also subject to the general rule that positional arguments must always precede named arguments. Both of the following calls throw an '' | + | |
- | + | ||
- | < | + | |
- | array_fill(...[' | + | |
- | array_fill(start_index: | + | |
- | </ | + | |
- | + | ||
- | Furthermore, | + | |
- | + | ||
- | < | + | |
- | test(...$values, $value); // Compile-time error (as before) | + | |
- | test(...$values, paramName: $value); // Compile-time error | + | |
- | </ | + | |
- | + | ||
- | One of the primary use-cases for that variadic/ | + | |
- | + | ||
- | < | + | |
- | function passthru(callable $c, ...$args) { | + | |
- | return $c(...$args); | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | The support for named arguments in both variadics and argument unpacking ensures that this pattern will continue | + | |
- | + | ||
- | ==== func_* | + | |
- | + | ||
- | The '' | + | |
- | + | ||
- | < | + | |
- | function test($a = 0, $b = 1, $c = 2) { | + | |
- | var_dump(func_get_args()); | + | |
- | } | + | |
- | + | ||
- | test(c: 5); | + | |
- | // Will behave exactly the same as: | + | |
- | test(0, 1, 5); | + | |
- | // Which is: | + | |
- | // array(3) { [0] => 0, [1] => 1, [2] => 5 } | + | |
- | </ | + | |
- | The behavior of '' | + | When implementing this feature I found two bugs in php-src tests and the test runner |
- | All three functions are oblivious to the collection of unknown named arguments | + | * In the PHP test runner the strings " |
+ | * In an IMAP test a boolean argument $simpleMessages always got the string " | ||
| | ||
- | The '' | + | Changing |
- | The general philosophy here is that '' | + | While using strict_types |
- | ==== __call() | + | ===== Other boolean coercions in PHP ===== |
- | Unlike '' | + | Typed booleans |
- | < | + | However |
- | class Proxy { | + | |
- | public function __construct( | + | |
- | private object $object, | + | |
- | ) {} | + | |
- | public function __call(string $name, array $args) { | + | |
- | // $name == " | + | |
- | // $args == [1, " | + | |
- | $this-> | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | $proxy = new Proxy(new FooBar); | + | |
- | $proxy-> | + | |
- | </ | + | |
- | + | ||
- | ==== Parameter name changes during inheritance ==== | + | |
- | + | ||
- | Currently, parameter names are not part of the signature-contract. When only positional arguments are used, this is quite reasonable: The name of the parameter is irrelevant to the caller. Named arguments change this. If an inheriting class changes a parameter name, calls using named arguments might fail, thus violating the Liskov substitution principle (LSP): | + | |
- | + | ||
- | <code php> | + | |
- | interface I { | + | |
- | public function test($foo, $bar); | + | |
- | } | + | |
- | + | ||
- | class C implements I { | + | |
- | public function test($a, $b) {} | + | |
- | } | + | |
- | + | ||
- | $obj = new C; | + | |
- | + | ||
- | // Pass params according to I::test() contract | + | |
- | $obj-> | + | |
- | </ | + | |
- | + | ||
- | [[https:// | + | |
- | + | ||
- | * Python and Ruby allow parameter name changes silently, and throw an error during the call. | + | |
- | * C# and Swift introduce a new overload (or error if override is requested). As PHP does not support method overloading, | + | |
- | * Kotlin warns on parameter name change and errors on call. | + | |
- | + | ||
- | Because we are retrofitting named arguments to an old language with a large body of existing code, we do not consider it sensible to unconditionally diagnose parameter name mismatches, especially considering that a lot of old code will never be invoked using named arguments. This RFC proposes two possible approaches to handle this issue, which are described | + | |
- | + | ||
- | === Silently allow parameter name changes === | + | |
- | + | ||
- | The first option is to follow the model of Python or Ruby: PHP will silently accept parameter name changes during inheritance, | + | |
- | + | ||
- | This is a pragmatic approach that acknowledges that named arguments | + | |
- | + | ||
- | As previously mentioned, this approach is also used by some existing languages, most notably Python, which is one of the languages | + | |
- | + | ||
- | === Allow using parameter names from parent methods === | + | |
- | + | ||
- | The alternative is to allow using parameter names from parent methods, as the following example illustrates: | + | |
<PHP> | <PHP> | ||
- | interface I { | + | if ($variable) |
- | public function test($foo, $bar); | + | // the $variable in the if statement is coerced in the following way: |
+ | // - true for a string if it is not empty and not ' | ||
+ | // - true for an int if it is not zero | ||
+ | // - true for a float if it is not zero | ||
+ | // - true for an array if it is not empty | ||
+ | // - always true for a resource | ||
+ | // - always true for an object | ||
+ | // - always false for null | ||
} | } | ||
- | class C implements I { | + | if ($array) { |
- | public function test($a, $b) {} | + | // executed for a non-empty array |
} | } | ||
- | $obj = new C; | + | toBool($array); // TypeError, must be of type bool, array given |
- | + | ||
- | // Pass params according to C::test() contract | + | |
- | $obj-> | + | |
- | // Pass params according to I::test() contract | + | |
- | $obj-> | + | |
</ | </ | ||
- | Here using '' | + | Typed booleans behave differently compared to these expressions because they do not accept arrays, resources, objects |
- | + | ||
- | Names from parent methods are registered as aliases, but not bound to a specific signature. As such, it's possible (though not recommended) | + | |
<PHP> | <PHP> | ||
- | // Use parameter names from both C::test() and I::test() | + | // often used to check if $string is not empty, and it is reasonably clear |
- | $obj-> | + | if ($string) |
- | </ | + | // do something with $string here |
- | + | ||
- | From a design perspective it would be better to forbid such calls, but I don't believe that it is worth the technical and performance cost this would entail. | + | |
- | + | ||
- | There is one problem with this scheme: What happens | + | |
- | + | ||
- | < | + | |
- | interface I { | + | |
- | | + | |
} | } | ||
- | class C implements I { | + | $obj-> |
- | public function test($bar, $foo) {} | + | // is it a value from a form, API or DB that should be '', |
- | } | + | // or is it a mistake because something is missing? |
- | + | ||
- | // Fatal error: Parameter | + | |
- | // | + | |
</ | </ | ||
- | In this case, the LSP inheritance checks will report | + | When giving |
- | + | ||
- | Parameter names from prototype methods can come from a number of sources: | + | |
- | + | ||
- | * Parent methods, including grand parents. | + | |
- | * Interface methods, including implementations of the same method from multiple interfaces. | + | |
- | * Abstract trait methods. | + | |
- | + | ||
- | As such, a single parameter can have a potentially large number of aliases from a large number of prototypes. | + | |
- | + | ||
- | A case that requires special consideration are parameters that are absorbed by a variadic in a child class: | + | |
<PHP> | <PHP> | ||
- | class A { | + | $obj-> |
- | public function method($a) {} | + | $obj->boolProperty = strlen($string) > 0; // instead check that $string is not empty |
- | } | + | |
- | class B extends A { | + | |
- | public function method(...$args) {} | + | |
- | } | + | |
- | class C extends B { | + | |
- | public function method($c = null, ...$args) {} | + | |
- | } | + | |
- | + | ||
- | (new B)->method(a: 42); | + | |
- | (new C)->method(a: 42); | + | |
</ | </ | ||
- | There are principally two ways in which this might behave: | + | ===== filter extension ===== |
- | < | + | The filter extension has its own way to validate booleans |
- | // Option A: | + | |
- | (new B)-> | + | |
- | (new C)-> | + | |
- | // Option B: | + | * " |
- | (new B)-> | + | * if FILTER_NULL_ON_FAILURE is also used, only " |
- | (new C)->method(a: 42); // $c = null, $args = [' | + | |
- | </ | + | This behavior is incompatible with how PHP handles boolean coercions, making it impossible to resolve the behaviors without massive BC breaks. But it does add another argument in favor of this RFC - somebody switching from the filter extension to built-in boolean types has a big chance of accidentally introducing behavior changes in their application: |
- | With option A, we would remember that '' | + | * PHP converts most values to true, while the filter extension converts these values to false (or null) - for example " |
+ | * The deprecation notice | ||
+ | |||
+ | Usages | ||
- | With option B, we instead discard parent parameters that are absorbed in a variadic. This means that the parameter '' | + | ===== Considered alternatives ===== |
- | This RFC proposed | + | It was briefly considered |
- | Overall, while I think this approach | + | Another possibility would have been to also change |
- | ==== Internal functions | + | ===== Implementation notes ===== |
- | Historically, | + | As this is my first RFC and my first contribution to php-src, I mimicked |
- | Since PHP 8.0, it is possible to specify reflectible default values for internal functions, and this has already happened for functions which are bundled with the PHP distribution. This proposal is based on this default value information: | + | ===== Backward Incompatible Changes ===== |
- | However, it is not possible to specify a sensible notion of "default value" | + | The following operations will now emit an '' |
- | <PHP> | + | * Assignment to a typed property of type '' |
- | function array_keys(array $arg, $search_value = UNKNOWN, | + | * Argument for a parameter of type '' |
- | </ | + | * Returning such a value for userland functions declared with a return type of '' |
+ | |||
+ | The actual conversion to a boolean value remains unchanged - anything that was coerced to false before will still be coerced to false, and anything coerced to true will still be coerced to true. | ||
- | The '' | + | The following shows typical ways to avoid a deprecation notice: |
- | + | ||
- | Skipping such a parameter will result in an '' | + | |
<PHP> | <PHP> | ||
- | // This is okay. | + | // Resolution 1: Check for an expected value or range |
- | array_keys($array, search_value: | + | toBool($number > 0); |
- | + | toBool($int === 5); | |
- | // Error: Argument #2 ($search_value) must be passed explicitly, | + | toBool($string === ' |
- | // | + | toBool(strlen($string) > 0); |
- | array_keys($array, strict: true); | + | |
+ | // Resolution | ||
+ | toBool($scalar == true); | ||
+ | |||
+ | // Resolution 3: Explicitly cast the argument | ||
+ | toBool((bool) | ||
</ | </ | ||
- | I believe this is exactly | + | With the many deprecation notices that appeared in PHP 8.0 and 8.1 there is some wariness if more deprecation notices are worth it. These are the arguments why the RFC author thinks it will be worth it without |
- | The disadvantage of this general approach | + | * Each individual case is easy to fix, the easiest (but also least useful) is to loosly compare a value to true ($value == true) instead of directly giving the value to a typed bool |
+ | * Most of the coercions that will lead to a deprecation notice are likely to be unintended and the information given in the notice should make it reasonably clear to a developer whether it is a bug and how to fix it | ||
+ | * bool arguments | ||
+ | * deprecation notices do not demand immediate attention, and the " | ||
+ | |||
+ | ===== Proposed PHP Version ===== | ||
- | The alternative, | + | Next minor version: PHP 8.2. |
- | === Documentation / Implementation mismatches | + | ===== Unaffected |
- | + | ||
- | Currently, the parameter names used in the documentation and the implementation do not always match. If this proposal is accepted, we will synchronize the parameter names between both. This will also involve creating some naming guidelines, such as on the use of casing in parameter names. | + | |
- | + | ||
- | ===== Backwards incompatible changes ===== | + | |
- | + | ||
- | In the narrow sense, this proposal has no backwards-incompatible changes, in that the behavior of existing code remains completely unchanged. | + | |
- | + | ||
- | However, there are two primary complications that may occur when named arguments are used with code that is not prepared to deal with them: | + | |
- | + | ||
- | First, as parameter names are now significant, | + | |
- | + | ||
- | Second, code may not be prepared to deal with unknown named arguments collected into variadics. In most cases this will manifest with the parameter names simply being ignored, which is mostly harmless. | + | |
- | + | ||
- | ===== Alternatives ===== | + | |
- | + | ||
- | There are two primary alternative implementation approaches for named arguments that I'm aware of, which will be briefly discussed in the following. | + | |
- | + | ||
- | First, to make named arguments opt-in. The current RFC allows all functions/ | + | |
- | + | ||
- | The big disadvantage of the opt-in approach is, of course, that named arguments would not work with any existing code (both userland and internal). I think that this would be a big loss to the feature, to the point that it might no longer be worthwhile. In particular, this would lose out on the object initialization use-case (as the syntax would not be usable in most cases), and would not help with old APIs, which tend to be particularly bad offenders when it comes to having many defaulted parameters and boolean flags. | + | |
- | + | ||
- | I think it would be more fruitful to provide an explicit opt-out mechanism, such as a '' | + | |
- | + | ||
- | Second, implementing named arguments as a side-effect of improved array destructuring functionality. As an example, let's return to the '' | + | |
- | + | ||
- | <PHP> | + | |
- | class ParamNode extends Node { | + | |
- | public string $name; | + | |
- | public ExprNode $default; | + | |
- | public TypeNode $type; | + | |
- | public bool $byRef; | + | |
- | public bool $variadic; | + | |
- | + | ||
- | public function __construct(string $name, array $options) { | + | |
- | [ | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | ] = $options; | + | |
- | + | ||
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | parent:: | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | This uses the existing syntax for array destructuring with keys, but additionally assumes support for destructuring default values, as well as destructuring type checks. As an additional step, we could support destructuring directly in the function signature: | + | |
- | + | ||
- | < | + | |
- | class ParamNode extends Node { | + | |
- | public string $name; | + | |
- | public ExprNode $default; | + | |
- | public TypeNode $type; | + | |
- | public bool $byRef; | + | |
- | public bool $variadic; | + | |
- | + | ||
- | public function __construct( | + | |
- | string $name, | + | |
- | array [ | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | " | + | |
- | ], | + | |
- | ) { | + | |
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | parent:: | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | While I think that improvements to array destructuring are worth pursuing, I don't think this covers the named parameter use-case satisfactorily. While this does take care of the type-safety concern, it still requires APIs to be specifically designed around an options array. | + | |
- | + | ||
- | Additionally, | + | |
- | + | ||
- | ===== Future Scope ===== | + | |
- | + | ||
- | ==== Shorthand syntax for matching parameter and variable name ==== | + | |
- | + | ||
- | Especially for constructors, | + | |
- | + | ||
- | < | + | |
- | new ParamNode( | + | |
- | name: $name, | + | |
- | type: $type, | + | |
- | default: $default, | + | |
- | variadic: $variadic, | + | |
- | byRef: $byRef | + | |
- | ); | + | |
- | </ | + | |
- | + | ||
- | Some languages offer special syntax (both for object initialization and destructuring) to avoid repeating the same name twice. Here is how such a syntax could look like in PHP, depending on the chosen named arguments syntax: | + | |
- | + | ||
- | < | + | |
- | new ParamNode(: | + | |
- | new ParamNode(=$name, | + | |
- | new ParamNode(=> | + | |
- | </ | + | |
- | + | ||
- | It should be noted that this problem is not specific to named arguments, and also affects array destructuring: | + | |
- | + | ||
- | < | + | |
- | // What you have to write right now: | + | |
- | [' | + | |
- | </ | + | |
- | + | ||
- | Analogously to the above examples, this could be written as: | + | |
- | + | ||
- | < | + | |
- | [:$x, :$y, :$z] = $point; | + | |
- | [=$x, =$y, =$z] = $point; | + | |
- | [=> $x, => $y, => $z] = $point; | + | |
- | </ | + | |
- | + | ||
- | Finally, this could also be useful for array construction, | + | |
- | + | ||
- | < | + | |
- | return compact(' | + | |
- | + | ||
- | // Could become: | + | |
- | return [:$x, :$y, :$z]; | + | |
- | return [=$x, =$y, =$z]; | + | |
- | return [=> $x, => $y, => $z]; | + | |
- | </ | + | |
- | + | ||
- | If I wanted to put these ideas into a general framework, I think one way to go about this would be as follows: | + | |
- | + | ||
- | * Consider '' | + | |
- | * Consider '': | + | |
- | + | ||
- | Under this proposal, all three of the following would behave identically: | + | |
- | + | ||
- | < | + | |
- | $point = [' | + | |
- | $point = [x: $x, y: $y, z: $z]; | + | |
- | $point = [:$x, :$y, :$z]; | + | |
- | </ | + | |
- | Approaching from this angle, the named argument syntax we should use is '' | + | * Manually casting to boolean will not raise a notice. |
+ | * Strict Type behaviour is unaffected. | ||
+ | * Implicit boolean expressions (as used in if, ternary, logic operators) are not affected. | ||
+ | * FILTER_VALIDATE_BOOLEAN in the filter extension | ||
- | ==== Positional-only | + | ===== Patches |
- | A useful extension of this proposal would be to allow parameters that can only be used positionally, | + | Patch: https:// |
- | ===== Changelog | + | ===== References |
- | * 2020-06-23: Add alternative LSP behavior. | + | Initial mailing list discussion: <https://externals.io/ |
- | * 2020-06-23: Remove syntax as open question, specify use of '':'' | + | RFC mailing list discussion: <https://externals.io/ |
- | * 2020-05-05: RFC picked up again for PHP 8.0. | + | |
- | * 2013-09-09: '' | + | |
rfc/named_params.txt · Last modified: 2022/05/26 15:31 by iquito