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