139 comments

[ 4.1 ms ] story [ 185 ms ] thread
in python i started writing any function that takes >2 arguments as either

  def foo(*,a,b,c)...
or

  def foo(obvious_and_required_thing, *, a,b,c) ...
and noticed an immediate improvement in clarity.
What does * represent here? The "self" object?
It‘s a separator between positional and keywords. It’s * because the “rest” argument (*args) also makes following arguments keyword-only, so a lone star is essentially one which collects nothing.
So this requires the following args to be named? Neat!
Yes.

For python-level functions initially all arguments could be passed either positionally or by keyword, unless they slotted into the special `∗args` (positional-only) or `∗∗kwargs` (keyword-only) parameters (note: the star-prefixes is the important part here the names can be anything).

Now I'm saying this was for python-level functions because (AFAIK) at the C level it was always possible to rather easily define positional-only or keyword-only arguments.

Anyway one of the improvements of Python 3 was to allow parameters other than `∗∗kwargs` after `∗args`, with the effect of making them individually named but keyword-only (PEP 3102). A logical extension of the same was to allow keyword-only arguments without any positional, that's what the `*` pseudo-argument defines:

> The reasoning behind this change is as follows. Imagine for a moment a function which takes several positional arguments, as well as a keyword argument.

> Now, suppose you wanted to have 'key' be a keyword-only argument. Under the above syntax, you could accomplish this by adding a varargs (nb: `∗ignore`) argument immediately before the keyword argument.

> Unfortunately, the 'ignore' argument will also suck up any erroneous positional arguments that may have been supplied by the caller. Given that we'd prefer any unwanted arguments to raise an error, we could do this (nb: add an explicit assertion that `ignore` is empty)

> As a convenient shortcut, we can simply omit the 'ignore' name, meaning 'don't allow any positional arguments beyond this point'.

I understand that this is standard practice by now, but this is one of those cases where I can't help but feel the language design team strayed too far from the original spirit of python. Now there's yet another possible interpretation of an asterisk: multiplication; splatting; exponentiation; declaring variadic arguments; and now declaring that all subsequently declared arguments are to be keyword-only. And I understand that it sort-of relates to declaring variadic arguments, but it certainly isn't declaring variadic arguments, and in my opinion reusing the same token in the same syntactic construct for two wildly different purposes is destined to cause confusion. For evidence, see the other replies: people don't know about this and can't easily tell what it does. This runs counter to the Zen: "Readability counts", "Explicit is better than implicit", "Special cases aren't special enough to break the rules.", etc.

I think JavaScript got this right where the semantic resolution steps of arguments pretty closely follow the steps for assignment, which means once someone understands the basic idea of object destructuring they get named arguments for free, in the form of destructured object parameters:

    const foo = (positionalArg1, {namedArg1, namedArg2 = 'foo'}) => { ... }
    foo(1, {namedArg: 'bar'}) 
    // resolved same as 
    const [positionalArg1, {namedArg, namedArg2 = 'foo'}] = [1, {namedArg: 'bar'}]
I'll disagree with you here. In Python `*` is already a extremely popular symbol for expanding and there's no usage conflict between expand and numeric multiplication. This change gels really nicely with the rest of expand ecosystem.

I think it's much more beautiful and convenient than your JS example which in all honesty looks necessary cluttered and ugly.

But there’s no expanding happening here? So why take a token that’s immediately recognizable as either “expanding” or “muliplying-ish” and extend it to mean “or enforcing named parameters and not expanding”?

> cluttered and ugly

I won’t argue with you about visuals because I don’t think they’re all that important. But, I think it’s undeniable that reusing the same syntactic concepts across different semantic use cases is quite a lot more logically clean & beautiful than introducing new syntax that other HN commenters with past python experience can’t even understand. If someone sees the JS named parameter syntax at a declaration and has any JS experience, they’ll almost certainly figure out what to do - if someone sees the asterisk, even with Python experience, they’ll think “what the heck is this asterisk?” And again, the other commenters on this thread are clear evidence that this is indeed as unintuitive as I describe.

> But there’s no expanding happening here?

There's a collection of 0 arguments. the "∗" was introduced specifically as a shortcut for:

    def foo(a, *ignore, b):
        if ignore:
            raise TypeError
forgoing the name simply signals to the language that it should collect nothing.

It's a logical extension of the assumption that anything which follows `∗arg` is a keyword-only parameter.

    *args
creates a catch-all variadic function parameter called `args` which matches all positional parameters not heretofore assigned

    *
(same operator, without a name following) indicates that no more positional parameters may be matched after this point

They are close enough cognitively to warrant reuse IMO.

> This runs counter to the Zen: "Readability counts", "Explicit is better than implicit"

Huh? It's both making parameters more explicit and functions using it more readable and self-documenting.

In fact, the old way, before bare / and * in args list, where functions essentially had mandatory optional-keyword arguments made every function create an API in violation of “There should be one-- and preferably only one --obvious way to do it.”

I’m not interested in comparing it to the old way, that’s saying “this unintuitive way of doing things is a better way of doing them than before when they were impossible”. Achieving that is a too low a bar to be worth discussing.

It is very much not readable, as evidenced by the other python-experienced commenters on this thread having no clue what it means (“readability” doesn’t mean “easy to understand for people who already know what it means”, as achieving that is a very low bar. What should be targeted is “obvious enough to have a clear meaning even to those who haven’t studied the exact section of the spec”, which is possible in the JS approach as they’re reusing the same syntactic constructs throughout the language, but is impossible in the Python approach because they introduce new syntax to work around every little limitation of their existing syntax)

Passing an object to a function call, at best, can be described as a workaround.

Named argument are more explicit.

I wrote a lot of old python and js code with dict/object as argument, they were dreadful

I'm ambivalent.

There's a tension in PHP-land between PHP's roots as a low-ish level, get-it-done, hackish language, with its big standard library and simple scalar types, and the better-organized and quite vocal developers who want it to be more Java-like, with great big frameworks and many deeply-nested complex class hierarchies. Instead of unwieldy hobbyist-hacker balls of mud, you build enterprise-scale balls of mud.

Named arguments don't do a lot for the first group. It looks like the big point in favor is not having to look up a reference for which-arguments-go-where in functions anymore, but a good IDE already does that for you.

But there is a common anti-pattern where you have some polymorphic function or interface, and you want to accrete your arguments somewhere and then bundle them up and call the function, so then you see things like [1]:

    call_user_func($function, ['length' => $n, 'foo' => $bar, ...]);
...and that is bad because it totally bypasses all of PHP's type-checking and leaves it up to the called function to sanity-check the input types instead of letting the compiler do it, and basically nobody does that anyway.

So named parameters would fix that at least, which would be nice. I'm just not looking forward to the impact this is going to have on code readability, because everyone (including this article) is going to go, "oh, now my parameter list is too long!", and break the function signature into one-line-per-parameter, which is vile.

[1]: I'm guilty of this too.

> break the function signature into one-line-per-parameter, which is vile

Speak for yourself. If each line indicates what it is the parameter for I couldn’t care less.

Nobody will use this for functions that have just two parameters. It’s the ones that have 10 possibilities that are crazy.

Why not use a dictionary or parameter object when you get past 2 arguments? Or is that not a thing in PHP?
That's what the above comment was referring to when saying people to things like:

  call_user_func_array($function, ['length' => $n, 'foo' => $bar, ...]);
> and basically nobody does that anyway.

That's it, I'm officially a nobody :-)

> leaves it up to the called function to sanity-check the input types instead of letting the compiler do it, and basically nobody does that anyway.

You don’t check the input types?

You're reading the sentence incorrectly: "and basically nobody does that anyway", means the preceding type checking argument you refer to is moot. So I insist, I am a nobody (according to this definition).

Btw I don't always sanity check the parameters, mostly at the system edges only.

Not in get-it-done, hackish scripts.
I'd think that such a solution doesn't solve but merely moves the problem.

A method with N arguments, where N > 2, with a call that is messy due to the number of arguments could instead take a single argument being a parameter object where the constructor for the parameter object takes the same N arguments.

A constructor with N arguments with a call that is messy due to the number of arguments ...

Aah, so then you use the Builder Pattern! Instead of one constructor taking N arguments, have a constructor that takes no arguments and N function calls to initialize the constructed values. It's cleaner because it's a named pattern! /s
That is possible, but requires writing a class, which increases mental load
With a dictionary (array in PHP) you lose strict typing and it's possible for the person constructing it to make a typo and it won't cause an error, the called function will just think an optional argument was left out.

Writing and maintaining parameter classes is just tedious.

Typescript can do it, so why not add the same feature to PHP as well?

As a JS/TS developer, all I see is avoiding two characters: {}, everything else is possible in JavaScript and well-typed in TypeScript.

PHP 8

    save(
      value: 3
    )
JS 1

    save({
      value: 3
    })
PHP's strict typing is only enforced at runtime, and because of that, its type system is very simple, unlike Typescript's. You can enforce that a variable is an array, but not that it's an array of ints, let alone that it's an array with certain types under certain keys.

Although there is external tooling that uses static analysis for such more complicated types.

You can get the best of both approaches in PHP 5.6 or later using the argument unpacking "..." operator that works similarly to the */"splat" operator in Python or Ruby: https://www.php.net/manual/en/migration56.new-features.php
it's not the best of both worlds though, splat should only be used on something where you're basically doing the same thing to a bunch of things.

like add(1,2,3,4,5) ->

  function add(...$nums){
    $count = 0;
      foreach($nums as $num) { $count += $num; }
  }
If you know what arguments you're getting, then the more sound way would be just use an associative array, but either way you really don't get great type checking.

This is a much better solution.

I do this all the time, but it feels wrong... and you don't get type checking, this feels like it makes more sense and logical.
Other than plain object constructs these kinds of functions are unheard of and better handled with a parameter object or array.

I can't help but feel if they just set up a clean way to name construct Params for classes this wouldn't need to happen.

Why are they better handled with a parameter object or array? https://news.ycombinator.com/item?id=23962955

What is the functional difference between a named construct "Params" and just allowing named parameters?

(comment deleted)
> What is the functional difference between a named construct "Params" and just allowing named parameters?

That "just" is doing a lot of work. I've not used PHP for a few years, but off the top of my head I can forsee the following problems:

- `call_user_func` would need to change to support this, e.g. accepting and passing-along named parameters.

- `call_user_func_array` would need to change, e.g. passing along associative-array arguments as named parameters.

- `func_get_args` would need to change, e.g. returning an associative array

Other parts of the standard library, like ReflectionParameter, would also need corresponding changes.

On their own I don't think those are necessarily bad. However, this would break a lot of code which relies on those functions and their invariants. For example, currying and partial application would become more complicated (e.g. http://chriswarbo.net/blog/2014-02-21-partial_application___... ).

I'm not necessarily against this change, but I would make the following points:

Extending APIs is a breaking change, since it breaks invariants/contracts that previously held (in this case by function definitions and call sites). This breaks backwards-compatibility, and that should be acknowledged and stated explicitly, rather than ignored. Here's a quick example of code which breaks, since it relies on the now-violated invariant that the parameter order of a function call matches that of the definition:

    function call_with_strings() {
      $args    = func_get_args();
      $f       = array_shift($args);
      $strings = [];
      foreach ($args as $arg) {
        $strings[] = strval($arg);
      }
      return call_user_func($f, $strings);
    }
Secondly: there's a definite bandwagon effect between scripting languages like PHP/Python/JS/etc., for better (easier to transfer between) or worse (uncompelling monocultures lacking innovation/USP). Just because a feature exists in one, doesn't necessarily make it a good idea for the others (or the first, for that matter!); conversely, there are lots of great features out there which scripting languages don't seem to have picked up, and which are hardly ever brought up in these discussions. For example:

- Default arguments are generally supported, e.g. `parseInt(i, base=10)`. Alternative approaches to the same problem, like currying, are mostly absent, e.g. `parseIntFor(base, i); parseInt = parseIntFor(10);`

- Generators are generally supported, e.g. `for x in foo: yield x`. Alternative approaches to the same problem, like delimited continuations, are mostly absent, e.g. `reset; for x in foo: shift(x);`. This is especially interesting, since it's just an extension of exception handling, which is another generally-supported feature!

- Named parameters are generally supported, e.g. `foo(a=1, c=5)`. Alternative approaches to the same problem, like row-polymorphism, are mostly absent, e.g. `foo(['a' => 1, 'c' => 5])`.

So one of your arguments is that it'll be difficult to maintain the C implementation of call_user_func, call_user_func_array and func_get_args? But this has already been done by the PHP maintainers.

As for your argument that it breaks code that today relies on argument order... Even in current versions of PHP, nothing in the language stops me from changing function f($a) to function f($b, $a) between v1 and v2 of my library and your code is equally broken. Whether I as a function author decide to re-order my arguments is a decision informed but not dictated by the existence of named arguments.

I don't understand your rant about bandwagons/monoculture. If you ask me the reasons most popular langauges are moving in the same direction is because popular languages are stealing each others' best ideas, which is a good thing. All your "alternative" solutions read like horrible code to me compared to the now commonly accepted designs.

> So one of your arguments is that it'll be difficult to maintain the C implementation of call_user_func, call_user_func_array and func_get_args?

No, I wasn't talking about C; or about language implementations at all. I was arguing that the phrase "just allowing named parameters" gives the impression that this feature would only affect function calls and definitions, when that's not the case. The most obvious repercussions are for `call_user_func`, `func_get_args`, etc. whose API would need to change. That induces changes to code that calls those functions, and code that calls that code, and so on.

Although PHP is famously un(der)specified, I'm talking about things like "`call_user_func(f, args...)` is equivalent to `f(args...)`"; anything relying on that property will break in the presence of named parameters (since they may be given in a different order), unless patched to take it into account.

> Whether I as a function author decide to re-order my arguments is a decision informed but not dictated by the existence of named arguments.

Sure, but that's just a first-order effect. It's a breaking change for higher order code (where functions and arguments are data, and calling is an operation on that data). That's why I gave an example of higher-order code.

As a silly analogy: if PHP extended its default numeric types to be complex numbers, that wouldn't affect existing values like `$age = 18` or `$length = 50`, since they're "informed, but not dictated by the existence of complex numbers"; it also wouldn't affect calculations using these values like `plus($age, $length)`. Yet it would have profound effects on numeric functions, which may have to be patched to take complex values into account: some functions, like `array_sum`, might work fine as-is; whilst others, like `array_slice`, might make no sense given the new situation (how might we take `3+5i` elements from an array?).

> All your "alternative" solutions read like horrible code to me compared to the now commonly accepted designs.

This sentence is a good example of what my "rant" is about. Why should improvements depend on what's "commonly accepted" (surely that's antithetical)? Could you elaborate any further on what makes those examples "read like horrible code"?

As a more detailed example we can look at `switch`: it's clearly a "now commonly accepted design", due to its prevalence in many "popular languages" like PHP/Javascript/etc. An alternative approach which solves the same problem is `match`, as used by ML. However, ML is not a popular language, and ML also introduced currying which you say "read[s] like horrible code". Does that make `switch` better than `match`?

This is an interesting example since a recent Python Enhancement Proposal is trying to add `match` to the language. This proposal seems credible, since the author list includes Guido van Rossum (the creator of Python): https://www.python.org/dev/peps/pep-0622

Perhaps this is an outlier, since Python doesn't already have an equivalent like `switch`?

I wonder if the existence of that PEP changes your opinion of `switch` versus `match`? Maybe not; after all, Python doesn't have `switch` so maybe `match` is better than nothing. What if I showed you an equivalent proposal for adding it to Javascript (using the keyword `case`, like the non-popular language Haskell)? https://github.com/tc39/proposal-pattern-matching

At what point does a feature go from "horrible code" to "stealing each others' best ideas"?

What makes this even more interesting is that the very first sentence of PEP622 m...

> Nobody will use this for functions that have just two parameters. It’s the ones that have 10 possibilities that are crazy.

It can be super useful even for functions with low arity e.g. `max(seq, key=func)`.

I'll use it for functions with two parameters, array functions and the order you pass in arguments is not standardized. I can start just using named functions to make it clear and easier to read/write.
You can have typed dictionaries too, even in a language similar to PHP: https://mypy.readthedocs.io/en/stable/more_types.html#typedd...

But anyway, I'd prefer anonymous records like in F#. There's no reason to have static and dynamic mappings have the same syntax, especially if they're not the same to the type checker, even a compiler could statically dispatch the static variant. So I agree with you that dicts are overused.

who want ... and many deeply-nested complex class hierarchies

Are there really people aiming for that, as a goal? Or is it rather just the result of large-scale software? And/or if it's really complex, then perhaps that's just the result of suboptimal design, not the intent?

Yes, sort of. There are many people that think of OOP as the goal and not the tool, that a more OOP solutions is better. Some of them equate complex class hierarchies with a job well done.

I'm being a bit hyperbolic, but I've worked that think a if/switch statements are a code smell that should be converted to complex class hierarchies.

I can sympathize with that, but at the same time: the idea that the language and its OOP implementation is a tool rather than the goal, and the idea of improving said tool are not mutually exclusive. :)

In my experiences with C#, things like named params have been nice, even if it's often just syntactic sugar; these things can be quality of life improvements for the programmer without changing anything about the goal.

Named params helps the beginners, maybe even more for use of internal APIs

- no needle/haystack order problems anymore - no need going to docs to understand what a boolean or „magic value“ stands for

Not just beginners. Seeing the following in a pull request is bad for everybody:

    someFunction(username, true, false, 10)
Named parameters remove ambiguity and make the code easier to read and review, and even avoid bugs because if you're passing the wrong values it's immediately obvious.

One of the hidden issues with the above code comes in the future: if anyone ever changes the order of the 2nd and 3rd arguments in the function definition, and forgets to update this spot, the code will still "work" (no compile error) but instead will have the wrong runtime behaviour. In a pull request, you can't see all the affected spots that didn't get updated, so it is easy to slip through.

> tension in PHP-land between PHP's roots as a low-ish level, get-it-done, hackish language [...] and the better-organized and quite vocal developers who want it to be more Java-like

Agree, and I'd like to add the original purpose of PHP, and what made it popular in the first place was that it can be used as embedded PHP within SGML-ish processing instruction ie <!php ...>. Only that PHP made such a lousy hack job of it being not HTML-aware and not doing context-dependent escaping, that it would become the primary script injection/XSS attack vector on the web. PHP's forte IMHO is its community and the large installed base of apps (there doesn't exist anything like Magento with its first-party plugins by DHL, UPS, payment providers, etc on Node.js for example). Why PHP wants to become more like Java or JavaScript is a mystery for me, when there's a hell of a cleanup job to be done beforehand, such as fixing <?php ...>.

One-line-per-argument function signatures are nice for making clean diffs :)
> There's a tension in PHP-land between [...] hobbyist-hacker [vs] enterprise-scale

IMO the same tension exists in Python. The hobbyist-hackers are using the language to write small-ish scripts, and the enterprise-scale developers are working on million-line codebases.

Unfortunately for the hobbyist-hackers, at some point (perhaps when the BDFL started working at Dropbox), the focus of the language shifted from the first group to the second. That's why, for example, Python now has a complex type system - that many developers never use.

I would argue that has a strong correlation with the fact that universities are now teaching Python instead of Java. Once that alumni hits the job market, hobbyist-hackers are no longer the majority of users.
You'd have a point if there were any changes that are being introduced and putting developers on a straight-jacket. But I don't see this tension at all.

What is recent on Python3 that makes it hostile to hobbyists? The new features are optional, it's not like Python is forcing people to adopt mypy.

> What is recent on Python3 that makes it hostile to hobbyists?

Python has become a very complex language [0], and the complexity continues to increase. IMO it's too much for people who only write code for, say, a few hours a week.

> The new features are optional

I hope Python isn't becoming a "choose your own subset" language like C++.

[0] https://twitter.com/raymondh/status/1280946969116995584

The tweet shows a clear list of features (aside from the walrus operator) that are "complex" because they deal with inherit complexity. People who write code only for a few hours a week don't need to deal with asyncio or parallel programming, typing, cython, etc, etc.

The point about different frameworks and ecossytem is a strawman. Yes, it's large. No, it doesn't mean that is complex. No, you don't need to know all of it.

> I hope Python isn't becoming a "choose your own subset" language like C++

You are implying that are what is being added to the language have overlapping functionality. You really need a better argument than a FUD-y tweet to justify your concerns.

What current languages do you think are friendly to hobbysts, with low complexity overhead, like Python before the enterprise-scale refocusing?
> Python now has a complex type system - that many developers never use.

Python doesn't have a complex type system. How could it when, to this day, it doesn't bundle a type-checker?

Python had a way to annotate functions (PEP 3107), which the community largely used for type-hinting, so Python introduced better support for type hints (PEP 484 and various extensions since).

> IMO the same tension exists in Python. The hobbyist-hackers are using the language to write small-ish scripts, and the enterprise-scale developers are working on million-line codebases.

It's an endless cycle in all aspects of IT, the conflict between small tech and enterprise tech

Large tools from bureaucratic enterprises are useless (that's a given)

Shadow IT built around hacker tools gets the business goals through

Enterprise IT see that these tools are popular and actually meet business goals, but they didn't invent them

Enterprise IT attempts to justify itself and find something approximating the usability of the good tools from some "safe" vendor like Oracle, Microsoft, etc. After all nobody got fired for buying IBM.

Enterprise IT spends a fortune, everyone hates it, but old tools are defunded so can't be used.

Shadow IT build new ecosystem around different hacker tools to get the business goals through

And the cycle continues.

> Large tools from bureaucratic enterprises are useless (that's a given)

There are plenty useful tools that are large and built by bureaucratic enterprises. Stuff like ERP systems just has so many features to cover and tax laws to implement that it most likely can genuinely not be built by a small team.

(Disclosure: I work for such an enterprise, though I'm not involved in the development of software sold to customers.)

That many developers never use type annotations (nothing interesting has happened to the type system in a long time) kind of suggests that it's not a problem. The feature is there if you want it, if you don't you ignore it.
Type annotations aren't entirely optional, though. For example, they're required if you want to use data classes.

Even if they were optional, that would still be a problem. As above, too many optional features are turning Python into a "choose your own subset" language like C++.

(comment deleted)
>Named arguments don't do a lot for the first group. It looks like the big point in favor is not having to look up a reference for which-arguments-go-where in functions anymore, but a good IDE already does that for you.

I, for one, never particularly liked this last argument.

I get why it's being done that way, but the bit about parameter names being allowed to vary in subclasses feels... non-great.

As a result you can't really safely use named parameters on calls without more extensive control of what you're being passed: even if you typehint to a class or interface, you can just get passed a parameter-name-changed subclass that breaks your call anyway. Feels like a "strict" type warning would be in order rather than just silent acceptance of the variance combined with a hard error.

> I get why it's being done that way, but the bit about parameter names being allowed to vary in subclasses feels... non-great.

As justified, it feels necessary for backwards-compatibility reasons since named arguments are not defined as named, just used as such.

Still feels like a "strict warning" situation. Again, I can see why they have to do it that way because this was just not previously part of the public interface to a function, but it is now, so there should be some indication that doing this name mixing is, in fact, Now A Problem.

I guess it can just be handled on a static analysis/code-checker kind of level, but the language already has a system for emitting these kind of "code standards" warnings.

That's always been the way with PHP. It's always been the language of "you _can_ write nice code, but only if you follow strict rules and avoid this (long) list of bad practices that it encourages".

> Doctor, it hurts when I bend my elbow like this!

> So don't bend your elbow like that.

It's beginning to look a lot like -- typescript
generally yes, but specifically here typescript does not have named parameters (yeah you can emulate with a hack through object literals which is sad)
What's sad about it? Having interfaces defining the arguments of functions with complex parameters in a type safe manner and with support for auto-completion is one of the greatest thing to ever happen to JavaScript in my opinion. And you get named function arguments as a by-product of something way more powerful than just that.
I always wonder why PHP never seems to favour making breaking changes to keep things cleaner, if you aren't going to improve language constructs in a major version, when will they be cleaned up?
I think that post Python 3 nobody is going to do that.

It would be interesting to see a fork which removed all the crud though. Something that was easy to use for the person doing occaisional web work or beginner but without the traps and pitfalls.

Python 3 attempted too much change at once. The Python folks could have spread incremental rollout of the changes across several major version numbers and probably not have faced such slow adoption.
I thought the problem was carrying on support for 2.xx too long. Some people will never move on unless they are forced to.
I'm not sure that they could have spread it out. I believe that was the point of Python 3. Still, PHP may end up in the exact same position Python was in for the last decade, if they tried to do a similar change.

It could be worse of cause, they could end up like Perl 6 (now Raku), which makes Python 3 look like a successful transition in comparison.

> Python 3 attempted too much change at once.

Python 3 got this so, so wrong. It changed enough to break everyone's code, but not enough to make upgrading worthwhile.

It could have changed more, or changed less - done right, either would have been better than what actually happened.

>It would be interesting to see a fork which removed all the crud though.

My understanding was that some of that already exists in Hack. Although it seems they might've not gone far enough.

PHP 8 has plenty of breaking changes (https://github.com/php/php-src/blob/08e6c209550a268500bbdef4...), mostly in the department of throwing errors more aggressively, and fixing some long-standing "features" (like 0 == "foobar" being true). But there's a budget to this, and you have to strike a balance between how much code you break and what benefit you get out it.
(comment deleted)
The fact it errors if you over supply the function arguments is a bit worrying

Adding a key to an array shouldn't be a breaking change, if you over supply a function by position today PHP doesn't error

(comment deleted)
> if you over supply a function by position today PHP doesn't error

I'd argue that this it the worrying part. How often do you intentionally supply an extra argument, and how often is it an accident? I'd take the warning.

It's important to recognise what we can't do with that error in:

    <?php
    
    $user = [
      'age' => 25,
      'name' => 'Brent',
      'email' => 'brent@stitcher.io',
    ];
    
    function send_email(string $name, string $email) : void
    {
        //...
    }
    
    send_email(...$user);
^So this would error, even though it's a nice specification as far as the function is concerned, I'm all for strict static analysis but not at the expense of open maps

"If your program deals with information, these are among your primary problems: information is sparse, incrementally accumulated, open/extensible, conditionally available, formed into arbitrary sets in different contexts, merged with other information etc."

While you're right, I don't think that's something commonly used. I definitely can't remember the last time I wrote code where this would be useful.

That might be because usage of associative arrays is more or less discouraged in modern PHP in favor of value objects (especially with property promotion in PHP8). Main reason for that is that this usage is horrible for IDEs and static analysis. Way too easy to rename a parameter and create a runtime error. Typing is also enforced at usage instead of creation, and you have to repeat the type information in every function signature.

I really love this new feature however my only concern is that renaming some parameters will trigger a lot of changes in other places as a side effect, thus polluting diffs.
I get what you mean, but I remember at one point going through some kind of 'oh no, it's a large diff' or even 'do not do this because the diff will increase' phase. And I realized that's just not ok. There's already enough things to keep in mind when programming, adding an extra one just because some external tool which actually doesn't have a lot to do with how the code works, i.e. holding back changes because of diff, just isn't worth it.

Now, I admit cluttered diffs can be a problem, but there are workarounds: more fine-grained commits (especially separate commits for renames only) and better diff display. As an example of the latter: another commnter mentions one argument per line produces better diffs. It does for your standard display with +++/---, but tools like Sublime Merge have this covered and siplay the change in such a way you can immediately see what argument changed it's name, inline in the code. If it's was just a typo fix it will usually only highlight the changed character(s) not the complete name. Even going from all arguments on one line to one agrgument per line is covered, since it'll just display it as added whitespace and won't highlight the argument themselves indiacting they did not change.

My biggest pet peeve is when I meticulously craft a feature branch with commits neatly separated by subtopic so that it's pleasant to review... and then the reviewer just takes a glance, writes "lgtm" and squash-merges.
Not sure if the standard library has evolved since my PHP days, but this would at least solve the "key / haystack" issue. In which lots of the standard library had different orders in which parameters are passed. One can finally pass them named and no longer have to care about the order. This may also have been solved by better tooling since then, though.
a proper IDE solves that, but i always laugh when getting back to php why they made array_map and array_filter switch parameter order.
> why they made array_map and array_filter switch parameter order

It kind of makes sense if you look at the signatures (array_map takes a callable and a variable number of arrays, while array_filter takes a singe array and an optional calable), but yeah, it's horrible to use.

This was my favourite thing about learning Swift. I hadn’t used a named argument language before. It feels very relaxing knowing that the compiler can check all these things for you. I haven’t used php for years but it’s cool to see it adopting this. Maybe I’ll give it another go!
C# has had named arguments for a while now too - and I’m in love with them. They’ve helped me avoid many ugly bugs caused by refactoring, such as when a method has many parameters with the same type - and a refactoring reordered or added/deleted parameters - thanks to named-parameters the call-sites still worked instead of silently failing (because the call-site would still be type-correct and compiled, even if it was intent-incorrect).

My only peeve is that VS still doesn’t let you style/colour named-argument labels in a different colour, so it does add to visual-noise in the editor.

Adding labels to every argument is just silly, so I do have a personal hard-and-fast rule that any call-site of a method with literal arguments must be explicitly labelled (especially booleans!), and any call-site of a method with consecutive homogenous typed parameters must also be labelled - these rules have worked out fantastically for me.

Definitely a top feature of crystal lang. Not having to worry about what the default was to the second positional argument while using the third is satisfying, not to mention that it's basically optional self documenting, too.
Named arguments is a no-brainer for me for any kind of programming language. I am hoping D adopts named arguments too, although it's a very uphill battle there with many programmers coming from C/C++ background being opposed to it in the name of API stability.
I agree, it is a sensible improvement. What I don't like is spread operators in every form, but I can certainly live without

  funcy(null, null, null, null, null, null, cake, null, lie)...
I remember when dealing with Excel COM having lists of 30 arguments and more. And it made a difference if parameter 26 or parameter 27 was null.
Named arguments are great where you can't do it without them. It's good that language provides you the tool. And it's your job to as a developer to not abuse it.
Two of the languages I use most often (Python and Raku (formerly known as Perl 6)) have named arguments, and it improves clarity of code tremendously.

I don't know how well it fits into PHP, but if it's anything like in python or raku, go for it! :-)

(comment deleted)
There was a time when I would've been all-out in favor for this because one of the most often used functions in Drupal had no less than seven arguments: https://api.drupal.org/api/drupal/includes%21common.inc/func... it being seven, the running joke was knowing the Seven Parameters Of L is our initiation rite.

Then the options collapsed into an array https://api.drupal.org/api/drupal/includes%21common.inc/func... and that was in 2008.

And then URLs in 2015 became a value object and while you can still pass absolute => TRUE in options if you really want to, you can just call setAbsolute() on the object.

I guess if PHP core adopts this , phpstorm will quickly adopt as well and typing strpos( will immediately expand to strpos(haystack: , needle: so you can just fill that in. I guess? so maybe a reluctant yes but I am a bit wary of the APIs that will follow from this. I guess I have slowly grown fond of value objects? I must be getting old.

Ultimately every high level languages are converging to have the feature set of Kotlin, its nice to see them getting closer and closer.
Python has it since ever.
Common Lisp had it earlier.
Common Lisp had everything before every other language.
I'm talking about features in general, not just named parameters. Python doesn't have an advanced type system and yes I'm aware of mypy
There are a lot of arguments surrounding this subject. Just came in here to chime about how I miss named arguments in c++. Sure, I can emulate them [0] but at the cost of ide support, runtime and compile time.

https://github.com/nurettin/pwned/blob/770144a15efe7ab96bf4d...

They are effectively in C++20 without real penalty (designated initializers).
Yes, designated initializers help initialize a dto, much like what C# did back in 3.0 times. I wish they did something similar to function parameters so we didn't even have to create a data transfer object.
So here's a good test for the value of [language feature X]:

Do people naturally try and reinvent that feature in its absence?

So for named parameters you have:

1. Javascript would often be written with methods with a single "options" parameter, where "options" is an anonymous object and basically a map;

2. Day-to-day I write Hack. Many functions are similarly written taking a shape as an argument. Shapes are structs, basically, but the typechecker is smart enough to treat them as a well-defined by anonymous type.

3. I've also seen C code that does much the same with things structs.

Empirically there seems to generally be a demand for this kind of feature and it makes sense: long parameter lists are tedious and error-prone (have you ever seen a function that has 3 boolean parameters in a row?) where people try and be helpful by providing "optional" parameters (which are really just parameters with default values).

So sure, I'm all for named parameters.

Three? I found one in work codebase that had 9.

That's 2^9 states to consider.

JS records are superior.

I can create an object, then dynamically add various parameters before sending them rather than bogging down the function call. Destructuring in particular ensures this is pleasant for both the function creator and consumer.

You see a similar debate between StandardML and Ocaml though on a more fundamental level. SML uses structurally-typed records, so you can instantiate an inline record without needing to add any types and the function will destructure it for use. Ocaml went with nominal typing making this painful, so they added a complex optional argument syntax instead. Typescript is also structurally typed and I'd guess this is the reason.

> named arguments allow you to pass input data into a function, based on their argument name instead of the argument order

Granted, I'm not a PHP developer, but I can't understand why a change like this would be controversial. It sounds like it's optional, and would help greatly to reduce mistakes when passing arguments to functions? Having multiple lines of things being passed to a function may look odd to some, but couldn't the same argument be made as with arrays, that one line per "thing" makes sense, if nothing else to clean up diffs when collaborating on code?

Granted, I'm not a PHP developer, but I can't understand why a change like this would be controversial. It sounds like it's optional,

It's only optional when you're writing code. Even if you don't use features in your own projects you're undoubtedly going to be reading a lot of other's code, which may or may not use these features, and from that perspective added language complexity is a burden.

Sure, but it's only confusing when reading it if it's hard to implicitly sort of understand what's happening when you're not used to it, right? Named arguments seem pretty straightforward to me, in the sense that it's relatively obvious what they are when you see them. It's not exactly like a new operator or some kind of symbol unique to the language that is almost impossible to understand without looking it up.
Any framework/library code which abstracts over invoking a callable must also be extended to support associative arrays, and to avoid assuming that parameters will be given in the order they're defined. For example, things which play around with call_user_func and func_get_args (e.g. delayed evaluation, partial evaluation, currying, mapping over datastructures, composing/pipelining, etc.)
It's optional for the caller, but not for the callee. I think the controversy centers around two things:

a) Specific to this feature: For library maintainers, this means that argument names are now part of the public API, whether I like it or not, and renaming a parameter from $orderBy to $sortBy is now technically a breaking change. Yeah, you can put something in the readme saying "Heads up: I don't support named arguments and intend to rename arguments in semver-minor releases", but should you need to do that?

b) General to all new PHP features: PHP is a great language, with lots of crap code written in it. Will this feature result in people writing better or worse code? Just because something is optional doesn't mean it's a good idea, if it will result in the overall code quality of the ecosystem to get worse. Some people think that named arguments will make it too easy to create monster functions with too many parameters.

I personally support (aka am DELIGHTED by) named arguments, but I'm sympathetic to the concerns of library maintainers and I don't see a better way around it than a remark in the readme (at least until/unless they introduce e.g. a @@NoNamedArguments annotation in PHP 8.1). And I think that the benefits of named arguments far surpass the risk that they encourage people to create methods with too many arguments. But I can see both arguments.

a) is a good point. I often rename parameters after a while to have more precise meaning. But I guess the advantages of named parameters outweigh the potential drawbacks.
I'd hope some of the existing static analysis tools can figure out a name was changed faster than my tests!
(comment deleted)
As a python dev my mind is kinda blown that php didn't have keyword arguments yet. Especially with the current trend of using kwargs exclusively for better code quality and readability.
(comment deleted)
I've often wondered why there was no "default" keyword, so you could call a function like this:

foo('first arg', default, 'third arg');

Named parameters will do but they are more verbose.

Speaking whishes, method overloading would be great.

Having switched from 80% Java (20% about 5 other languages) to probably 75% Kotlin over the last year, one of my absolute favorite things named arguments.

When you name your arguments, simply supplying them will use the default if one has been specified. If one hasn’t been specified then you must provide it when calling and your compiler/IDE will definitely let you know.

I used to go out of my way with method overloads, JavaDocs, argument @annotations, and Builder classes in order to get this same level conciseness and cleanliness in my Java code. It is extra work (even with Lombok) but I personally can’t stand when I don’t keep it clean like that. Understandably a lot of other developers on my teams don’t always go to the same lengths.

I honestly haven’t ready the details on PHP’s plan for this, but with Kotlin’s names arguments I’ve found almost no need of method overloading anymore- most of those input variations are now covered (except for when you’ve got different types coming in, which usually can be handled with a common interface or base class).

Yeah I'm totally fine with named arguments, I even thought they would come sooner (I've been doing PHP since 1998 so I have some perspective ahah).

And yes overloading is more about different types for me. I guess I got accustomed to it with C# and nowadays Elixir. PHP 8 has union types so I guess it will do.

PHP will eventually evolve into Java. The only difference will be the runtime.
imo this looks more like kotlin. that said, there are several projects for running php on the jvm (quercus, jphp, ...).
I've been saying for a while now that PHP today is basically Java - generics + better nullability and with a much worse standard library.
It's pretty funny that named arguments were, for a long time, a feature present only in Objective-C of all the languages I tried (and I tried many).

It was one of the features of Objective-C I liked and I missed in any other language. I also think they were one of the reasons many developers disliked the language.

When Swift was introduced, it got named parameters from Objective-C and now they are getting more "mainstream", entering in a language much more widespread than the former two.

Python has had named arguments for a long time (since v1.4), which is arguably more widespread than PHP or Swift.
When I was learning Verilog HDL, named parameters are commonly used and highly encouraged. I wondered why more traditional programming languages didn't use this concept aswell for the same reasons the article goes into.
I first ran into them in Python myself. After using kwargs for the first time, I wondered the same thing. While I try to limit the number of args I pass to functions and methods, sometimes it simply can't be helped. Kotlin is a wonderful example of how both positional AND named arguments can be supported.
It feels like an anti-pattern, but I've been burned so many times by forgetting what order things go in, and not wanting to waste the 10 seconds to go look, that in a lot of methods if I have more than 2 parameters, I'll convert it to an array.

the associative array, thus giving me the named attributes.

It's a bit hackety, but the new named params looks like a much more elegant solution.

One issue also is if you have a method that takes 3 arguments and the 2nd is an integer not a string, and you pass a string you'll get an error... using an array, you can just test if the array key exists or not, and skip functionality as dictates by what's passed in.