On Sat, Apr 18, 2020 at 5:03 PM Steven D'Aprano <st...@pearwood.info> wrote:

> 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.
>

OK, I have to admit I had a bit of a failure of imagination. But there is
something about all these examples (plausibility?) that feels distinctly
different from your proposal. I can't quite put my finger on it. It might
just be that your proposal is new and I need to get used to it the way I'm
used to how the rest of Python works. But I'm not convinced of that.


>     f(a, b, c)
>
>     x = a, b, c
>     f(x)
>
> are very different things.


This imagined refactoring doesn't feel as plausible. A complete beginner
might think that they can do that, but a programmer who knows what tuples
are can reason that it doesn't make sense. On the other hand, an
intermediate programmer can be forgiven for thinking that they could
refactor out the {u, v} in the proposed syntax. Hypothetically, sets could
support the mapping protocol. Understanding that it's impossible requires
understanding the guts of the language better and that variable names
aren't stored with objects at runtime. Put differently, the proposal could
mislead readers into thinking that sets do actually know the names of the
values in them, without them trying to refactor out the 'set'.


> Here's another one:
>
>     import name
>
>     x = name
>     import x
>

This is stretching the idea of "text that looks like an expression". 'name'
looks like an expression in complete isolation, but even a beginner who has
just learned imports knows (at least at an intuitive, informal level) that
'name' is not an expression that is evaluated to determine what to import.
This is generally obvious from the fact that 'name' is usually not defined
beforehand. So even someone who just knows the basics of variables and
hasn't seen imports before might guess that the refactoring would fail
(with a NameError).

Another litmus test that it isn't like an expression is that `import
(name)` isn't valid.


>
> Here's a third:
>
>     del fe, fi, fo, fum
>
>     x = fe, fi, fo, fum
>     del x
>

This could be simplified to

del y

vs

x = y
del x

This actually is something that a beginner might think would work, and
several have probably tried. The workings of variables, references, names,
scope, and garbage collection are confusing at first. We shouldn't
introduce new features that are similarly confusing.


> Here's an example so obvious (and trivial) that I'm almost embarrassed
> to include it:
>
>     seq[index]
>
>     x = [index]
>     seqx
>

Again, '[index]' only looks like a list in complete isolation. If you try
to interpret it as a list while it's stuck to 'seq', the code doesn't even
begin to make sense. You can't just stick two expressions together, there
has to be an operator or something in between. And the refactoring isn't
plausible - it's obvious that seqx is a new variable and not the
concatenation of seq and x, otherwise it could equally be s|eqx or se|qx or
s|e|q|x.


> 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 :-(
>

Me too :-(


> Although in this case at least you get a syntax error when you try.
>

Therefore it doesn't satisfy my criteria.


> Here's an example with sequence unpacking:
>
>     a = [1, 2, *seq]
>
>     x = 2, *seq
>     a = [1, x]
>

The problem here isn't related to unpacking. This doesn't work for the same
reasons:

a = [1, 2, 3]

x = 2, 3
a = [1, x]

or just:

a = [1, 2]

x = 1, 2
a = [x]

Which shows that it's basically the same as the function call example at
the beginning. If you understand how lists and tuples work, you can
understand why the refactoring doesn't work.

Put differently, it's not just about refactoring out variables. The above
refactoring is equivalent to changing:

a = [1, 2, 3]

to

a = [1, (2, 3)]

And it's pretty obvious to a beginner that the two expressions are
different. It's much less obvious that these are different:

    f(**{u, v})

    f(**({u, v}))

Tuples in Python are weird. They're supposed to be defined by the comma,
but they're obviously not really, because commas have all sorts of other
meanings, and empty tuples don't need commas. They need parentheses
sometimes in cases that are hard to summarise. People think that
parentheses are what define the tuple and don't realise they need a
trailing comma for singletons. It's a mess, and again, we should avoid
creating things which are similarly confusing.


> Another example:
>
>     class MyClass(metaclass=MyMeta)
>
>     metaclass = MyMeta
>     class MyClass(metaclass)
>

No, this hasn't refactored `metaclass=MyMeta` out into a variable, you've
just moved it. The correct analogy would be:

M = metaclass=MyMeta
class MyClass(M)

which makes the same point, but still violates the rules because
`metaclass=MyMeta` isn't text that looks like an expression.


>
> That's just a special case of keyword notation itself:
>
>     func(x=expr)
>
>     x = expr
>     func(x)
>

Indeed it is just a special case, and it has the same problem.  x=expr is
not text that looks like an expression. As you say below, you cannot for
example put it into parentheses.


> 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
>

`def method...` is not an expression.


> > 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))
>

No you can't, so thankfully it raises a SyntaxError, unlike your proposal.


> > 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.
>

Yes I got confused and made a mistake here.

The fact that it doesn't raise a SyntaxError is still bad, for the same
reasons that I argued about in the beginning (whereas previously I thought
it was bad for new and different reasons). The point is that the syntax
looks and feels like it can be manipulated like an expression.
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/D5T7YPOI43JQWB7C4SAUHDAIWOTPFMU5/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to