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
Hey Nikita,
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_paramsRelative 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:
-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:
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
Hey Nikita,
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_paramsRelative 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:-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: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
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
Hey Nikita,
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_paramsRelative 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:-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: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
Hey Benjamin,
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
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, andfromXmlPayload()
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]
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:-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
Hey Theodore,
http://ocramius.github.com/
On Tue, May 5, 2020 at 6:59 PM Theodore Brown theodorejb@outlook.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:-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 theParamNode
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:
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 objectsHow can lots of optional parameters be "fixed" in practice with structs?
E.g. how would they improve theParamNode
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
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]
Hey Theodore,
On Tue, May 5, 2020 at 6:59 PM Theodore Brown theodorejb@outlook.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:-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 theParamNode
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: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
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).
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
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
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
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,
NikitaHi,
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
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
Marco Pivetta ocramius@gmail.com hat am 5. Mai 2020 um 16:11 geschrieben:
Hey Nikita,
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:...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
Hi Nikita,
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_paramsRelative 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, 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
Hi John,
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 aspart 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]
Hi Rowan,
Hi John,
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 aspart 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
Hi John,
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]
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_paramsRelative 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.
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 <methodsynopsis> 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
W dniu 06.05.2020 o 09:43, Christoph M. Becker pisze:
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 <methodsynopsis> 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
<parameter> 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
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_paramsRelative 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,
NikitaI'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.
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_paramsRelative 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:
- When calling a method, also allow using parameter names from the parent
class/interface. - 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
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_paramsRelative 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:
- When calling a method, also allow using parameter names from the parent
class/interface.- 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
Am 29.05.2020 um 21:02 schrieb Larry Garfield larry@garfieldtech.com:
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_paramsRelative 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:
- When calling a method, also allow using parameter names from the parent
class/interface.- 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,
NikitaJust 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
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:
- When calling a method, also allow using parameter names from the parent
class/interface.- 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
Am 05.05.2020 um 15:51 schrieb Nikita Popov nikita.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:
- Could keywords be allowed as parameter names? Currently using class: 'foo' throws a syntax error.
- 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
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 nikita.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_paramsFirst 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:
- Could keywords be allowed as parameter names? Currently using class:
'foo' throws a syntax error.- 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
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 nikita.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_paramsFirst 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:
- Could keywords be allowed as parameter names? Currently using class:
'foo' throws a syntax error.- 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
replacingT_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
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
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_paramsRelative 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
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_paramsRelative 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
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_paramsRelative 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,
NikitaAs 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
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
On Tue, Jun 23, 2020 at 12:10 PM Nikita Popov nikita.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_paramsRelative 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,
NikitaAs 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:
-
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. -
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
I've made two additional changes to the proposal:
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.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
muchcall_user_func_array()
in particular, but various other APIs that do
the same thing, such as ReflectionMethod::invokeArgs(), which should all
behave consistently.
That seems logical, although with the splat operator I don't know why anyone is using call_user_func_array()
anymore to begin with. Still, consistency is a good thing.
Relatedly, I'm wondering if something like this should be allowed:
call_user_func('strlen', str: 'foo');
Same statement: Seems logical, although why anyone is still using call_user_func()
I don't know. :-)
--Larry Garfield
On Tue, Jun 23, 2020 at 12:10 PM Nikita Popov nikita.ppv@gmail.com
wrote:On Tue, May 5, 2020 at 3:51 PM Nikita Popov nikita.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_paramsRelative 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,
NikitaAs 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:
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.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
muchcall_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.
Okay, I think I've now done the last set of updates to this RFC:
-
call_user_func()
,call_user_func_array()
,
ReflectionClass::newInstance(), ..., now support named parameters. - I've moved the alternative LSP behavior into the alternatives section
and don't plan to pursue it in this RFC. After thinking it over, I think
this is a nice migration hack, but not something that should be part of the
language long term.
Regards,
Nikita