
On 19.04.20 12:57, Steven D'Aprano wrote:
On Sat, Apr 18, 2020 at 09:13:44PM +0200, Dominik Vilsmeier wrote:
func(foo, **, bar) vs. func(foo, **{bar})
It's still a mode switch, only the beginning and end markers have changed. Instead of `**,` (or `**mapping,`) we then have `**{` as the opening marker and instead of `)` (the parenthesis that closes the function call) we have `}` as the closing marker. How do you define a mode switch? I don't have a clear definition, instead my point was to show that there is no substantial difference between `**` and `**{...}` (though you seem to think differently). Is a list display a mode? Is a string a mode? Is a float a mode? I'd say yes, technically they are. There's a substantial difference between writing `sys.exit()` and `"sys.exit()"`. Same for 'comment mode': `# sys.exit()` , and most IDEs have shortcuts for switching it on and off. However I think a major difference is that all these can exist in different contexts, also in isolation, and hence we think of them as self-contained entities. The modal aspect is only relevant to the compiler. In some sense, maybe, but to me the critical factor is that nobody talks about "list mode", "string mode", let alone "float mode". Its about the mental model.
With `func(foo, **, bar, baz, quux)` if I use `**` as a pseudo-argument, the interpreter switches to "auto-fill" mode and everything that follows that (until the end of the function call) has to be interpreted according to the mode. With your proposal if I use `**{` then the interpreter similarly switches to auto-fill mode and everything that follows until the next `}` is affected by that mode. There's no big difference. The idea behind `**` is that you could also use `**kwargs` instead and `**` is just the case when you don't have anything to unpack (as an analogy to `*args` vs. `*` in a function definition when you don't need to consume varargs). A few people immediately started describing this as a mode, without prompting. I think it is a very natural way of thinking about it. I think what obscures the modal aspect of `**{...}` is the fact that it somehow looks like an expression, but it isn't. It's special syntax that can only be used in function calls, to tell the compiler that it should autofill the parameter names. It's a mode in disguise. And we have no way of turning the mode off. So if there is every a proposal to allow positional arguments to follow keyword arguments, it won't be compatible with auto-fill mode. That is true, it doesn't have an explicit end token. However I'm not convinced that ruling out the positional-after-keyword-arguments option is a relevant argument against it. With `func(foo, **{bar, baz, quux})` the mental model is closer to ordinary argument or dict unpacking. Nobody refers to this:
func(spam, *[eggs, cheese, aardvark], hovercraft)
as "list mode" or "argument unpacking mode". It's just "unpacking a list" or similar. No-one thinks about the interpreter entering a special "collect list mode" even if that's what the parser actually does, in some sense. We read the list as an entity, which then gets unpacked.
In this example `*` and `[eggs, cheese, aardvark]` are distinct entities, the latter can exist without the former and it has the exact same meaning, independent of context. So we think about it as a list that gets unpacked (and the list being a concept that can exist in isolation, without unpacking). With the proposed syntax we have `**{eggs, cheese, aardvark}` and here the `**` and `{...}` parts are inseparable. Even though the latter could exist in isolation but then it means something completely different. In the `**{...}` listing of names these not only refer to their objects as usual but also serve the purpose of identifying keyword parameter names. This unusual extension of identifier meaning is limited by `**{` and `}` and hence I consider it a mode, just like `**,` and `)`.
Likewise for dict unpacking: nobody thinks of `{'a': expr}` as entering "dict mode". You just make a dict, then unpack it.
And nobody (I hope...) will think of keyword shortcut as a mode:
func(foo, **{bar, baz}, quux=1)`
It's just unpacking an autofilled set of parameter names. Not a mode at all. And notice that there is absolutely no difficulty with some future enhancement to allow positional arguments after keyword arguments. "unpacking an autofilled set of parameter names" implies that two distinct actions take place. First the set of parameter names is autofilled and converted into something that can be unpacked, and then it actually gets unpacked. But that's not what is happening, you can't have `**({bar, baz})`. What you *describe* is another proposal, namely using e.g. `{:bar, :baz}` to construct the mapping and then `**` to unpack it; `**({:bar, :baz})` works without problem. So the `**{bar, baz}` syntax is not actually unpacking anything, it's a hint for the compiler to treat `bar, baz` as `bar=bar, baz=baz` and hence it's a mode switch.