
On Sat, Apr 18, 2020 at 02:13:51PM +0200, Alex Hall wrote:
My issue with this, and maybe it's what Andrew is also trying to say, is that it breaks our usual assumptions about composing expressions. `{u, v}` is an expression, it represents a set, and it always represents that wherever you put it. Under your proposal, these two programs are both valid syntax with different meanings:
f(**{u, v})
x = {u, v} f(**x)
True, and that does count as a (minor?) point against it. But not one that I think should rule it out.
Is there anything else similar in the language? Obviously there are cases where the same text has different meanings in different contexts, but I don't think you can ever refactor an expression (or text that looks like an expression) into a variable and change its meaning while keeping the program runnable.
Of course! There are many ways that this can occur. f(a, b, c) x = a, b, c f(x) are very different things. Here's another one: import name x = name import x Here's a third: del fe, fi, fo, fum x = fe, fi, fo, fum del x Here's an example so obvious (and trivial) that I'm almost embarrassed to include it: seq[index] x = [index] seqx If code is made of composable building blocks, those blocks aren't *characters*. What makes a composable block is dependent on context. To make up for how trivial the previous example was, here's a complicated one: for item in items: if item: continue do_stuff() versus: def block(item): if item: continue do_stuff() for item in items: block() I have often wished I could refactor continue and break into functions, but you can't :-( Although in this case at least you get a syntax error when you try. Here's an example with sequence unpacking: a = [1, 2, *seq] x = 2, *seq a = [1, x] Another example: class MyClass(metaclass=MyMeta) metaclass = MyMeta class MyClass(metaclass) That's just a special case of keyword notation itself: func(x=expr) x = expr func(x) Those are not the same, unless the first positional argument happens to be named `x`. And one final example: class C: def method(self): pass versus: def method(self): pass class C: method This is not an exhaustive list, just the first few things that came to my mind.
This proposal makes it harder for beginners to understand how a program is interpreted. It breaks the simple mental model where building blocks are combined in a consistent fashion into larger parts.
I **LOVE** the ability to reason about code with a simple mental model of building blocks. I would consider it a very important property of syntax. But it is not an absolute requirement in all things. I mean, we wouldn't want to say that function call syntax `f(x, y, z)` is a disaster because it looks like we combined a name with a tuple. I acknowledge that "cannot compose this" is a point against it, but I deny that it should be a flat out disqualification. There are lots of things in Python that cannot be trivially composed.
The example above looks a bit dumb, but maybe users will try:
``` if flag: kwargs = {u, v} else: kwargs = {w, x} f(**kwargs) ```
I think this point will apply to all(?) such syntactic proposals. I don't think this scenario is too different from this: if flag: f(u=expr1, v=expr2) else: f(w=expr3, x=expr4) If you want to refactor that, you can't do this: f((u=expr1, v=expr2) if flag else (w=expr3, x=expr4)) but you can just use regular dict unpacking: d = dict(u=expr1, v=expr2) if flag else dict(w=expr3, x=expr4) f(**d)
Which is valid syntax but is wrong. Then they might try changing that to:
f(**({u, v} if flag else {w, x}))
which is suddenly invalid syntax.
Not invalid syntax, but it's still wrong. You'll get a TypeError when trying to `**` unpack a set instead of a dict.
This is a very weird user experience. On that note, is this valid?
f(**({u, v}))
I would expect that to parse as regular old dict unpacking, and give a TypeError at runtime. -- Steven