Re: [PHP-DEV] [RFC][Proposal] Renamed parameters

This is only part of a thread. view whole thread
  111178
July 24, 2020 16:14 andreas@dqxtech.net (Andreas Hennings)
TLDR
Only consider parameter names from a type-hinted interface, ignore
parameter names from the actual class.

-----

I had a look at
https://wiki.php.net/rfc/named_params#to_parameter_name_changes_during_inheritance
Indeed I have concerns about this, because a call with named arguments
would make your code incompatible with 3rd party implementations of the
interface that do not keep the parameter names.
I would see this as a flaw in a newly added feature, which should be fixed
during the feature freeze.
I might even say the current version as described in the RFC is broken.

I have no objection to the OP's proposal, but would like to add some
alternative ideas to the discussion.

My first idea would be to only consider the parameter names in the original
interface, and ignore all renamed parameters in sub-classes.
This would cause problems if a class implements multiple interfaces that
declare the same method, but with different parameter names.

So, a better idea would be like this:
- The variable must be type-hinted with an interface. E.g. as a parameter
or as object properties.
- named arguments always use the parameter names from the type-hinted
interface.

This would need some static analysis at compile time, and we need to be
able to type-hint regular local variables.

An alternative would be to somehow "import" the parameter names at the top
of the file, somehow like so:

use Acme\Animal::bar;  // Parameter names from this method will be used.

(this syntax probably needs more thought).

Yet another option would be to somehow specify the interface in the method
call..

((\Acme\Animal) $instance)->foo(a: 'A', b: 'B');

Regards
Andreas



On Fri, 24 Jul 2020 at 17:41, Sara Golemon <pollita@php.net> wrote:

> On Fri, Jul 24, 2020 at 10:10 AM Nikita Popov ppv@gmail.com> > wrote: > > > > You added PHP 8.0 as a proposed version, but that will not be > possible > > > > anymore 2 weeks of discussion + 2 weeks of voting are not possible to > fit > > > > in before the feature freeze, which is in 11 days. > > > > > > While you are technically correct, the primary point of a feature > freeze > > > is not allowing in completely new features. > > > It will always happen that there are changes and extensions to RFCs > > > introduced for the next version which may need to be addressed first, > > > because there is massive benefit to the new feature in that case. (from > a > > > backwards/forwards compatibility standpoint for example) > > > > > We should of course be open to making minor adjustments due to > > unanticipated issues after feature freeze -- after all, that's where we > > gain experience with new features. The emphasis here is very much on > > *minor* and *unanticipated* though. > > > > Endorsing this. Anything post FF needs to be held to a high standard. > Minor and Unanticipated changes. > * The option to set alt-names on parameters is substantial. > * Making named params explicitly opt-in is less substantial (though > certainly not trivial), especially if it were hung off an attribute which > would mean no actual new syntax, but Niki's point that it wasn't an unknown > or unanticipated issue stands. > > I share OP's worry that this pushes workload onto library and framework > maintainers, and I wouldn't look forward to the review of all APIs to make > sure their names are correct, but the vote has spoken. We should > absolutely continue pursuing this topic with an eye to 8.1 so that library > updates are less painful moving forward. > > -Sara >
  111196
July 26, 2020 23:41 andreas@dqxtech.net (Andreas Hennings)
Hello all,

I would like to make my own alternative proposal from my previous message
more concrete.

I am going to describe a version that is not compatible with the existing
RFC at https://wiki.php.net/rfc/named_params.
It _could_ be modified to be an extension to the existing, accepted RFC for
named parameters.
But I think it is useful to discuss this version first.

1. Calls with named arguments can only happen on:
  - explicitly named functions (e.g. no call_user_func()).
  - constructor calls with an explicitly named class.
  - object methods, if the object variable is type-hinted or a type is
known at compile time.
2. Named arguments in calls are converted to number-indexed arguments at
compile time.
3. Parameter name lookup is done based on the type-hinted class or
interface. Parameter names on the actual object are ignored.

Example:

interface I {
  function setBackgroundColor($bgcolor);
}

interface J {
  function setBackgroundColor($background_color);
}

class C implements I, J {
  function setBackgroundColor($color) {}
}

class X {
  private J $colorable;

  function foo(I $colorable) {
    // Good: Parameter names from "I::setBackgroundColor()" are used.
    $colorable->setBackgroundColor(bgcolor: 'green');

    // Error: Unknown parameter name for I::setBackgroundColor().
    $colorable->setBackgroundColor(color: 'green');

    // Error: Unknown parameter name for I::setBackgroundColor().
    $colorable->setBackgroundColor(background_color: 'green');

    // Good: Parameter names from "J::setBackgroundColor()" are used.
    $this->colorable->setBackgroundColor(background_color: 'green');
  }
}

// Parameter names from C::setBackgroundColor() will be ignored within
->foo().
(new X())->foo(new C());

----------

Future extensions:

Allow to define "descriptive pseudo-interfaces" to provide reliable
parameter names, if the original package cannot be trusted to keep
parameter names stable.

interface K describes I {
  function setBackgroundColor($color);
}

function foo(K $colorable) {
  // Parameter names from K::setBackgroundColor() are used.
  $colorable->setBackgroundColor(color: 'green');
}

// Parameter names from C::setBackgroundColor() will be ignored within
foo().
foo(new C());

-----------

Why?

This version of named parameters can be used with any existing old
interface.
It is compatible with any 3rd party library that may implement an interface
with renamed parameters.
It also allows inheritance from multiple "equivalent" interfaces which use
different parameter names.

Only the package that defines the interface needs to keep the parameter
names stable between versions.

-----------

How to make this compatible with the existing RFC?

We could introduce an alternative method call syntax for calls that should
take parameter names from a specific interface, instead of the actual class
instance.
Eg.

$obj->{J::setBackgroundColor}(background_color: 'green');

This would be undesirably verbose for long interface names..

-----

Best
Andreas






On Fri, 24 Jul 2020 at 18:14, Andreas Hennings <andreas@dqxtech.net> wrote:

> TLDR > Only consider parameter names from a type-hinted interface, ignore > parameter names from the actual class. > > ----- > > I had a look at > https://wiki.php.net/rfc/named_params#to_parameter_name_changes_during_inheritance > Indeed I have concerns about this, because a call with named arguments > would make your code incompatible with 3rd party implementations of the > interface that do not keep the parameter names. > I would see this as a flaw in a newly added feature, which should be fixed > during the feature freeze. > I might even say the current version as described in the RFC is broken. > > I have no objection to the OP's proposal, but would like to add some > alternative ideas to the discussion. > > My first idea would be to only consider the parameter names in the > original interface, and ignore all renamed parameters in sub-classes. > This would cause problems if a class implements multiple interfaces that > declare the same method, but with different parameter names. > > So, a better idea would be like this: > - The variable must be type-hinted with an interface. E.g. as a parameter > or as object properties. > - named arguments always use the parameter names from the type-hinted > interface. > > This would need some static analysis at compile time, and we need to be > able to type-hint regular local variables. > > An alternative would be to somehow "import" the parameter names at the top > of the file, somehow like so: > > use Acme\Animal::bar; // Parameter names from this method will be used. > > (this syntax probably needs more thought). > > Yet another option would be to somehow specify the interface in the method > call.. > > ((\Acme\Animal) $instance)->foo(a: 'A', b: 'B'); > > Regards > Andreas > > > > On Fri, 24 Jul 2020 at 17:41, Sara Golemon <pollita@php.net> wrote: > >> On Fri, Jul 24, 2020 at 10:10 AM Nikita Popov ppv@gmail.com> >> wrote: >> > > > You added PHP 8.0 as a proposed version, but that will not be >> possible >> > > > anymore 2 weeks of discussion + 2 weeks of voting are not possible >> to >> fit >> > > > in before the feature freeze, which is in 11 days. >> > > >> > > While you are technically correct, the primary point of a feature >> freeze >> > > is not allowing in completely new features. >> > > It will always happen that there are changes and extensions to RFCs >> > > introduced for the next version which may need to be addressed first, >> > > because there is massive benefit to the new feature in that case. >> (from >> a >> > > backwards/forwards compatibility standpoint for example) >> > > >> > We should of course be open to making minor adjustments due to >> > unanticipated issues after feature freeze -- after all, that's where we >> > gain experience with new features. The emphasis here is very much on >> > *minor* and *unanticipated* though. >> > >> >> Endorsing this. Anything post FF needs to be held to a high standard. >> Minor and Unanticipated changes. >> * The option to set alt-names on parameters is substantial. >> * Making named params explicitly opt-in is less substantial (though >> certainly not trivial), especially if it were hung off an attribute which >> would mean no actual new syntax, but Niki's point that it wasn't an >> unknown >> or unanticipated issue stands. >> >> I share OP's worry that this pushes workload onto library and framework >> maintainers, and I wouldn't look forward to the review of all APIs to make >> sure their names are correct, but the vote has spoken. We should >> absolutely continue pursuing this topic with an eye to 8.1 so that library >> updates are less painful moving forward. >> >> -Sara >> >
  111197
July 27, 2020 01:18 tysonandre775@hotmail.com (tyson andre)
Hi Andreas Hennings,

> 1. Calls with named arguments can only happen on: >   - explicitly named functions (e.g. no call_user_func()). >   - constructor calls with an explicitly named class. >   - object methods, if the object variable is type-hinted or a type is known at compile time.
This proposal would seem to depend on moving opcache into core, among other things. Depending on how it's implemented, the condition of "a type is known at compile time" may also depend on whatever opcache optimization passes were enabled, which would make the behavior of whether this throws an Error at runtime unintuitive to users. (e.g. `$a = SOME_OPTIMIZABLE_CONDITION ? new A() : new B(); $a->someMethod(namedArg: value);`) - `SOME_OPTIMIZABLE_CONDITION` may depend on the php version or environment (e.g. `PHP_OS_FAMILY == 'Windows'`) A recent secondary poll on a rejected RFC I proposed did indicate broad interest in moving opcache to php's core, but I doubt that'd be done in 8.0 due to the feature freeze, and I also doubt optimizations would always be enabled because of the overhead of optimization for small short-lived scripts ( https://wiki.php.net/rfc/opcache.no_cache#if_you_voted_no_why ) Also, the times when a type is known (with 100% certainty) at compile time are known by opcache but unintuitive to users, due to the highly dynamic nature of PHP (`$$var = 'value'`, references, calls to require()/extract() modifying the scope, globals being effectively modifiable at any time, etc.) Even for typed properties, the existence of magic methods such as `__get()` (e.g. in subclasses) means that the type of $this->prop at runtime is uncertain. - `__get()` is called if it exists when a declared fetched property (typed or otherwise) was unset. Regards, - Tyson
  111200
July 27, 2020 12:22 andreas@dqxtech.net (Andreas Hennings)
On Mon, 27 Jul 2020 at 03:18, tyson andre <tysonandre775@hotmail.com> wrote:

> Hi Andreas Hennings, > > > 1. Calls with named arguments can only happen on: > > - explicitly named functions (e.g. no call_user_func()). > > - constructor calls with an explicitly named class. > > - object methods, if the object variable is type-hinted or a type is > known at compile time. > > This proposal would seem to depend on moving opcache into core, among > other things. > Depending on how it's implemented, the condition of "a type is known at > compile time" may also depend on whatever opcache optimization passes were > enabled, > which would make the behavior of whether this throws an Error at runtime > unintuitive to users. >
Obviously there would need to be a consistent definition about how the type of a variable should be determined. And there would need to be a way to determine this at runtime, in case that opcache is not enabled. This could be the same system that is responsible for runtime type checks. Perhaps we should drop the "is known at compile time" and only support explicit type hints. Anything we do here should be dumb static analysis, not smart static analysis. E.g. function foo(I $obj) { $obj->setColor(color: 'blue'); // Good, based on I::setColor(). $obj2 = $obj; $obj2->setColor(color: 'blue'); // Error, because simple static analysis cannot determine the type of $obj2. }
> (e.g. `$a = SOME_OPTIMIZABLE_CONDITION ? new A() : new B(); > $a->someMethod(namedArg: value);`) >
Any "optimizable condition" would have to be treated like a regular variable with unknown value. So in the above case, named arguments would not be allowed.
> > - `SOME_OPTIMIZABLE_CONDITION` may depend on the php version or > environment (e.g. `PHP_OS_FAMILY == 'Windows'`) > > A recent secondary poll on a rejected RFC I proposed did indicate broad > interest in moving opcache to php's core, > but I doubt that'd be done in 8.0 due to the feature freeze, and I also > doubt optimizations would always be enabled because of the overhead of > optimization for small short-lived scripts > ( https://wiki.php.net/rfc/opcache.no_cache#if_you_voted_no_why ) > > Also, the times when a type is known (with 100% certainty) at compile time > are known by opcache but unintuitive to users, > due to the highly dynamic nature of PHP > (`$$var = 'value'`, references, calls to require()/extract() modifying the > scope, globals being effectively modifiable at any time, etc.) > Even for typed properties, the existence of magic methods such as > `__get()` (e.g. in subclasses) means that the type of $this->prop at > runtime is uncertain.
> - `__get()` is called if it exists when a declared fetched property (typed > or otherwise) was unset. >
Good point. One thing to note, it seems __get() is only called when the property is accessed from _outside_. https://3v4l.org/sY96q (just a snapshot, play around with this as you feel) I personally don't really care that much about public properties, I could happily live in a world where named parameters are not available for objects in public properties. Or alternatively, we could say that it is the responsibility of the __get() method to return an object that is compatible with the type hint on the public property, IF named parameters are used. Even if all of this does not work, I think the idea still has merit: Evaluate the parameter names based on a known interface, instead of an unknown implementation which may come from a 3rd party. Perhaps a type hint is not the best way to determine this interface.. Greetings Andreas
> > Regards, > - Tyson
  111201
July 27, 2020 18:40 rowan.collins@gmail.com (Rowan Tommins)
Hi Andreas,


On 27/07/2020 00:41, Andreas Hennings wrote:
> 1. Calls with named arguments can only happen on: > - explicitly named functions (e.g. no call_user_func()). > - constructor calls with an explicitly named class. > - object methods, if the object variable is type-hinted or a type is > known at compile time. > 2. Named arguments in calls are converted to number-indexed arguments at > compile time. > 3. Parameter name lookup is done based on the type-hinted class or > interface. Parameter names on the actual object are ignored.
While this is an interesting concept in general, it introduces a much larger change to the semantics of the language than seems justified for this particular problem - it would effectively require introducing an element of "static typing" into the language. By "static typing", I mean this: > variables have an intrinsic type, and the same value can behave differently depending on the type of variable that holds it Which could be contrasted with "dynamic typing" like this: > values have an intrinsic type, and operations will be selected based on those values, regardless of which variables hold them Although we have various type annotations in the language now, they are all just restrictions on the types of value a variable can hold; they don't change the behaviour of that value. With this proposal, these two function would potentially do different things when given the exact same argument: function a(FooInterface $arg) { $arg->doSomething(namedparam: 42); } function b(BarInterface $arg) { $arg->doSomething(namedparam: 42); } Static typing of that sort is a useful feature of other languages, and there are people who'd love to see PHP go that way, but it's not something we should bolt on in a hurry just to solve an issue with named parameters. Regards, -- Rowan Tommins (né Collins) [IMSoP]
  111204
July 27, 2020 23:21 andreas@dqxtech.net (Andreas Hennings)
Good point about the static typing!

On Mon, 27 Jul 2020 at 20:40, Rowan Tommins collins@gmail.com> wrote:

> Hi Andreas, > > > On 27/07/2020 00:41, Andreas Hennings wrote: > > 1. Calls with named arguments can only happen on: > > - explicitly named functions (e.g. no call_user_func()). > > - constructor calls with an explicitly named class. > > - object methods, if the object variable is type-hinted or a type is > > known at compile time. > > 2. Named arguments in calls are converted to number-indexed arguments at > > compile time. > > 3. Parameter name lookup is done based on the type-hinted class or > > interface. Parameter names on the actual object are ignored. > > > While this is an interesting concept in general, it introduces a much > larger change to the semantics of the language than seems justified for > this particular problem - it would effectively require introducing an > element of "static typing" into the language. > > By "static typing", I mean this: > > > variables have an intrinsic type, and the same value can behave > differently depending on the type of variable that holds it > > Which could be contrasted with "dynamic typing" like this: > > > values have an intrinsic type, and operations will be selected based > on those values, regardless of which variables hold them > > Although we have various type annotations in the language now, they are > all just restrictions on the types of value a variable can hold; they > don't change the behaviour of that value. With this proposal, these two > function would potentially do different things when given the exact same > argument: > > function a(FooInterface $arg) { $arg->doSomething(namedparam: 42); } > function b(BarInterface $arg) { $arg->doSomething(namedparam: 42); } >
It would be an edge case, if both of them are valid but with swapped parameter names. In the more common cases, one of them would simply be broken. But your point is still correct. I would argue that this is still better than the behavior with the current RFC: The developer who implements the function a or b only knows the parameter names as in FooInterface or BarInterface. They know nothing about the actual class of the object passed in as $arg. A name lookup based on the known interface is closer to what the developer wants, and is guaranteed to work for all implementations of the interface. The main inheritance contract for methods is still based on parameter order: E.g. interface I { function foo(A $x, B $y); } interface J extends I { function foo(A $y, B $x); } The parameter names can be swapped, but the types have to remain in order.
> > > Static typing of that sort is a useful feature of other languages, and > there are people who'd love to see PHP go that way,
Count me in :)
> but it's not > something we should bolt on in a hurry just to solve an issue with named > parameters. >
You have a point. It would not be a full-blown static typing, but at least it has the smell of it. The parameter name lookup would be based on the variable itself, instead of the value. As mentioned, there could be other ways to determine the version of the method that should be used for parameter name lookup. The options I can come up with atm would make the code more verbose, and/or look awkward, and probably already clash with existing language features. But perhaps there are better ways. Either in the call itself: $x->{FooInterface::doSomething}(namedparam: 42); FooInterface::doSomething::invoke($x, namedparam: 42); Or through a declaration in the file: use \Acme\FooInterface::*(); $x->doSomething()
> just to solve an issue with named > parameters.
I still think the current RFC is deeply flawed and should not have been accepted in its current form. It adds uncertainty and fragility to the language. The Liskov substitution principle was something we could rely on, guaranteed natively by the language. But with named parameters it is not. I saw the explanations in the RFC about inheritance, but I don't find them convincing. But I am not a voting member and also I did not pay attention when this discussion was happening. And I am just one person, I don't want to make more than one person's worth of drama. Perhaps we will see people mostly use this for constructor or static calls, and avoid it for polymorphic method calls. For these cases it seems like a useful feature.
> > > Regards, > > -- > Rowan Tommins (né Collins) > [IMSoP] > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >