Skip to content

PEP 646: Add section on grammar changes #2039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 18, 2021
Merged

PEP 646: Add section on grammar changes #2039

merged 9 commits into from
Aug 18, 2021

Conversation

mrahtz
Copy link
Contributor

@mrahtz mrahtz commented Jul 20, 2021

Copy link
Member

@JelleZijlstra JelleZijlstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is clear to me now!

This is just my opinion and I'm not going to be the one making decisions here, but to me change (1) (* in subscripts) feels stronger than change (2) (* in *args annotations). (1) is a fairly natural extension of the grammar that could be independently useful. (2) is a special case for one very specific situation, so it is more ad hoc.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm running out of time to review the second half of the diff, here's what I have for the first half.)

pep-0646.rst Outdated
Comment on lines 771 to 775
class TypeVarTuple:
def __init__(self, name):
self._unpacked = _UnpackedTypeVarTuple(name)
def __iter__(self):
yield self._unpacked
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe note that this is just a trick to make it print the right thing when its repr() is called. It is not helpful for static checking (static checkers don't call iter(), they inspect the AST), nor for dynamic checking (you'd have to recognize _UnpackedTypeVarTuple -- whose existence you hypothesize but never really explain).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I think dynamic checkers would be able to recognise _UnpackedTypeVarTuple. I've added what I imagine _UnpackedTypeVarTuple to look like (and removed the underscore, since in fact it's meant to be visible to things outside typing.py) - is this enough?

pep-0646.rst Outdated
Comment on lines 778 to 779
star-unpacking in other contexts, ``_UnpackedTypeVarTuple`` would always be received
by ``__class_getitem__`` as part of a tuple:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To describe the semantics of this grammar, we need neither _UnpackedTypeVarTuple nor __class_getitem__. Rather, we need to describe what the instance method __getitem__ will receive when the argument has various forms involving *.

Fortunately it seems to be that in all cases where the substitution is valid, x[..., *a, ...] produces the same as x[(..., *a, ...)], with the one edge case that x[*a] becomes x[(*a,)]. Of course if the dots involve a slice, the substitution is not valid, but we can then replace slices with calls to slice(...) to make things valid, for example x[1:2, *a] will become x[(slice(1, 2), *a)]. And then we can just defer to the existing semantics for *a and for x[y].

We probably will need to defend why we even allow x[1:2, *a], since we don't need it for variadic types. Our case is somewhat weak here (and we can state that we'd be fine if the SC were to strike this), but to me it seems that it's the simplest grammar change. After all that's also why we chose to allow e.g. x[*a, *b].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mm, great points. I've rewritten this section to explain these points (incorporating your wording, which I thought was very clear). What do you think?

pep-0646.rst Outdated
Comment on lines 801 to 802
idxs_to_select = (1, 2, 3)
array[0, *idxs_to_select, -1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it would, but PEP 637 was rejected because there weren't enough perceived benefits. And honestly the example doesn't make a lot of sense to me. (Is it equivalent to array[0, 1, 2, 3, -1]?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rewritten this section to make it clearer this is a side effect rather than something we necessarily endorse, and to clarify what this example is equivalent to - better?

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the rest. Not too much new stuff.

pep-0646.rst Outdated
>>> Ts = TypeVarTuple('Ts')
>>> def foo(*args: *Ts): pass # Equivalent to `*args: tuple(Ts)`
>>> foo.__annotations__
{'args': (UnpackedTs,)} # Where UnpackedTs is Ts._unpacked
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't define or explain UnpackedTs. How does it relate to _UnpackedTypeVarTuple? Is this literally what it will print?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have clarified this - better?

pep-0646.rst Outdated

star_annotation[expr_ty]: ':' a=star_expression { _PyAST_Tuple(_PyPegen_singleton_seq(p, a), Load, EXTRA) }

This accomplishes the desired outcome (making ``*args: *Ts`` not be a syntax error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you also need to explain the semantics when the thing after the star is not a TypeVarTuple. If I have

class X: ...
x = X()
def f(*args: *x): ...
print(f.__annotations__)

What will it print? What calls to methods of X are made?

Copy link
Contributor Author

@mrahtz mrahtz Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have tried to clarify this; better now?

Comment on lines +888 to +889
1. **Support change 1 but not change 2**. Starred expressions within indexes are
more important to us than the ability to annotation ``*args``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

@mrahtz
Copy link
Contributor Author

mrahtz commented Jul 26, 2021

Thanks for the detailed comments, Guido - these are very helpful!

@gvanrossum
Copy link
Member

I’m on vacation for two weeks, will be sluggish to review again.

@mrahtz
Copy link
Contributor Author

mrahtz commented Jul 27, 2021

Sure - enjoy!

@gvanrossum
Copy link
Member

Ugh, I lost track of this. Will review later today (after the Tensor Typing meeting).

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically LGTM -- I only have very small nits left!

Thanks for your patience.

pep-0646.rst Outdated
Comment on lines 756 to 757
(Note that ``from future import annotations`` does not help here, since no annotations
are involved.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This remark is a bit mysterious. Why would a reader make the mistake of thinking that would help? What might they think it helps for?

Also, it's __annotations__.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from __future__ import annotations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this based on one of your earlier comments:

The first grammar change enables use of star expressions in index operations (that is,
within square brackets), necessary to support star-unpacking of TypeVarTuples.

I think this should summarize the reason for this change here. The first Summary example shows it well:

class Array(Generic[DType, *Shape]):

That this is the very first example shows how fundamental this is. (And note that "from future import annotations" doesn't help in this position, since it is not technically an annotation.)

I thought you were gesturing towards the fact that with future annotations, new syntax is unnecessary for being able to do whatever manner of exotic things in x: Tensor[A, B, C, D] because of annotations not being evaluated at runtime - so a naive reader might think the same was true for everything else suggested in the PEP.

Anyway, I also thought it was a bit unclear in the end, so I'm happy to remove it.

pep-0646.rst Outdated
Comment on lines 890 to 891
for ``args``. In other words, ``*args: *foo`` is equivalent to
``*args: tuple(foo)``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add "at runtime" somewhere in this last sentence, to make it absolutely clear that this is not an equivalence for static type checkers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

pep-0646.rst Outdated
Comment on lines 920 to 921
>>> l = [1, 2, 3]
>>> def bar(*args: *l): pass # Equivalent to `*args: tuple(l)`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use a different variable name than l, which is easily confused with either 1 or I depending on the font.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

pep-0646.rst Outdated
@@ -867,6 +867,7 @@ After:

star_etc:
| '*' param_no_default param_maybe_default* [kwds]
# This is the new alternative. Other alternatives are unchanged.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With whole-line comments it's always a bit ambiguous where they point. Maybe just add # New to the end of the new alternative?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@mrahtz
Copy link
Contributor Author

mrahtz commented Aug 18, 2021

Thanks, Guido!

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! I'll merge this now, it's a very nice update.

Before we go back to the SC, let's make sure we check these boxes:

  • Have we motivated sufficiently why we prefer the *Ts notation over Unpack[Ts]? (I think it's much more readable.)
  • Have we collected enough endorsements from potential users? We should probably link to issues or messages (e.g. in the mailing list archives) in a brief section labeled "Endorsements" -- the current SC seems to like such things. (We should also be transparent about the non-endorsement from PyTorch.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants