[RFC] Named arguments

  110004
May 5, 2020 13:51 nikita.ppv@gmail.com (Nikita Popov)
Hi internals,

I've recently started a thread on resurrecting the named arguments proposal
(https://externals.io/message/109549), as this has come up tangentially in
some recent discussions around attributes and around object ergonomics.

I've now updated the old proposal on this topic, and moved it back under
discussion: https://wiki.php.net/rfc/named_params

Relative to the last time I've proposed this around PHP 5.6 times, I think
we're technically in a much better spot now when it comes to the support
for internal functions, thanks to the stubs work.

I think the recent acceptance of the attributes proposal also makes this a
good time to bring it up again, as phpdoc annotations have historically had
support for named arguments, and this will make migration to the
language-provided attributes smoother.

Regards,
Nikita
  110005
May 5, 2020 14:11 ocramius@gmail.com (Marco Pivetta)
Hey Nikita,

On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I've recently started a thread on resurrecting the named arguments proposal > (https://externals.io/message/109549), as this has come up tangentially in > some recent discussions around attributes and around object ergonomics. > > I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params > > Relative to the last time I've proposed this around PHP 5.6 times, I think > we're technically in a much better spot now when it comes to the support > for internal functions, thanks to the stubs work. > > I think the recent acceptance of the attributes proposal also makes this a > good time to bring it up again, as phpdoc annotations have historically had > support for named arguments, and this will make migration to the > language-provided attributes smoother. >
As mentioned some days ago, I see named parameters as an added liability, rather than values. The rationale of my negativity around the topic being that a diff like following is now to be considered a BC break: ```diff -function foo($parameterName) { /* ... */ } +function foo($newParameterName) { /* ... */ } ``` In addition to that, the feature seems to be especially designed to work with particularly bad API (anything with a gazillion optional parameters, such as all the OpenSSL nightmares in php-src). In practice, the issues around bad API (in this case, bad = lots of optional parameters, maybe even ordered arbitrarily) are fixed by using proper value types and structs or value objects: ```php myHorribleLegacyAndOrganicallyGrownApi( ....MyInfiniteSequenceOfParametersAsProperValidatedStructure::fromArray($stuff) ->toSequentialParameters() ); ``` For the few use-cases where this is needed, the userland solution seems to be sufficient, without introducing catastrophic BC boundary expansions. Unless I'm not seeing an incredible (hidden, to me) value from this functionality, this is a clear -1 from me. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/
  110006
May 5, 2020 15:28 larry@garfieldtech.com ("Larry Garfield")
On Tue, May 5, 2020, at 9:11 AM, Marco Pivetta wrote:
> Hey Nikita, > > On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: > > > Hi internals, > > > > I've recently started a thread on resurrecting the named arguments proposal > > (https://externals.io/message/109549), as this has come up tangentially in > > some recent discussions around attributes and around object ergonomics. > > > > I've now updated the old proposal on this topic, and moved it back under > > discussion: https://wiki.php.net/rfc/named_params > > > > Relative to the last time I've proposed this around PHP 5.6 times, I think > > we're technically in a much better spot now when it comes to the support > > for internal functions, thanks to the stubs work. > > > > I think the recent acceptance of the attributes proposal also makes this a > > good time to bring it up again, as phpdoc annotations have historically had > > support for named arguments, and this will make migration to the > > language-provided attributes smoother. > > > > As mentioned some days ago, I see named parameters as an added liability, > rather than values. > The rationale of my negativity around the topic being that a diff like > following is now to be considered a BC break: > > ```diff > -function foo($parameterName) { /* ... */ } > +function foo($newParameterName) { /* ... */ } > ``` > > In addition to that, the feature seems to be especially designed to work > with particularly bad API (anything with a gazillion optional parameters, > such as all the OpenSSL nightmares in php-src). > In practice, the issues around bad API (in this case, bad = lots of > optional parameters, maybe even ordered arbitrarily) are fixed by using > proper value types and structs or value objects: > > ```php > myHorribleLegacyAndOrganicallyGrownApi( > > ...MyInfiniteSequenceOfParametersAsProperValidatedStructure::fromArray($stuff) > ->toSequentialParameters() > ); > ``` > > For the few use-cases where this is needed, the userland solution seems to > be sufficient, without introducing catastrophic BC boundary expansions. > > Unless I'm not seeing an incredible (hidden, to me) value from this > functionality, this is a clear -1 from me.
I agree that named params can be used as a crutch for poor API design. However, that's not always the case. The biggest use case I see is, as discussed previously, value object constructors. "Pass an entirely untyped and undocumentable array of parameters" is not an sufficient userland solution in my mind, but is the only available alternative right now. Attributes, now that those are a thing, are another very good use case, IMO. The combination of constructor promotion and named params gives us an effective equivalent to structs and struct-creation from Go or Rust or similar languages with a minimum of overhead, allowing for typed, lintable, efficient data structure definitions to more easily replace "big anonymous associative arrays." Those are way worse than the API abuse named parameters could offer. I share your concern that it could get abused; so does anything else though, for that matter. Part of that could be addressed with good documentation and education. If we had to change the approach, then I would be open to limiting them to constructors as that's where the really big use cases are. I don't know if that inconsistency would be worse than allowing them everywhere, though. --Larry Garfield
  110007
May 5, 2020 15:38 ocramius@gmail.com (Marco Pivetta)
Hey Larry,


On Tue, May 5, 2020 at 5:29 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> The biggest use case I see is, as discussed previously, value object > constructors. "Pass an entirely untyped and undocumentable array of > parameters" is not an sufficient userland solution in my mind, but is the > only available alternative right now. >
This is easily mitigated by named constructors ( https://verraes.net/2014/06/named-constructors-in-php/), which are a good/established practice among those using value objects. You can create multiple (meaningful) constructor signatures depending on use-case, which is still a much safer approach than delegating this responsibility to a caller. My example above uses a `::fromArray()` "factory" precisely for that purpose. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/
  110008
May 5, 2020 15:44 kontakt@beberlei.de (Benjamin Eberlei)
On Tue, May 5, 2020 at 4:11 PM Marco Pivetta <ocramius@gmail.com> wrote:

> Hey Nikita, > > On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: > > > Hi internals, > > > > I've recently started a thread on resurrecting the named arguments > proposal > > (https://externals.io/message/109549), as this has come up tangentially > in > > some recent discussions around attributes and around object ergonomics. > > > > I've now updated the old proposal on this topic, and moved it back under > > discussion: https://wiki.php.net/rfc/named_params > > > > Relative to the last time I've proposed this around PHP 5.6 times, I > think > > we're technically in a much better spot now when it comes to the support > > for internal functions, thanks to the stubs work. > > > > I think the recent acceptance of the attributes proposal also makes this > a > > good time to bring it up again, as phpdoc annotations have historically > had > > support for named arguments, and this will make migration to the > > language-provided attributes smoother. > > > > As mentioned some days ago, I see named parameters as an added liability, > rather than values.
> The rationale of my negativity around the topic being that a diff like > following is now to be considered a BC break: > > ```diff > -function foo($parameterName) { /* ... */ } > +function foo($newParameterName) { /* ... */ } > ``` >
> In addition to that, the feature seems to be especially designed to work > with particularly bad API (anything with a gazillion optional parameters, > such as all the OpenSSL nightmares in php-src). >
I see the PHP internal API as the main benefactor of this feature over userland code imho. Internals realistically couldn't get "smaller APIs" without adding many new functions instead, for example htmlspecialchars. The trade off from API design perspective is either providing a more complex API by having function permutations or an OOP API, which in turn developers have more trouble remembering. Or the more beginner approachable way is providing a single function that is easy to remember exists, but in turn does more things and may require a look into the documentation to see all the options. Given we have this API already in PHP and this will not realistically change, I think named params will be a big win.
> In practice, the issues around bad API (in this case, ba > d = lots of
> optional parameters, maybe even ordered arbitrarily) are fixed by using > proper value types and structs or value objects: > > ```php > myHorribleLegacyAndOrganicallyGrownApi( > > > ...MyInfiniteSequenceOfParametersAsProperValidatedStructure::fromArray($stuff) > ->toSequentialParameters() > ); > ``` > > For the few use-cases where this is needed, the userland solution seems to > be sufficient, without introducing catastrophic BC boundary expansions. >
In this example $stuff is an array which has keys. Presumably $parameterName => $newParameterName would be equivalent to a change of key names. Or property names on MyInfiniteSequenceOfParametersAsProperValidatedStructure. So you get the BC break of renaming anyways in your example. Now as a provider of a public API (library, framework) you can still counter this with a BC layer: function foo($newParameterName, $parameterName = null) { if ($parameterName !== null) { $newParameterName = $parameterName; trigger_error("Using paramaterName as named parameter is deprecated, use newParameterName instead.", E_USER_DEPRECATED); } /... } this looks ugly (because we haven't seen it before), but is not more or less complex than the same BC / deprecation layer in fromArray with changed option: public static function fromArray(array $options) { if (isset($options['parameter_name'])) { $options['new_parameter_name'] = $options['parameter_name']; trigger_error("Using paramaeter_name as option is deprecated, use new_parameter_name instead.", E_USER_DEPRECATED); } } So nothing much would technically change for someone who cares about BC for their users.
> > > Unless I'm not seeing an incredible (hidden, to me) value from this > functionality, this is a clear -1 from me. > > Marco Pivetta > > http://twitter.com/Ocramius > > http://ocramius.github.com/
  110009
May 5, 2020 15:55 ocramius@gmail.com (Marco Pivetta)
Hey Benjamin,

On Tue, May 5, 2020 at 5:44 PM Benjamin Eberlei <kontakt@beberlei.de> wrote:

> I see the PHP internal API as the main benefactor of this feature over > userland code imho. Internals realistically couldn't get "smaller APIs" > without adding many new functions instead, for example htmlspecialchars. >
It takes a couple minutes to write a non-broken `htmlspecialchars` function to use in your own project. Besides that, designing a language feature to accommodate pre-existing bad design smells a lot. There are libraries out there trying to provide better API to what is provided by PHP-SRC, such as https://github.com/thecodingmachine/safe: that's an easy win, by creating **adapters** for bad API, instead of **language-level constructs** that will haunt us forever.
> The trade off from API design perspective is either providing a more > complex API by having function permutations or an OOP API, which in turn > developers have more trouble remembering. Or the more beginner approachable > way is providing a single function that is easy to remember exists, but in > turn does more things and may require a look into the documentation to see > all the options. Given we have this API already in PHP and this will not > realistically change, I think named params will be a big win. >
Sorry, but no: you provide multiple constructors, each with a limited set of arguments, each guaranteeing constraints that depend on context. For instance, a `fromArray()` may check for key existence, while `fromJson()` may use a JSON-Schema definition to check that all is as required, and `fromXmlPayload()` may apply an XSD validator, and so on... Number, type and lifecycle/mutability of parameters may change depending on named constructor, and the reduced scope makes it very easy to document requirements, and to make the API ergonomic (yes, it's actually easier to use: surprise!). In this example $stuff is an array which has keys. Presumably
> $parameterName => $newParameterName would be equivalent to a change of key > names. Or property names on > MyInfiniteSequenceOfParametersAsProperValidatedStructure. So you get the BC > break of renaming anyways in your example. >
Correct: that's a BC break on my `MyInfiniteSequenceOfParametersAsProperValidatedStructure` which does not depend on the definition of `myHorribleLegacyAndOrganicallyGrownApi()`. It is still a very well isolated BC scenario on a single `::fromArray()` named ctor, compared to introducing a language-level BC boundary that crosses all existing code out there. Now as a provider of a public API (library, framework) you can still
> counter this with a BC layer: > > function foo($newParameterName, $parameterName = null) { > if ($parameterName !== null) { > $newParameterName = $parameterName; > trigger_error("Using paramaterName as named parameter is > deprecated, use newParameterName instead.", E_USER_DEPRECATED); > } > /... > } > > this looks ugly (because we haven't seen it before), but is not more or > less complex than the same BC / deprecation layer in fromArray with changed > option: > > public static function fromArray(array $options) { > if (isset($options['parameter_name'])) { > $options['new_parameter_name'] = $options['parameter_name']; > trigger_error("Using paramaeter_name as option is deprecated, use > new_parameter_name instead.", E_USER_DEPRECATED); > } > } > > So nothing much would technically change for someone who cares about BC > for their users. >
In first place, I would rarely ever design a `::fromArray()` API that isn't related to serialization or configuration loading (which is another kind of serialization, after all). I would not look forward to maintaining BC compliance like with `foo($newParameterName, $parameterName = null)`, and I do not see which added value counters this massive amount of maintenance overload. We're chasing a non-existing problem (or feature requirement) by adding other problems that are far greater. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/
  110010
May 5, 2020 16:27 rowan.collins@gmail.com (Rowan Tommins)
On Tue, 5 May 2020 at 16:56, Marco Pivetta <ocramius@gmail.com> wrote:

> > Sorry, but no: you provide multiple constructors, each with a limited set > of arguments, each guaranteeing constraints that depend on context. > > For instance, a `fromArray()` may check for key existence, while > `fromJson()` may use a JSON-Schema definition to check that all is as > required, and `fromXmlPayload()` may apply an XSD validator, and so on... >
Maybe it's just an unfortunate choice of example, but my reaction was the same as Benjamin's: "fromArray" doesn't look like "a named constructor with a limited set of arguments", it looks like a hack used to emulate named parameters by setting them as keys in an array which then has to be manually documented and validated. The only fully generic alternative I know of that can be properly checked by static analysis is some kind of builder API: myHorribleLegacyAndOrganicallyGrownApi( ...(new MyInfiniteSequenceOfParametersBuilder) ->setFoo($foo) ->setBar($bar) // ... etc ->toSequentialParameters() ); I do sympathise with the problems of making parameter names part of the compatibility contract, and until recently have always argued it should be opt-in at the call-site. I've been somewhat won round to the idea that that leaves the migration too painfully slow. Regards, -- Rowan Tommins [IMSoP]
  110011
May 5, 2020 16:59 theodorejb@outlook.com (Theodore Brown)
On Tue, May 5, 2020 at 9:11 AM Marco Pivetta <ocramius@gmail.com> wrote:

> As mentioned some days ago, I see named parameters as an added liability, > rather than values. > The rationale of my negativity around the topic being that a diff like > following is now to be considered a BC break: > > ```diff > -function foo($parameterName) { /* ... */ } > +function foo($newParameterName) { /* ... */ } > ```
If the function is part of your own codebase, an IDE can automatically update function calls when renaming a parameter. For functions that are part of a public API, yes, parameters shouldn't be renamed outside of major versions. But in my experience, it's rare for function parameters that are part of public APIs to be renamed. Do you have many real-world examples of where this had to happen? Also, I haven't heard of this being a big problem in other languages with named arguments like Python.
> the feature seems to be especially designed to work with particularly > bad API (anything with a gazillion optional parameters, such as all > the OpenSSL nightmares in php-src).
Why do you say that? For me this feature would be extremely helpful when calling constructors that have several optional arguments (e.g. similar to the `ParamNode` example in the RFC). Maybe you consider this a bad API, but it's a very common pattern, and named arguments will make it far easier to work with than the typical alternative of converting arguments to an options array (which is itself a BC break and has many other downsides as the RFC points out).
> In practice, the issues around bad API (in this case, bad = lots of > optional parameters, maybe even ordered arbitrarily) are fixed by > using proper value types and structs or value objects
How can lots of optional parameters be "fixed" in practice with structs? E.g. how would they improve the `ParamNode` example in the RFC? Would it even be possible to migrate existing classes like this to structs without a BC break? Personally I think it's a lot simpler and more consistent to have named arguments which can be used anywhere, including in attributes, vs. having to implement and learn a new syntax for structs which only works for that one use case (and may not be easy to migrate to). Best regards, Theodore Brown
  110012
May 5, 2020 17:18 ocramius@gmail.com (Marco Pivetta)
Hey Theodore,
<http://ocramius.github.com/>


On Tue, May 5, 2020 at 6:59 PM Theodore Brown <theodorejb@outlook.com>
wrote:

> On Tue, May 5, 2020 at 9:11 AM Marco Pivetta <ocramius@gmail.com> wrote: > > > As mentioned some days ago, I see named parameters as an added liability, > > rather than values. > > The rationale of my negativity around the topic being that a diff like > > following is now to be considered a BC break: > > > > ```diff > > -function foo($parameterName) { /* ... */ } > > +function foo($newParameterName) { /* ... */ } > > ``` > > If the function is part of your own codebase,
BC Breaks that are intra-codebase are rarely every a problem nor a point of discussion.
> an IDE can automatically > update function calls when renaming a parameter. For functions that are > part of a public API, yes, parameters shouldn't be renamed outside of > major versions. But in my experience, it's rare for function parameters > that are part of public APIs to be renamed. Do you have many real-world > examples of where this had to happen? >
Parameter name changes are very much normal, since (as I already posted in /r/php), naming is hard, and getting it right is not a one-shot effort. Also, I haven't heard of this being a big problem in other languages
> with named arguments like Python. >
Good question: I guess parameter renames are avoided on purpose? Not familiar with the python community myself, sorry. Why do you say that? For me this feature would be extremely helpful
> when calling constructors that have several optional arguments (e.g. > similar to the `ParamNode` example in the RFC). Maybe you consider > this a bad API,
I do in fact consider the `ParamNode` example in that API to be a bad candidate for named parameters, since I'd make all of the arguments in that signature required anyway: defaults aren't meaningful anyway, as the AST library where it comes from has all the context to instantiate them all (the caller, being the parser, will have all the parameters anyway). If you were to instantiate a `ParamNode` from, for example, a `ReflectionParameter`, you would probably add named ctor (or an indepentent factory, if in external supporting domain) to pass all parameters as well. If you were to add more optional fields, or use this in a context of an AST builder (BTW, nikic/php-parser has facilities for that already) a mutator would be efficient too: ```php class ParamNode extends Node { public function asReference(): static { $instance = clone $this; $instance->byRef = true; return $instance; } ``` but it's a very common pattern, and named arguments
> will make it far easier to work with than the typical alternative of > converting arguments to an options array (which is itself a BC break > and has many other downsides as the RFC points out). >
You can add as many named constructors as you want: adding `ParamNode::fromArray()` is possible.
> > In practice, the issues around bad API (in this case, bad = lots of > > optional parameters, maybe even ordered arbitrarily) are fixed by > > using proper value types and structs or value objects > > How can lots of optional parameters be "fixed" in practice with structs? > E.g. how would they improve the `ParamNode` example in the RFC? Would > it even be possible to migrate existing classes like this to structs > without a BC break? >
It is possible to add more named constructors in a BC compliant way, while it is hard to change existing public constructors: * `::fromArray()` * `::fromReflectionProperty()` * `::make(string $name, ?ExprNode $default, ?TypeNode $type, bool $byRef, bool $variadic)` * `::makeForPHP53(string $name, ?ExprNode $default, ?TypeNode $type, bool $byRef)` - no variadic support? No problem :-) * `::makeWithCodeLocation(string $name, ?ExprNode $default, ?TypeNode $type, bool $byRef, bool $variadic, CodeCoordinates $coordinates)` All of the above are possible BC-friendly growth directions for such kind of data structure. They also provide better type definitions, context, naming and invariants than a single signature that has to cover all use-cases at once. Personally I think it's a lot simpler and more consistent to have named
> arguments which can be used anywhere, including in attributes, vs. > having to implement and learn a new syntax for structs which only works > for that one use case (and may not be easy to migrate to). >
No new syntax: we already have the tools. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/
  110016
May 5, 2020 17:59 rowan.collins@gmail.com (Rowan Tommins)
On Tue, 5 May 2020 at 18:19, Marco Pivetta <ocramius@gmail.com> wrote:

> > I do in fact consider the `ParamNode` example in that API to be a bad > candidate for named parameters, since I'd make all of the arguments in that > signature required anyway: defaults aren't meaningful anyway, as the AST > library where it comes from has all the context to instantiate them all > (the caller, being the parser, will have all the parameters anyway). >
Parameters don't need to be optional to benefit from named syntax; they just need to be hard to parse by a human writing (and reading!) the call. An obvious example is PHP's needle/haystack inconsistencies that everyone loves to hate. If all parameters were named, strpos(haystack: $content, needle: 'hello') would be fine and I wouldn't have to look up in the manual to check I had them in the right order. An interesting example is network protocols originating from contexts where parameters are named. Take AMQP, the messaging protocol used by RabbitMQ. In PHP, you would currently write something like this to declare a queue in no-wait mode: $channel->queue_declare('hello', false, true, false, false, true); In Python (or Ruby, or C#, or Elixir, or...) you can make it much more readable *even when providing all the arguments*, and don't have to remember what order things go in: channel.queue_declare(queue='hello', durable=True, nowait=True, passive=False, exclusive=False, auto_delete=False) Those aren't arbitrary names chosen by the Python client, either; they're listed in the protocol spec: https://www.rabbitmq.com/amqp-0-9-1-quickref.html#queue.declare queue.declare(short reserved-1, queue-name queue, bit passive, bit durable, bit exclusive, bit auto-delete, no-wait no-wait, table arguments) ➔ declare-ok Incidentally, this leads to the one part of John Bafford's counter-proposal I would be fundamentally opposed to:
> Parameters are also positional. foo(a:..., b: ...) and foo(b: ..., a: ....) are two completely different functions, by name. If you have foo(a:b:)
defined, then you cannot call it as foo(b: ..., a: ...). Re-ordering is, IMO, a key advantage of named parameters, so banning it feels both arbitrary and counter-productive. Regards, -- Rowan Tommins [IMSoP]
  110018
May 5, 2020 18:21 nikita.ppv@gmail.com (Nikita Popov)
On Tue, May 5, 2020 at 7:19 PM Marco Pivetta <ocramius@gmail.com> wrote:

> Hey Theodore, > > > > On Tue, May 5, 2020 at 6:59 PM Theodore Brown <theodorejb@outlook.com> > wrote: > >> On Tue, May 5, 2020 at 9:11 AM Marco Pivetta <ocramius@gmail.com> wrote: >> >> > As mentioned some days ago, I see named parameters as an added >> liability, >> > rather than values. >> > The rationale of my negativity around the topic being that a diff like >> > following is now to be considered a BC break: >> > >> > ```diff >> > -function foo($parameterName) { /* ... */ } >> > +function foo($newParameterName) { /* ... */ } >> > ``` >> >> If the function is part of your own codebase, > > > BC Breaks that are intra-codebase are rarely every a problem nor a point > of discussion. > > > > >> an IDE can automatically >> update function calls when renaming a parameter. For functions that are >> part of a public API, yes, parameters shouldn't be renamed outside of >> major versions. But in my experience, it's rare for function parameters >> that are part of public APIs to be renamed. Do you have many real-world >> examples of where this had to happen? >> > > Parameter name changes are very much normal, since (as I already posted in > /r/php), naming is hard, and getting it right is not a one-shot effort. > > Also, I haven't heard of this being a big problem in other languages >> with named arguments like Python. >> > > Good question: I guess parameter renames are avoided on purpose? Not > familiar with the python community myself, sorry. > > Why do you say that? For me this feature would be extremely helpful >> when calling constructors that have several optional arguments (e.g. >> similar to the `ParamNode` example in the RFC). Maybe you consider >> this a bad API, > > > I do in fact consider the `ParamNode` example in that API to be a bad > candidate for named parameters, since I'd make all of the arguments in that > signature required anyway: defaults aren't meaningful anyway, as the AST > library where it comes from has all the context to instantiate them all > (the caller, being the parser, will have all the parameters anyway). > > If you were to instantiate a `ParamNode` from, for example, a > `ReflectionParameter`, you would probably add named ctor (or an indepentent > factory, if in external supporting domain) to pass all parameters as well. > > If you were to add more optional fields, or use this in a context of an > AST builder (BTW, nikic/php-parser has facilities for that already) a > mutator would be efficient too: > > ```php > class ParamNode extends Node { > public function asReference(): static > { > $instance = clone $this; > > $instance->byRef = true; > > return $instance; > } > ``` >
Reminds me of the saying: Design patterns are just standardized workarounds for language limitations. Builders and withers? Those are not, intrinsically, good code. They are workarounds for lack of good object initialization support. I should not have to implement a large amount of builder boilerplate to make the construction of simple objects safe and ergonomic. We're not talking about some kind of complex multi-stage construction logic here, but the construction of what essentially amounts to a value object. Generally, named arguments really change what constitutes a good API and what doesn't. Things like boolean flags to functions are considered bad design *because* we do not have named arguments. If I pick out some random Python API, say subprocess.run()... subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None) .... and show that to a PHP developer, they're probably going to tell me that this is horrible API design. They would, of course, be wrong. It's reasonable API design, just in a language that supports named arguments. Anyway. Your point that named arguments expand the API surface has been acknowledged. I don't think this issue is really avoidable, it's a rather fundamental trade-off of named parameters. I do think you're a bit quick in jumping to conclusions. It's not like this problem doesn't exist in (nearly) every language supporting named arguments. There are some things we can do to mitigate this though. One that we recently discussed in chat is to allow methods to change parameters during inheritance, and allow named arguments to refer to the parameter names of the parent method as well. Renaming parameters in a way that causes conflicts (same parameter name at different position) would cause an LSP error. I'm not entirely convinced this is the best approach yet, but this does address some concerns (including the "interface extraction" concern you mention on reddit). Another is to allow specifying the public parameter name and the private parameter variable name separately, as is possible in Swift. This would allow changing "parameter" names arbitrarily, without breaking the public API. This would be a pretty direct counter to your concern, but I'm not really sure that your concern is important enough to warrant the additional weight in language complexity. I've never used Swift myself, so maybe this is actually awesome and I just don't know it. Regards, Nikita
  110019
May 5, 2020 18:33 ocramius@gmail.com (Marco Pivetta)
On Tue, May 5, 2020 at 8:22 PM Nikita Popov ppv@gmail.com> wrote:

> Builders and withers? Those are not, intrinsically, good code. They are > workarounds for lack of good object initialization support. I should not > have to implement a large amount of builder boilerplate to make the > construction of simple objects safe and ergonomic. We're not talking about > some kind of complex multi-stage construction logic here, but the > construction of what essentially amounts to a value object. >
They are not good code, but they are reasonable code, in which each call is an atomic state creation/mutation. Generally, named arguments really change what constitutes a good API and
> what doesn't. Things like boolean flags to functions are considered bad > design *because* we do not have named arguments. If I pick out some random > Python API, say subprocess.run()... >
You got multiple APIs within a single massive signature: may as well start counting the permutations of possible characters you pass to an endpoint, and what you get in the end is `eval()` :-P
> subprocess.run(args, *, stdin=None, input=None, stdout=None, > stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, > check=False, encoding=None, errors=None, text=None, env=None, > universal_newlines=None) > > ... and show that to a PHP developer, they're probably going to tell me > that this is horrible API design. They would, of course, be wrong. It's > reasonable API design, just in a language that supports named arguments. >
There are so many ways in which the above fails in different ways (besed on flags) that I can't even start to reason about the resulting chaos. Would rather consume a dozen of differently named (curried) versions of this. Similar to the above: might as well uncurry all the defined API of a program into a single `function (string, array $args)` (which is what the PHP engine or CPUs doe at low-level, obviously).
  110021
May 5, 2020 18:43 mike@newclarity.net (Mike Schinkel)
On May 5, 2020, at 2:33 PM, Marco Pivetta <ocramius@gmail.com> wrote:
> > On Tue, May 5, 2020 at 8:22 PM Nikita Popov ppv@gmail.com> wrote: > >> subprocess.run(args, *, stdin=None, input=None, stdout=None, >> stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, >> check=False, encoding=None, errors=None, text=None, env=None, >> universal_newlines=None) >> >> ... and show that to a PHP developer, they're probably going to tell me >> that this is horrible API design. They would, of course, be wrong. It's >> reasonable API design, just in a language that supports named arguments. >> > > There are so many ways in which the above fails in different ways (besed on > flags) that I can't even start to reason about the resulting chaos.
That looks like a perfect use-case for table-based unit-testing.
> Would rather consume a dozen of differently named (curried) versions of > this.
And I would rather have one function/method with many parameters than many many different named functions/methods. Easier to learn, easier to remember, and easier to define a standard for function/method naming. Different strokes, different folks. #fwiw -Mike
  110020
May 5, 2020 18:37 kjarli@gmail.com (Lynn)
On Tue, May 5, 2020 at 8:22 PM Nikita Popov ppv@gmail.com> wrote:

> Anyway. Your point that named arguments expand the API surface has been > acknowledged. I don't think this issue is really avoidable, it's a rather > fundamental trade-off of named parameters. I do think you're a bit quick in > jumping to conclusions. It's not like this problem doesn't exist in > (nearly) every language supporting named arguments. > > There are some things we can do to mitigate this though. One that we > recently discussed in chat is to allow methods to change parameters during > inheritance, and allow named arguments to refer to the parameter names of > the parent method as well. Renaming parameters in a way that causes > conflicts (same parameter name at different position) would cause an LSP > error. I'm not entirely convinced this is the best approach yet, but this > does address some concerns (including the "interface extraction" concern > you mention on reddit). > > Another is to allow specifying the public parameter name and the private > parameter variable name separately, as is possible in Swift. This would > allow changing "parameter" names arbitrarily, without breaking the public > API. This would be a pretty direct counter to your concern, but I'm not > really sure that your concern is important enough to warrant the additional > weight in language complexity. I've never used Swift myself, so maybe this > is actually awesome and I just don't know it. > > Regards, > Nikita >
Hi, If I understand this correctly, something like this could be done? ``` // current version, argument name = "firstArgument" function example(string $firstArgument) {} // name changes, bc can be kept, argument name = "firstArgument" and "argument" function example(string firstArgument: $argument) {} ``` This would make it possible to keep backwards compatibility, and perhaps even trigger a notice when called with the old name? Regards, Lynn
  110022
May 5, 2020 18:49 mike@newclarity.net (Mike Schinkel)
> On May 5, 2020, at 2:37 PM, Lynn <kjarli@gmail.com> wrote: > > If I understand this correctly, something like this could be done? > > ``` > // current version, argument name = "firstArgument" > function example(string $firstArgument) {} > // name changes, bc can be kept, argument name = "firstArgument" and > "argument" > function example(string firstArgument: $argument) {} > ``` > This would make it possible to keep backwards compatibility, and perhaps > even trigger a notice when called with the old name? >
I was going to suggest similar, but with the `as` keyword similar to the use in the `use` statement: function example( string $argument as firstArg ) {} -Mike
  110041
May 6, 2020 12:40 nikita.ppv@gmail.com (Nikita Popov)
On Tue, May 5, 2020 at 8:38 PM Lynn <kjarli@gmail.com> wrote:

> > > On Tue, May 5, 2020 at 8:22 PM Nikita Popov ppv@gmail.com> wrote: > >> Anyway. Your point that named arguments expand the API surface has been >> acknowledged. I don't think this issue is really avoidable, it's a rather >> fundamental trade-off of named parameters. I do think you're a bit quick >> in >> jumping to conclusions. It's not like this problem doesn't exist in >> (nearly) every language supporting named arguments. >> >> There are some things we can do to mitigate this though. One that we >> recently discussed in chat is to allow methods to change parameters during >> inheritance, and allow named arguments to refer to the parameter names of >> the parent method as well. Renaming parameters in a way that causes >> conflicts (same parameter name at different position) would cause an LSP >> error. I'm not entirely convinced this is the best approach yet, but this >> does address some concerns (including the "interface extraction" concern >> you mention on reddit). >> >> Another is to allow specifying the public parameter name and the private >> parameter variable name separately, as is possible in Swift. This would >> allow changing "parameter" names arbitrarily, without breaking the public >> API. This would be a pretty direct counter to your concern, but I'm not >> really sure that your concern is important enough to warrant the >> additional >> weight in language complexity. I've never used Swift myself, so maybe this >> is actually awesome and I just don't know it. >> >> Regards, >> Nikita >> > > Hi, > > If I understand this correctly, something like this could be done? > > ``` > // current version, argument name = "firstArgument" > function example(string $firstArgument) {} > // name changes, bc can be kept, argument name = "firstArgument" and > "argument" > function example(string firstArgument: $argument) {} > ``` > This would make it possible to keep backwards compatibility, and perhaps > even trigger a notice when called with the old name? >
Yes, that's what I had in mind. However, after thinking about it a bit, I don't think adding such functionality is worthwhile in PHP. As Rowan explained in another mail, the named arguments functionality in Swift is quite distinct from named arguments in other languages. It is used in a way that makes it more common for external labels to differ from internal names. For the rare case where something like this would be useful in PHP, we already have function example(string $firstArgument) { $argument = $firstArgument; // ... } as an existing way to internally rename an argument. If we had reason to believe that this was commonly needed, then introducing special syntax for it might make sense. But I don't think think we have such evidence. We do have some evidence to the contrary in that Swift is the only mainstream language I'm aware of that has felt it necessary to introduce such a syntax (and as mentioned, Swift's "named parameters" are really more like "named functions where part of the name is inside the parameter list"). Regards, Nikita
  110023
May 5, 2020 19:43 jbafford@zort.net (John Bafford)
On May 5, 2020, at 14:21, Nikita Popov ppv@gmail.com> wrote:
> > Another is to allow specifying the public parameter name and the private > parameter variable name separately, as is possible in Swift. This would > allow changing "parameter" names arbitrarily, without breaking the public > API. This would be a pretty direct counter to your concern, but I'm not > really sure that your concern is important enough to warrant the additional > weight in language complexity. I've never used Swift myself, so maybe this > is actually awesome and I just don't know it. > > Regards, > Nikita
The proposal in my earlier email is very much inspired from Swift. I think having distinct outside and inside names helps greatly with creating expressive and readable APIs. The most expressive name for a thing might differ greatly between inside and outside contexts. Also, the name of a parameter as used by the inside of a function should be an implementation detail; there's no reason it should have to be exposed to the outside. For example, Swift's Array has the following two methods (slightly simplified): func firstIndex(of element: Element) -> Int? func firstIndex(where predicate: (Element) -> Bool) -> Int? You call them like: array.firstIndex(of: someElement) array.firstIndex(where: { $0 == someElement }) From the outside, firstIndex(of: ...) is more readable than firstIndex(element: ...) because it reads more like natural language. Same with firstIndex(where: ...) vs. firstIndex(predicate: ...). On the inside, calling it "element" and "predicate" help you focus on what it _is_, without worrying about calling it something that also retains meaning to callers. If you at some point decided to have a mass-renaming of "predicate" to "test", you could change that everywhere in your library without forcing any sort of change to users, because predicate is "on the inside". And both of them starting with 'firstIndex(' gives them a clear relationship; they generally do the same thing, just with a type of input. With php currently, you would probably implement those as: function firstIndexOfElement($element) function firstIndexWithPredicate($predicate) $collection->firstIndexOfElement($element); $collection->firstIndexWithPredicate($someCallable); But I think that's more wordy, and now you have either a BC break or need to add a new function if you decided to rename all instances of "predicate" to "test". A more readable API might look like this: $collection->firstIndex(of: $element); $collection->firstIndex(where: $someCallback); which is less typing, presuming your IDE didn't offer autocomplete for you anyway. -John
  110015
May 5, 2020 17:27 mails@thomasbley.de (Thomas Bley)
> Marco Pivetta <ocramius@gmail.com> hat am 5. Mai 2020 um 16:11 geschrieben: > > Hey Nikita, > On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: > > Hi internals,> I've recently started a thread on resurrecting the named arguments proposal(https://externals.io/message/109549), as this has come up tangentially insome recent discussions around attributes and around object ergonomics.> I've now updated the old proposal on this topic, and moved it back underdiscussion: https://wiki.php.net/rfc/named_params> Relative to the last time I've proposed this around PHP 5.6 times, I thinkwe're technically in a much better spot now when it comes to the supportfor internal functions, thanks to the stubs work.> I think the recent acceptance of the attributes proposal also makes this agood time to bring it up again, as phpdoc annotations have historically hadsupport for named arguments, and this will make migration to thelanguage-provided attributes smoother. > As mentioned some days ago, I see named parameters as an added liability,rather than values.The rationale of my negativity around the topic being that a diff likefollowing is now to be considered a BC break: > ```diff-function foo($parameterName) { /* ... */ }+function foo($newParameterName) { /* ... */ }``` > In addition to that, the feature seems to be especially designed to workwith particularly bad API (anything with a gazillion optional parameters,such as all the OpenSSL nightmares in php-src).In practice, the issues around bad API (in this case, bad = lots ofoptional parameters, maybe even ordered arbitrarily) are fixed by usingproper value types and structs or value objects: > ```phpmyHorribleLegacyAndOrganicallyGrownApi( > ...MyInfiniteSequenceOfParametersAsProperValidatedStructure::fromArray($stuff)->toSequentialParameters());``` > For the few use-cases where this is needed, the userland solution seems tobe sufficient, without introducing catastrophic BC boundary expansions. > Unless I'm not seeing an incredible (hidden, to me) value from thisfunctionality, this is a clear -1 from me. > Marco Pivetta > http://twitter.com/Ocramius > > http://ocramius.github.com/
Hi, I think it's a valid point that changing parameter names can be a BC break. In case the type declaration or the semantic of the parameter changes, it's good to let the code break. I guess that tools for static code analysis will be able to detect parameter name changes? Regards Thomas
  110014
May 5, 2020 17:27 jbafford@zort.net (John Bafford)
Hi Nikita,

> On May 5, 2020, at 09:51, Nikita Popov ppv@gmail.com> wrote: > > Hi internals, > > I've recently started a thread on resurrecting the named arguments proposal > (https://externals.io/message/109549), as this has come up tangentially in > some recent discussions around attributes and around object ergonomics. > > I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params > > Relative to the last time I've proposed this around PHP 5.6 times, I think > we're technically in a much better spot now when it comes to the support > for internal functions, thanks to the stubs work. > > I think the recent acceptance of the attributes proposal also makes this a > good time to bring it up again, as phpdoc annotations have historically had > support for named arguments, and this will make migration to the > language-provided attributes smoother. > > Regards, > Nikita
I very much do like the idea of named parameters, but I do not like the specific proposal in the linked RFC at all. I think that treating named parameters simply as syntactic sugar is not a good approach. If we're going to add named parameters, I think they should actually be significant in their own right. As such, please allow me to present an alternative. I would propose the following opt-in mechanism, which would work by treating the parameter names as *part of the function name*. Doing it this way I _think_ should allow for no backwards compatibility issues with existing code and would especially give library authors full control over the naming and presentation of their APIs. Here is how it would work: Let's start with an ordinary function: function foo(int $a, int $b) : void {} and then add named parameters: function foo(a: int $a, b: int $b) : void {} These are now two _completely different functions_. The first is named 'foo'. The second is named 'foo(a:b:)'. They can both coexist in the same namespace. This would solve two big classes of backwards compatibility issues. First, adding the named-parameter function would not require any changes to any existing code. If the named-parameter function were to become the preferred version, then whatever library implements it could easily forward calls and add a deprecation warning to the old function: function foo(int $a, int $b) : void { foo(a: $a, b: $b); } Second, by having separate public and internal parameter names, it becomes possible to evolve APIs without breaking existing users. If we start from our original two functions above and decide to fix the poorly-named parameters, we could make these changes and continue to have backwards compatibility with all existing users: function foo(hours: int $hours, minutes: int $minutes) : void {} function foo(a: int $hours, b: int $minutes) { foo(hours: $hours, minutes: $minutes } function foo($hours, $minutes) { foo(hours: $hours, minutes: $minutes } Changing the external parameter name counts as changing the function's name, which means it's an entirely different function. (Which lets us also leave the old-named function there as a fallback.) Changing the internal parameter name has no effect to the outside, and can be changed at will. This also solves the problem with overloading: you can change the internal parameter names all you like, so long as you keep the external parameter name - which is part of your declared API. Because the parameter names are part of the function name, you would *not* be able to pick and choose which parameter names you use. If the function declares parameter names, they must be used at the call site. Parameters are also positional. foo(a:..., b: ...) and foo(b: ..., a: ....) are two completely different functions, by name. If you have foo(a:b:) defined, then you cannot call it as foo(b: ..., a: ...). Both of these last two provisions are to eliminate ambiguity and create consistency in the call site. Otherwise we could easily wind up with confusion like in the implode and explode parameter orderings. You could allow having nameless parameters by doing this: function bar(_: int $one, two: int $two) {} bar(1, two: 2); The "nameless" parameters would still be explicitly named ("_"), but users would not have to provide that name at the call site. It would be permissible to mix named and unnamed parameters, though style guides want to discourage that as there's probably few use-cases where this makes sense: function baz(holiday: string $name, _: string $date, color: string $color) {} baz(holiday: 'Star Wars Day', 'May 4', color: 'purple'); There might be complexity around named and unnamed overlap with this special case: function overlap($a, $b); //nominally named 'foo' function overlap(_: $a, _: $b); //nominally named 'foo(_:_)' In which case both functions would normally be called with the exact same syntax. We could declare that, either the second form is explicitly disallowed; or it is allowed, but both may not exist in the same scope. (I would prefer the latter, because it allows the function to be explicitly referenced by symbol; see future-scope musings below.) Named parameters will still all be positional, so you could still do something like: call_user_func_array('baz:holiday:_:color', ['Star Wars Day', 'May 4', 'purple']); Because named parameters are part of the function name, that would also allow us to have what would appear to be name-based overloading, so we could have: function __construct(name: string $name, ...) {} function __construct(dto: SomeDTO $data) {} $foo = new Holiday(name: 'Star Wars', ...): $bar = new Holiday(dto: $formData); This would be very useful for collections classes, where you could have: $collection->insert($object): $collection->insert(element: $object); //same as above, but more explicit $collection->insert(contentsOf: $anotherCollection); You could also use this to "solve" some of the more confusing php standard library parameter ordering, entirely in userspace (or as part of the stdlib without BC breaks), by defining new helper functions: function strpos(haystack: string $haystack, needle: $needle, offset: int $offset = 0) : int {...} function strpos(needle: $needle, haystack: string $haystack, offset: int $offset = 0) : int {...} or even, reimagining the naming to be more like reading a sentence: strpos($string, in: $someArray); position(of: $string, in: $someArray) str_repeat($string, count: 5); implode($array, with: $glue); explode($string, delimiter: $delimiter); This means that rather than a one-size-fits-all approach for _every_ method in the standard library, we could, on a case-by-case basis, add variants for where named parameters are explicitly helpful. It would not be necessary to do this all-at-once; unaudited functions and methods would continue to be used exactly as they always have been. New functions could offer either named-parameters and no-named-parameters versions, whichever is most appropriate. (There could be both, but having two names for the same thing is probably not good for API clarity unless one is intended as a BC shim.) The exact details of the "mangled" name of functions is not important, so long as it is unique for each function, and human-readable and typeable since the only way to reference a function in a dynamic context is by a string name. For example, instead of 'foo(a:b:)', 'foo:a:b' could be used. I specifically picked 'foo(a:b:)' because it looks like a function call (but without values, it's unambiguously not a call), and also opens the possibility of using that specific syntax to allow for referring to functions by symbol, rather than by string. That is, in a hypothetical future, you could do this: var (callable(T, T))[] $comparators = [ 'string': \strcmp(_:_:) \DateTime::class: self::specialDateComparator(_:_:), A::class: A::compare(_:_:), B::class: B:compare(first:second:), C::class: function($a, $b) {...}, ]; usort($array, $comparators[$eltType]); But that's definitely getting into future-scope possibilities. Thank you for your consideration. I can more fully flesh this out into an alternative RFC if people would like. -John
  110025
May 5, 2020 20:47 rowan.collins@gmail.com (Rowan Tommins)
Hi John,

On 05/05/2020 18:27, John Bafford wrote:
> I very much do like the idea of named parameters, but I do not like the specific proposal in the linked RFC at all. I think that treating named parameters simply as syntactic sugar is not a good approach. If we're going to add named parameters, I think they should actually be significant in their own right. As such, please allow me to present an alternative. > > I would propose the following opt-in mechanism, which would work by treating the parameter names as*part of the function name*. Doing it this way I_think_ should allow for no backwards compatibility issues with existing code and would especially give library authors full control over the naming and presentation of their APIs.
I really like *some* of this proposal, but I think some of it is solving a different problem. You mention in a later e-mail that you've been heavily inspired by Swift; Swift in turn was inspired by Objective C, which was inspired by Smalltalk. This is an important piece of history, because Smalltalk doesn't actually have methods with named parameters; it has "keyword messages" whose "selectors" are things like "indexOf:startingAt:". Swift makes them look more familiar by keeping part of the name separate from what it calls "argument labels", so a method might be called "indexOf(_:startingAt:)" or "index(of:startingAt:)". Although at a glance these look like named parameters, and they solve some of the same problems, they're actually a fundamentally different concept. They have some nice features: * Method calls can be made to read like sentences * The external API of the function is kept separate from the internal implementation * You can skip over default arguments by omitting their labels * Overloading what looks like one method, by having different methods whose names start the same but have different "argument labels", may be appealing But there are some crucial limitations: * You can't call a method without using its labels; you'd need to have a separate method with a similar name and no labels * Similarly, it's up to the method's author to have versions with a mixture of unlabelled and labelled parameters, so a call like "create('hello', 42, locked: true)" is only possible if that specific variant is defined * You can't use the labels in the "wrong" order, you have to know the order they come in; so it doesn't solve the haystack/needle problem, except by creating extra function definitions * Similarly, methods with large numbers of arguments are still hard to use because you need to know both the name *and* the relative position of the arguments you're providing (imagine defining variants with every order of the 5 bit flags in the AMQP example in my earlier e-mail) It would be possible to take the "outside name is different from inside name" concept and apply it to named parameters proper. But the problems I would like to solve with named parameters wouldn't be solved by Swift/Smalltalk style functions. Regards, -- Rowan Tommins (né Collins) [IMSoP]
  110048
May 6, 2020 18:18 jbafford@zort.net (John Bafford)
Hi Rowan,

> On May 5, 2020, at 16:47, Rowan Tommins collins@gmail.com> wrote: > > Hi John, > > On 05/05/2020 18:27, John Bafford wrote: >> I very much do like the idea of named parameters, but I do not like the specific proposal in the linked RFC at all. I think that treating named parameters simply as syntactic sugar is not a good approach. If we're going to add named parameters, I think they should actually be significant in their own right. As such, please allow me to present an alternative. >> >> I would propose the following opt-in mechanism, which would work by treating the parameter names as*part of the function name*. Doing it this way I_think_ should allow for no backwards compatibility issues with existing code and would especially give library authors full control over the naming and presentation of their APIs. > > > I really like *some* of this proposal, but I think some of it is solving a different problem. > > > You mention in a later e-mail that you've been heavily inspired by Swift; Swift in turn was inspired by Objective C, which was inspired by Smalltalk. This is an important piece of history, because Smalltalk doesn't actually have methods with named parameters; it has "keyword messages" whose "selectors" are things like "indexOf:startingAt:". Swift makes them look more familiar by keeping part of the name separate from what it calls "argument labels", so a method might be called "indexOf(_:startingAt:)" or "index(of:startingAt:)".
You're not wrong here, but, I think that's an (critical) implementation detail of Objective-C and Smalltalk that is not relevant here. Also, Swift does not use selectors or message passing, unless either interoperating with ObjC classes, or the code has explicitly opted-in. Normally, Swift function/method calls are statically determined at compile time in a way similar to C/C++.
> Although at a glance these look like named parameters, and they solve some of the same problems, they're actually a fundamentally different concept. > > They have some nice features: > > * Method calls can be made to read like sentences > * The external API of the function is kept separate from the internal implementation > * You can skip over default arguments by omitting their labels > * Overloading what looks like one method, by having different methods whose names start the same but have different "argument labels", may be appealing > > But there are some crucial limitations:
I don't think any of your points are downsides to the proposal. A lot of these I think boil down to the conflict between, "as an API user, I should be able to do whatever I want", and, "as an API author, I have decided my API will be used like this". I generally side with the API author being specific on how their API will be used.
> * You can't call a method without using its labels; you'd need to have a separate method with a similar name and no labels
If a function/method has labels, you're required to use them. Otherwise, you can't get the benefit of using them for name-based "overloading". For example, if you have the methods function firstIndex(of: $element) : ?int function firstIndex(where: callable $predicate) : ?int You can't just call $collection->firstIndex($foo), because the compiler would have no idea which method it refers to. And while you _could_ have a function firstIndex($param) that tries to determine which specific method to call, but now you've turned a compile-time static call into a runtime check, and that might still not be sufficient, for example, if you have a collection of callables! The labels are part of the method's name, which happen to be written inside the parenthesis. I just argue that both of those are more readable than either of these: function firstIndexOfElement($elt) : ?int function firstIndexMatchingPredicate(callable $predicate) : ?int If I give my method a particular name, I expect users to call them as such. If the user doesn't like it, then they can wrap the class and provide their own alternate interface.
> * Similarly, it's up to the method's author to have versions with a mixture of unlabelled and labelled parameters, so a call like "create('hello', 42, locked: true)" is only possible if that specific variant is defined
As above: a method's author is under no compulsion to do so. If I define my API to be create('hello', fontSize: 42, locked: true), then that's how I expect my users to call it.
> * You can't use the labels in the "wrong" order, you have to know the order they come in; so it doesn't solve the haystack/needle problem, except by creating extra function definitions
I don't think this is a problem either. In some cases, the extra context by having the parameter names spell out a gramatically-correct sentence structure will help with memorizing the order. In other cases, the constant repetition will help. If you visually see strpos(haystack: $str, needle: $str) all over the place, it will help; but this can also be used as a way to fix the problem by completely sidestepping it, by adding new functions/methods that offer more clarity: strpos($string, in: $haystack); position(of: $string, in: $haystack) indexOf($str, in: $haystack) In those examples, the sentence-like structure of the call, which are all variations on, "what is the position of $string in $haystack?" makes it more memorable.
> * Similarly, methods with large numbers of arguments are still hard to use because you need to know both the name *and* the relative position of the arguments you're providing (imagine defining variants with every order of the 5 bit flags in the AMQP example in my earlier e-mail)
Well, you wouldn't define variants for each order, you'd just rely on the compiler (or IDE) to help the user. So if I tried to call $channel->queue_declare('name', arguments: $args, durable: true) I'd expect the compiler to report back something along the lines of, "parameters are in the wrong order; use queue_declare(_: passive: durable: exclusive: autodelete: nowait: arguments: ticket:)". Maybe even prefix or postfix the parameter names with a ? to indicate the ones that are optional because there's a default value. Yes, you _could_ add additional function definitions to handle whatever random order people want to use functions in, but you don't have to, and most API authors probably wouldn't. Because there's generally only a few "correct" ways to word most sentences. In that regard, AMQP::queue_declare is a pathological exception. There may be a good reason for its specific order, but lacking that knowledge, it appears to be completely arbitrary and nonintuitive. If I said to someone, "language the hard improve working We constantly PHP to are", they'd probably look at me funny. We don't accept people speaking human language with randomized word order; neither should API authors (or other developers) be expected to accept people using APIs with randomized parameter orders.
> It would be possible to take the "outside name is different from inside name" concept and apply it to named parameters proper. But the problems I would like to solve with named parameters wouldn't be solved by Swift/Smalltalk style functions. > > > Regards, > > -- > Rowan Tommins (né Collins) > [IMSoP]
-John
  110050
May 6, 2020 20:07 rowan.collins@gmail.com (Rowan Tommins)
Hi John,

On 6 May 2020 19:18:41 BST, John Bafford <jbafford@zort.net> wrote:
>You're not wrong here, but, I think that's an (critical) implementation >detail of Objective-C and Smalltalk that is not relevant here. Also, >Swift does not use selectors or message passing, unless either >interoperating with ObjC classes, or the code has explicitly opted-in. >Normally, Swift function/method calls are statically determined at >compile time in a way similar to C/C++.
It may not use the same mechanism to dispatch them, but the concept of "parameter labels are part of the function name" is still very much present, and is very different from named parameters in other languages.
>I don't think any of your points are downsides to the proposal. A lot >of these I think boil down to the conflict between, "as an API user, I >should be able to do whatever I want", and, "as an API author, I have >decided my API will be used like this". I generally side with the API >author being specific on how their API will be used.
I think this is the same argument people have over the strict_types declaration: should a library be able to constrain how users write their code, even if it makes no difference to how the library functions? With strict_types=0, a user can ask the language to convert a string to an int on-the-fly, and a library author gets the int they asked for. Similarly, named parameters allow users to call a function with arguments in the "wrong" order, but the library author is guaranteed they'll all be there. You could put partial application in the same category: it allows a user to reuse your function without specifying all the parameters each time! Ultimately, even if the language didn't provide mechanisms for any of these things, a user could write a wrapper that did. I'm not aware of a programming language where libraries have the power to stop that, and I think making it easier is a Good Thing: it allows users to reuse each other's code, but adapt it to their own coding style.
>If a function/method has labels, you're required to use them. >Otherwise, you can't get the benefit of using them for name-based >"overloading".
This is why I said earlier that I think Smalltalk-style function naming is solving a different problem from named parameters. Smalltalk was explicitly trying to make programming read like natural sentences, and it used these multi-part names as a way to do that. That's a really interesting concept, but it's not the feature that people are asking for in PHP. And the restriction it places on how functions are called directly works against solving the problems that named parameters would solve.
> Because there's generally only a few "correct" ways to word most sentences. > In that regard, AMQP::queue_declare is a pathological exception. There may be a good > reason for its specific order, but lacking that knowledge, it appears > to be completely arbitrary and nonintuitive.
It may be a "pathological exception" if you define the problem as "how can we make methods read more like sentences"; but that's not the problem I see named parameters as solving. The way I see it, requiring a specific order for those parameters is itself an arbitrary constraint. An example that's come up frequently in the thread is constructing simple data objects. The actual requirement is "provide appropriately-typed values for these N named fields"; in many cases, there is no "natural order", and no particular value in the library author choosing an order, *except that the language forces them to*. Internally, the compiler / runtime needs everything to have an address in linear memory; but humans like to give things names. Rather than: function strpos(string $1, string $2, int $3) { ... } We prefer to define: function strpos(string $haystack, string $needle, int $offset) { ... } Within the body, we don't have to worry about the position of $haystack and $needle in memory, we just use their names. Named parameters are in a way just an extension of that; rather than: strpos("hello", "l", 3) We can write: strpos(haystack: "hello", needle: "l", offset: 3) Or: strpos(needle: "l", haystack: "hello", offset: 3) The order of parameters is no longer relevant; we've given them names, and the compiler can sort out where they need to live in memory.
>If I said to someone, "language the hard improve working We constantly >PHP to are", they'd probably look at me funny. We don't accept people >speaking human language with randomized word order; neither should API >authors (or other developers) be expected to accept people using APIs >with randomized parameter orders.
I you typed that into a special accessibility aid, and it was automatically translated into the correct order by predictable rules before I saw it, I a) wouldn't even know; and b) would be happy you were able to use that aid to communicate with me. Similarly, if you write a function and somebody can call it using their preferred style that still guarantees the signature is followed, that's a win for everyone. Regards, -- Rowan Tommins [IMSoP]
  110027
May 6, 2020 06:25 sobak@php.net (Maciej Sobaczewski)
Hi Nikita!

W dniu 05.05.2020 o 15:51, Nikita Popov pisze:
> Hi internals, > > I've recently started a thread on resurrecting the named arguments proposal > (https://externals.io/message/109549), as this has come up tangentially in > some recent discussions around attributes and around object ergonomics. > > I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params > > Relative to the last time I've proposed this around PHP 5.6 times, I think > we're technically in a much better spot now when it comes to the support > for internal functions, thanks to the stubs work. > > I think the recent acceptance of the attributes proposal also makes this a > good time to bring it up again, as phpdoc annotations have historically had > support for named arguments, and this will make migration to the > language-provided attributes smoother. > > Regards, > Nikita >
I'm on the fence when it comes to the feature itself so I will skip that part entirerly but I'd like to draw attention to one more problem. Many PHP Manual translations translate the parameter names, too - both in text, as well as in the function/method signatures themselves. We would need to fix every such occurence for every language (at least active ones but there are still couple of them). Otherwise documentation will become highly misleading. Most people probably use IDEs of course but it still doesn't change the fact that the manual cannot be totally out of sync with what could become the language syntax. Cheers, Maciej.
  110029
May 6, 2020 07:43 cmbecker69@gmx.de ("Christoph M. Becker")
On 06.05.2020 at 08:25, Maciej Sobaczewski wrote:

> I'm on the fence when it comes to the feature itself so I will skip that > part entirerly but I'd like to draw attention to one more problem. Many > PHP Manual translations translate the parameter names, too - both in > text, as well as in the function/method signatures themselves. > > We would need to fix every such occurence for every language (at least > active ones but there are still couple of them). Otherwise documentation > will become highly misleading. > > Most people probably use IDEs of course but it still doesn't change the > fact that the manual cannot be totally out of sync with what could > become the language syntax.
I hope that we will be able to generate the elements automatically from the stubs (which would likely require some more meta-information in the doc blocks). These could then automatically be injected into the docs. Of course, someone would have to contribute the required scripts. :) -- Christoph M. Becker
May 6, 2020 07:51 sobak@php.net (Maciej Sobaczewski)
W dniu 06.05.2020 o 09:43, Christoph M. Becker pisze:
> On 06.05.2020 at 08:25, Maciej Sobaczewski wrote: > >> I'm on the fence when it comes to the feature itself so I will skip that >> part entirerly but I'd like to draw attention to one more problem. Many >> PHP Manual translations translate the parameter names, too - both in >> text, as well as in the function/method signatures themselves. >> >> We would need to fix every such occurence for every language (at least >> active ones but there are still couple of them). Otherwise documentation >> will become highly misleading. >> >> Most people probably use IDEs of course but it still doesn't change the >> fact that the manual cannot be totally out of sync with what could >> become the language syntax. > > I hope that we will be able to generate the elements > automatically from the stubs (which would likely require some more > meta-information in the doc blocks). These could then automatically be > injected into the docs. Of course, someone would have to contribute the > required scripts. :) > > -- > Christoph M. Becker >
That would surely simplify things but that's just the part of the job to do. There are also inline mentions. Of course, they are wrapped in so we could try to convert them, too but this could be a bit error prone IMO and probably would make documentation a bit unreadable if you e.g. inject an English word in the middle of German/Polish sentence. Of course this should not be a blocker if the internals will decide that we want the named parameters. I'm not trying to stop the movement here. It's simply one more thing to consider :) Cheers, Maciej
  110030
May 6, 2020 07:50 kontakt@beberlei.de (Benjamin Eberlei)
On Wed, May 6, 2020 at 8:25 AM Maciej Sobaczewski <sobak@php.net> wrote:

> Hi Nikita! > > W dniu 05.05.2020 o 15:51, Nikita Popov pisze: > > Hi internals, > > > > I've recently started a thread on resurrecting the named arguments > proposal > > (https://externals.io/message/109549), as this has come up tangentially > in > > some recent discussions around attributes and around object ergonomics. > > > > I've now updated the old proposal on this topic, and moved it back under > > discussion: https://wiki.php.net/rfc/named_params > > > > Relative to the last time I've proposed this around PHP 5.6 times, I > think > > we're technically in a much better spot now when it comes to the support > > for internal functions, thanks to the stubs work. > > > > I think the recent acceptance of the attributes proposal also makes this > a > > good time to bring it up again, as phpdoc annotations have historically > had > > support for named arguments, and this will make migration to the > > language-provided attributes smoother. > > > > Regards, > > Nikita > > > > > I'm on the fence when it comes to the feature itself so I will skip that > part entirerly but I'd like to draw attention to one more problem. Many > PHP Manual translations translate the parameter names, too - both in > text, as well as in the function/method signatures themselves. >
Can you give some examples? I checked a bunch of common functions in ext/standard, DateTime and PDO and all languages except Turkish seem to use exactly the same argument names as the english manual. Maybe there are individual runaways? In any case, the Turkish manual would have to change. It is misleading anyways, because for each of these ReflectionParameter::getName() would return the actual parameter name at runtime.
> > We would need to fix every such occurence for every language (at least > active ones but there are still couple of them). Otherwise documentation > will become highly misleading. > > Most people probably use IDEs of course but it still doesn't change the > fact that the manual cannot be totally out of sync with what could > become the language syntax. > > Cheers, > Maciej. > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
  110300
May 29, 2020 14:32 nikita.ppv@gmail.com (Nikita Popov)
On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I've recently started a thread on resurrecting the named arguments > proposal (https://externals.io/message/109549), as this has come up > tangentially in some recent discussions around attributes and around object > ergonomics. > > I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params > > Relative to the last time I've proposed this around PHP 5.6 times, I think > we're technically in a much better spot now when it comes to the support > for internal functions, thanks to the stubs work. > > I think the recent acceptance of the attributes proposal also makes this a > good time to bring it up again, as phpdoc annotations have historically had > support for named arguments, and this will make migration to the > language-provided attributes smoother. >
Regarding the question of what to do with regard to LSP validation and parameter names changing during inheritance: During internal discussion, the following option has come up as a possible compromise: 1. When calling a method, also allow using parameter names from the parent class/interface. 2. During inheritance, enforce that the same parameter name is not used at different positions. This ensures that renaming parameter names during inheritance does not break code relying on parameter names of the parent method. At the same time, it prohibits genuine LSP violations, where a parameter has been moved to a different position. I've run some static analysis to detect cases that would be affected by the latter check, with these results: https://gist.github.com/nikic/6cc9891381a83b8dca5ebdaef1068f4d The first signature is the child method, and the second the parent method. I did not put in the effort to make this completely precise, so there's both false positives and false negatives here. But it should be enough for a general impression. And the general impression is that these are indeed legitimate LSP violations. This approach would be an alternative to either silently ignoring the issue (as the RFC proposed), or to warning for all parameter renames. Regards, Nikita
  110303
May 29, 2020 19:02 larry@garfieldtech.com ("Larry Garfield")
On Fri, May 29, 2020, at 9:32 AM, Nikita Popov wrote:
> On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: > > > Hi internals, > > > > I've recently started a thread on resurrecting the named arguments > > proposal (https://externals.io/message/109549), as this has come up > > tangentially in some recent discussions around attributes and around object > > ergonomics. > > > > I've now updated the old proposal on this topic, and moved it back under > > discussion: https://wiki.php.net/rfc/named_params > > > > Relative to the last time I've proposed this around PHP 5.6 times, I think > > we're technically in a much better spot now when it comes to the support > > for internal functions, thanks to the stubs work. > > > > I think the recent acceptance of the attributes proposal also makes this a > > good time to bring it up again, as phpdoc annotations have historically had > > support for named arguments, and this will make migration to the > > language-provided attributes smoother. > > > > Regarding the question of what to do with regard to LSP validation and > parameter names changing during inheritance: During internal discussion, > the following option has come up as a possible compromise: > > 1. When calling a method, also allow using parameter names from the parent > class/interface. > 2. During inheritance, enforce that the same parameter name is not used at > different positions. > > This ensures that renaming parameter names during inheritance does not > break code relying on parameter names of the parent method. At the same > time, it prohibits genuine LSP violations, where a parameter has been moved > to a different position. > > I've run some static analysis to detect cases that would be affected by the > latter check, with these results: > https://gist.github.com/nikic/6cc9891381a83b8dca5ebdaef1068f4d The first > signature is the child method, and the second the parent method. I did not > put in the effort to make this completely precise, so there's both false > positives and false negatives here. But it should be enough for a general > impression. And the general impression is that these are indeed legitimate > LSP violations. > > This approach would be an alternative to either silently ignoring the issue > (as the RFC proposed), or to warning for all parameter renames. > > Regards, > Nikita
Just to make sure I follow what you're proposing, given: class P { public function foo($a, $b, $c) { ... } } This is legal: class A extends P { public function foo($a2, $b, $c) {} } // Mean the same thing: $a = (new A)->foo(a = 1, b = 2, c = 3); $a = (new A)->foo(a2 = 1, b = 2, c = 3); This will parse error: class A extends P { public function foo($b, $a, $c) {} } Am I following the intent correctly? If so, that sounds like a very reasonable and safe middle-ground. --Larry Garfield
  110308
May 29, 2020 23:02 bobwei9@hotmail.com (Bob Weinand)
> Am 29.05.2020 um 21:02 schrieb Larry Garfield <larry@garfieldtech.com>: > > On Fri, May 29, 2020, at 9:32 AM, Nikita Popov wrote: >> On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: >> >>> Hi internals, >>> >>> I've recently started a thread on resurrecting the named arguments >>> proposal (https://externals.io/message/109549), as this has come up >>> tangentially in some recent discussions around attributes and around object >>> ergonomics. >>> >>> I've now updated the old proposal on this topic, and moved it back under >>> discussion: https://wiki.php.net/rfc/named_params >>> >>> Relative to the last time I've proposed this around PHP 5.6 times, I think >>> we're technically in a much better spot now when it comes to the support >>> for internal functions, thanks to the stubs work. >>> >>> I think the recent acceptance of the attributes proposal also makes this a >>> good time to bring it up again, as phpdoc annotations have historically had >>> support for named arguments, and this will make migration to the >>> language-provided attributes smoother. >>> >> >> Regarding the question of what to do with regard to LSP validation and >> parameter names changing during inheritance: During internal discussion, >> the following option has come up as a possible compromise: >> >> 1. When calling a method, also allow using parameter names from the parent >> class/interface. >> 2. During inheritance, enforce that the same parameter name is not used at >> different positions. >> >> This ensures that renaming parameter names during inheritance does not >> break code relying on parameter names of the parent method. At the same >> time, it prohibits genuine LSP violations, where a parameter has been moved >> to a different position. >> >> I've run some static analysis to detect cases that would be affected by the >> latter check, with these results: >> https://gist.github.com/nikic/6cc9891381a83b8dca5ebdaef1068f4d The first >> signature is the child method, and the second the parent method. I did not >> put in the effort to make this completely precise, so there's both false >> positives and false negatives here. But it should be enough for a general >> impression. And the general impression is that these are indeed legitimate >> LSP violations. >> >> This approach would be an alternative to either silently ignoring the issue >> (as the RFC proposed), or to warning for all parameter renames. >> >> Regards, >> Nikita > > Just to make sure I follow what you're proposing, given: > > class P { > public function foo($a, $b, $c) { ... } > } > > This is legal: > > class A extends P { > public function foo($a2, $b, $c) {} > } > > // Mean the same thing: > $a = (new A)->foo(a = 1, b = 2, c = 3); > $a = (new A)->foo(a2 = 1, b = 2, c = 3); > > This will parse error: > > class A extends P { > public function foo($b, $a, $c) {} > } > > Am I following the intent correctly? > > If so, that sounds like a very reasonable and safe middle-ground. > > --Larry Garfield
Yes, that's pretty much the intent - having a very small BC break surface, while still allowing for safe usage of named parameters according to both the child and parent interface. Bob
  110816
July 2, 2020 11:20 ajf@ajf.me (Andrea Faulds)
Hi,

Nikita Popov wrote:
> > Regarding the question of what to do with regard to LSP validation and > parameter names changing during inheritance: During internal discussion, > the following option has come up as a possible compromise: > > 1. When calling a method, also allow using parameter names from the parent > class/interface. > 2. During inheritance, enforce that the same parameter name is not used at > different positions. > > This ensures that renaming parameter names during inheritance does not > break code relying on parameter names of the parent method. At the same > time, it prohibits genuine LSP violations, where a parameter has been moved > to a different position. > > I've run some static analysis to detect cases that would be affected by the > latter check, with these results: > https://gist.github.com/nikic/6cc9891381a83b8dca5ebdaef1068f4d The first > signature is the child method, and the second the parent method. I did not > put in the effort to make this completely precise, so there's both false > positives and false negatives here. But it should be enough for a general > impression. And the general impression is that these are indeed legitimate > LSP violations. > > This approach would be an alternative to either silently ignoring the issue > (as the RFC proposed), or to warning for all parameter renames.
Would this do anything surprising when variadics are involved? Regards, Andrea
  110386
June 5, 2020 10:45 cschneid@cschneid.com (Christian Schneider)
Am 05.05.2020 um 15:51 schrieb Nikita Popov ppv@gmail.com>:
> \I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params
First of all I really like this approach to Named Parameters: It seems to fit well with the rest of PHP. Note: I'm using the key: 'value' syntax instead of key => 'value' here but that's just because I prefer that syntax and think it more naturally extends to stuff like the shorthand syntax under "Future Scope" but that's not a prerequisite. I have two questions regarding to the Named Parameters which are not related to the LSP discussion. They might be restrictions of the current implementation mentioned in the RFC as opposed to design restrictions: 1) Could keywords be allowed as parameter names? Currently using class: 'foo' throws a syntax error. 2) Could positional parameters be allowed after named parameters for variadic functions? These two restrictions can be looked at separately and the only reason I'm bringing them up together is because the use case I'm looking at is HTML generation with something like a function div() being used as follows. Please don't discard the two questions just because you don't like this particular use-case, thank you ;-) div(class: 'error', div(class: 'title', "Error Title"), "Detailed error description...", ) The first issue is probably mainly a parsing issue and changing T_STRING to identifier seems to fix it though I'm not 100% sure if there are any drawbacks to this. What is the main argument for not allowing positional arguments after named parameters for variadic functions? Ambiguities could still be reported if I am not mistaken. A work-around for the second issue would be to require an artificial parameter name like content taking a string or array and then do div( class: 'error', content: [ div(class: 'title', content: "Error Title"), "Detailed error description...", ], ) but that's somewhat more verbose without any real benefit I can see. PS: Concerning "Future Scope: Shorthand syntax for matching parameter and variable name" I think allowing :$name is reasonable and thinking further along that line maybe variadic functions separating named from positional arguments could be done using func(...$positional, ....:$named) similar to Python's *args, **kwargs). - Chris
  110387
June 5, 2020 13:40 nikita.ppv@gmail.com (Nikita Popov)
On Fri, Jun 5, 2020 at 12:45 PM Christian Schneider <cschneid@cschneid.com>
wrote:

> Am 05.05.2020 um 15:51 schrieb Nikita Popov ppv@gmail.com>: > > \I've now updated the old proposal on this topic, and moved it back under > > discussion: https://wiki.php.net/rfc/named_params > > First of all I really like this approach to Named Parameters: It seems to > fit well with the rest of PHP. > > Note: I'm using the key: 'value' syntax instead of key => 'value' here but > that's just because I prefer that syntax and think it more naturally > extends to stuff like the shorthand syntax under "Future Scope" but that's > not a prerequisite. > > I have two questions regarding to the Named Parameters which are not > related to the LSP discussion. > They might be restrictions of the current implementation mentioned in the > RFC as opposed to design restrictions: > > 1) Could keywords be allowed as parameter names? Currently using class: > 'foo' throws a syntax error. > 2) Could positional parameters be allowed after named parameters for > variadic functions? > > These two restrictions can be looked at separately and the only reason I'm > bringing them up together is because the use case I'm looking at is HTML > generation with something like a function div() being used as follows. > Please don't discard the two questions just because you don't like this > particular use-case, thank you ;-) > div(class: 'error', > div(class: 'title', "Error Title"), > "Detailed error description...", > ) > > The first issue is probably mainly a parsing issue and changing T_STRING > to identifier seems to fix it though I'm not 100% sure if there are any > drawbacks to this. >
Right. It should be possible to use keywords. It's a bit harder than just replacing T_STRING with identifier, but I don't see any fundamental reason why it shouldn't work.
> What is the main argument for not allowing positional arguments after > named parameters for variadic functions? > Ambiguities could still be reported if I am not mistaken. > A work-around for the second issue would be to require an artificial > parameter name like content taking a string or array and then do > div( > class: 'error', > content: [ > div(class: 'title', content: "Error Title"), > "Detailed error description...", > ], > ) > but that's somewhat more verbose without any real benefit I can see. >
If we ignore the philosophical question of whether having positional parameters after named is a good idea, this mostly comes down to technical complexity. Enforcing the "named after positional" restriction is a simple compile-time check. Without it, we would have to duplicate a large number of argument sending instructions just for this special case. This is basically the same reason why we don't support "foo(...$args, 1)". That's perfectly legitimate code, but supporting it seems like more technical complexity than it's worth.
> PS: Concerning "Future Scope: Shorthand syntax for matching parameter and > variable name" I think allowing :$name is reasonable and thinking further > along that line maybe variadic functions separating named from positional > arguments could be done using func(...$positional, ...:$named) similar to > Python's *args, **kwargs). >
Separating positional & named variadics / unpacks in that way is certainly a possibility, but I don't think it's the best choice for PHP. In Python this is pretty much required, because Python has distinct list and dictionary types. In PHP these are combined in one data structure, which also allows us to combine them for the purpose of variadics. Combining them makes sure that existing code using variadic forwarding will "just work" with named parameters -- but of course, it also means that some code will accept named parameters even though it was not explicitly designed with that in mind. There's a trade-off there. Regards, Nikita
  110421
June 8, 2020 12:05 nikita.ppv@gmail.com (Nikita Popov)
On Fri, Jun 5, 2020 at 3:40 PM Nikita Popov ppv@gmail.com> wrote:

> On Fri, Jun 5, 2020 at 12:45 PM Christian Schneider <cschneid@cschneid.com> > wrote: > >> Am 05.05.2020 um 15:51 schrieb Nikita Popov ppv@gmail.com>: >> > \I've now updated the old proposal on this topic, and moved it back >> under >> > discussion: https://wiki.php.net/rfc/named_params >> >> First of all I really like this approach to Named Parameters: It seems to >> fit well with the rest of PHP. >> >> Note: I'm using the key: 'value' syntax instead of key => 'value' here >> but that's just because I prefer that syntax and think it more naturally >> extends to stuff like the shorthand syntax under "Future Scope" but that's >> not a prerequisite. >> >> I have two questions regarding to the Named Parameters which are not >> related to the LSP discussion. >> They might be restrictions of the current implementation mentioned in the >> RFC as opposed to design restrictions: >> >> 1) Could keywords be allowed as parameter names? Currently using class: >> 'foo' throws a syntax error. >> 2) Could positional parameters be allowed after named parameters for >> variadic functions? >> >> These two restrictions can be looked at separately and the only reason >> I'm bringing them up together is because the use case I'm looking at is >> HTML generation with something like a function div() being used as follows. >> Please don't discard the two questions just because you don't like this >> particular use-case, thank you ;-) >> div(class: 'error', >> div(class: 'title', "Error Title"), >> "Detailed error description...", >> ) >> >> The first issue is probably mainly a parsing issue and changing T_STRING >> to identifier seems to fix it though I'm not 100% sure if there are any >> drawbacks to this. >> > > Right. It should be possible to use keywords. It's a bit harder than just > replacing T_STRING with identifier, but I don't see any fundamental reason > why it shouldn't work. >
I've implemented the necessary groundwork for this in https://github.com/php/php-src/commit/b03cafd19c01db57b89727ce77cc89a7d816077c, so now I can say for sure that using keywords as parameter name will work fine. Nikita
  110688
June 19, 2020 22:12 benas.molis.iml@gmail.com (Benas IML)
Hey Nikita,

This is a bit late response but ah, who cares ;-)

I suppose that for consistency with named params, we should also make `declare`
compatible with this syntax. So for example, `declare(strict_types:
1);` would be
the same as `declare(strict_types=1)`. This would also make much more sense to
newcomers and beginners.

Best regards,
Benas

On Tue, 5 May 2020 at 16:51, Nikita Popov ppv@gmail.com> wrote:
> > Hi internals, > > I've recently started a thread on resurrecting the named arguments proposal > (https://externals.io/message/109549), as this has come up tangentially in > some recent discussions around attributes and around object ergonomics. > > I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params > > Relative to the last time I've proposed this around PHP 5.6 times, I think > we're technically in a much better spot now when it comes to the support > for internal functions, thanks to the stubs work. > > I think the recent acceptance of the attributes proposal also makes this a > good time to bring it up again, as phpdoc annotations have historically had > support for named arguments, and this will make migration to the > language-provided attributes smoother. > > Regards, > Nikita
  110708
June 23, 2020 10:10 nikita.ppv@gmail.com (Nikita Popov)
On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote:

> Hi internals, > > I've recently started a thread on resurrecting the named arguments > proposal (https://externals.io/message/109549), as this has come up > tangentially in some recent discussions around attributes and around object > ergonomics. > > I've now updated the old proposal on this topic, and moved it back under > discussion: https://wiki.php.net/rfc/named_params > > Relative to the last time I've proposed this around PHP 5.6 times, I think > we're technically in a much better spot now when it comes to the support > for internal functions, thanks to the stubs work. > > I think the recent acceptance of the attributes proposal also makes this a > good time to bring it up again, as phpdoc annotations have historically had > support for named arguments, and this will make migration to the > language-provided attributes smoother. > > Regards, > Nikita >
As we're moving in on feature freeze, I plan to move this proposal forward soonishly. I have update the RFC to drop the syntax as an open question (I haven't seen much opposition to the use of ":"), and to describe the possible alternative LSP behavior at https://wiki.php.net/rfc/named_params#parameter_name_changes_during_inheritance .. While writing this down and implementing it, I found that this has more odd edge-cases than anticipated. Overall, I'm not sold that this approach is worth it. It sounds nice on paper, but I strongly suspect that it solves a problem that does not existing in practice, and will force us to keep this patch-over mechanism indefinitely, while the real solution would have been to fix the limited amount of code that is in the intersection of "renames parameters" and "is actually invoked with named arguments". Regards, Nikita
  110780
June 29, 2020 15:13 nikita.ppv@gmail.com (Nikita Popov)
On Tue, Jun 23, 2020 at 12:10 PM Nikita Popov ppv@gmail.com> wrote:

> On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: > >> Hi internals, >> >> I've recently started a thread on resurrecting the named arguments >> proposal (https://externals.io/message/109549), as this has come up >> tangentially in some recent discussions around attributes and around object >> ergonomics. >> >> I've now updated the old proposal on this topic, and moved it back under >> discussion: https://wiki.php.net/rfc/named_params >> >> Relative to the last time I've proposed this around PHP 5.6 times, I >> think we're technically in a much better spot now when it comes to the >> support for internal functions, thanks to the stubs work. >> >> I think the recent acceptance of the attributes proposal also makes this >> a good time to bring it up again, as phpdoc annotations have historically >> had support for named arguments, and this will make migration to the >> language-provided attributes smoother. >> >> Regards, >> Nikita >> > > As we're moving in on feature freeze, I plan to move this proposal forward > soonishly. > > I have update the RFC to drop the syntax as an open question (I haven't > seen much opposition to the use of ":"), and to describe the possible > alternative LSP behavior at > https://wiki.php.net/rfc/named_params#parameter_name_changes_during_inheritance > . > > While writing this down and implementing it, I found that this has more > odd edge-cases than anticipated. Overall, I'm not sold that this approach > is worth it. It sounds nice on paper, but I strongly suspect that it solves > a problem that does not existing in practice, and will force us to keep > this patch-over mechanism indefinitely, while the real solution would have > been to fix the limited amount of code that is in the intersection of > "renames parameters" and "is actually invoked with named arguments". >
Just as another reminder: I plan to put this to voting by the end of the week. I've also updated the RFC to make the LSP behavior a secondary vote. I'm not convinced this is a good idea myself, but some others seemed to prefer this approach. Regards, Nikita
  110785
June 29, 2020 16:49 theodorejb@outlook.com (Theodore Brown)
On Mon, June 29, 2020 at 10:13 AM Nikita Popov ppv@gmail.com> wrote:

> On Tue, Jun 23, 2020 at 12:10 PM Nikita Popov ppv@gmail.com> wrote: > > > As we're moving in on feature freeze, I plan to move this proposal forward > > soonishly. > > > > I have update the RFC to drop the syntax as an open question (I haven't > > seen much opposition to the use of ":"), and to describe the possible > > alternative LSP behavior at > > https://wiki.php.net/rfc/named_params#parameter_name_changes_during_inheritance. > > > > While writing this down and implementing it, I found that this has more > > odd edge-cases than anticipated. Overall, I'm not sold that this approach > > is worth it. It sounds nice on paper, but I strongly suspect that it solves > > a problem that does not existing in practice, and will force us to keep > > this patch-over mechanism indefinitely, while the real solution would have > > been to fix the limited amount of code that is in the intersection of > > "renames parameters" and "is actually invoked with named arguments". > > Just as another reminder: I plan to put this to voting by the end of the > week. > > I've also updated the RFC to make the LSP behavior a secondary vote. I'm > not convinced this is a good idea myself, but some others seemed to prefer > this approach.
Hi Nikita, Thanks for moving this forward. Currently I work with a lot of value objects which are constructed from an associative array (requiring lots of manual validation), and named arguments will make it feasible to migrate most of these to simple typed parameters that can be statically analyzed instead. For me at least this will be one of the nicest features in PHP 8. I'm also looking forward to the potential future shorthand syntax for matching parameter and variable names, which could also reduce boilerplate in array construction and destructuring. I'll probably vote No on the secondary "Automagically allow using parent parameter names?" vote. This is a feature that could always be added later without a BC break, but if it's added now it would be hard to remove if the extra complexity causes problems. For these rare cases it's probably better to update the parameter names to match or stick with normal sequential arguments. Best regards, Theodore
  110836
July 3, 2020 14:35 nikita.ppv@gmail.com (Nikita Popov)
On Mon, Jun 29, 2020 at 5:13 PM Nikita Popov ppv@gmail.com> wrote:

> On Tue, Jun 23, 2020 at 12:10 PM Nikita Popov ppv@gmail.com> > wrote: > >> On Tue, May 5, 2020 at 3:51 PM Nikita Popov ppv@gmail.com> wrote: >> >>> Hi internals, >>> >>> I've recently started a thread on resurrecting the named arguments >>> proposal (https://externals.io/message/109549), as this has come up >>> tangentially in some recent discussions around attributes and around object >>> ergonomics. >>> >>> I've now updated the old proposal on this topic, and moved it back under >>> discussion: https://wiki.php.net/rfc/named_params >>> >>> Relative to the last time I've proposed this around PHP 5.6 times, I >>> think we're technically in a much better spot now when it comes to the >>> support for internal functions, thanks to the stubs work. >>> >>> I think the recent acceptance of the attributes proposal also makes this >>> a good time to bring it up again, as phpdoc annotations have historically >>> had support for named arguments, and this will make migration to the >>> language-provided attributes smoother. >>> >>> Regards, >>> Nikita >>> >> >> As we're moving in on feature freeze, I plan to move this proposal >> forward soonishly. >> >> I have update the RFC to drop the syntax as an open question (I haven't >> seen much opposition to the use of ":"), and to describe the possible >> alternative LSP behavior at >> https://wiki.php.net/rfc/named_params#parameter_name_changes_during_inheritance >> . >> >> While writing this down and implementing it, I found that this has more >> odd edge-cases than anticipated. Overall, I'm not sold that this approach >> is worth it. It sounds nice on paper, but I strongly suspect that it solves >> a problem that does not existing in practice, and will force us to keep >> this patch-over mechanism indefinitely, while the real solution would have >> been to fix the limited amount of code that is in the intersection of >> "renames parameters" and "is actually invoked with named arguments". >> > > Just as another reminder: I plan to put this to voting by the end of the > week. > > I've also updated the RFC to make the LSP behavior a secondary vote. I'm > not convinced this is a good idea myself, but some others seemed to prefer > this approach. >
I've made two additional changes to the proposal: 1. Explicitly mentioned attribute support in https://wiki.php.net/rfc/named_params#attributes1, and added it to the implementation (oops). ReflectionAttribute::getArguments() will also return named arguments to the attribute, and ReflectionAttribute::newInstance() will behave in the intuitive manner. 2. Added some information on internal APIs in https://wiki.php.net/rfc/named_params#internal_apis. The tl;dr is that named params are pretty much completely transparent for normal extensions, but there are some additional APIs if for example you want to perform a named param call from an extension. In relation to this, I'm also considering to change the semantics of call_user_func_array() to treat array elements with string keys as named parameters, rather than simply ignoring keys. The motivation here is not so much call_user_func_array() in particular, but various other APIs that do the same thing, such as ReflectionMethod::invokeArgs(), which should all behave consistently. Relatedly, I'm wondering if something like this should be allowed: call_user_func('strlen', str: 'foo'); I'm leaning towards "yes", in which case call_user_func_array() should be also be treated consistently. Regards, Nikita