Parameter tuple unpacking in the age of positional-only arguments

On Tue, 26 Oct 2021, Eric V. Smith wrote:
Having recently heard a friend say "the removal of tuple parameter unpacking was one thing that Python 3 got wrong", I read this and PEP 3113 with interest. It seems like another approach would be to treat tuple-unpacking parameters as positional-only, now that this is a thing, or perhaps require that they are explicitly positional-only via in PEP 570: def move((x, y), /): ... # could be valid? def move((x, y)): ... # could remain invalid? Is it worth revisiting parameter tuple-unpacking in the age of positional-only arguments? Or is this still a no-go from the perspective of introspection, because it violates "There are no hidden details as to what a function's call signature is."? (This may be a very short-lived thread.) Erik -- Erik Demaine | edemaine@mit.edu | http://erikdemaine.org/

From a "hobbyist / programming aesthete" perspective, I think this is a great idea. I think pattern matching in ML-descended languages is wonderfully elegant. Packing and unpacking tuples is already a common syntactic idiom in Python, and now that we have Structural Pattern Matching (SPM), it's going to encourage people to embrace pattern matching even more as an element of Pythonic style. So this is a good fit for the Python of 2022 (what a futuristic-looking year!) and beyond. I believe that the "no hidden details" problem can be resolved by a combination of thoughtful help-string generation and a suitable syntax for type hints. --- From a "rank-and-file programmer" perspective, I don't think I want this in Python. The functional languages that depend heavily on pattern matching really embrace it as a core concept at the syntax level. I think I encounter a use case like this maybe once a week at most, and each time I think "heh, that'd be nice" and move on with my life. I want Python code (really all code) to be as visually easy to parse as possible, so I can read it quickly and have the damn syntax get out of my way. I think Python syntax right now has a nice balance between concise elegance and comfortably boring predictability. I wouldn't want to swing the pendulum too far toward the former, because it would make my day job harder. The benefits would be relatively minor or near-zero for ~90% of users, would be moderate at best for the 9% of users who do a lot of tuple-destructuring stuff for whatever reason, and would be an amazing victory for the 1% of users who really freakin' love unpacking and already overuse it. The cost would be a fixed and probably irreversible increase in syntactic complexity, burdening not only future code readers, future students, and the future teachers of those future students (!!), but also developers of language introspection and static analysis tools. There is also the issue of playing nice with type hints. It's not just a matter of hidden details; how do you type-hint a function like `move((x, y))` without making a mess of symbols and nesting? --- Might as well have some fun with the idea! Here are a couple of extensions I've thought of over the last few weeks. 1) As-patterns One possibly interesting/useful extension is some ML-like "as-pattern" syntax to obtain the full argument as well as the destructured values: def move(point :: (x: float, y: float), /) -> tuple[float, float]: print(point) return x + 1, y + 1 def move((x: float, y: float) as point, /) -> tuple[float, float]: print(point) return x + 1, y + 1 def move(point from (x: float, y: float), /) -> tuple[float, float]: print(point) return x + 1, y + 1 def move(point with (x: float, y: float), /) -> tuple[float, float]: print(point) return x + 1, y + 1 2) Dict unpacking. BTW I think this should also be part of SPM syntax. def move({"x": x, "y": y}, /) -> tuple[float, float]: return x + 1, y + 1 def move({"x": x, "y": y}, /) -> tuple[float, float]: return x + 1, y + 1 move({"x": -1.5, "y": 1.5}) 3) Full-scale match/case SPM syntax. def move(Point(x=x, y=y), /) -> tuple[float, float]: return x + 1, y + 1 move(Point(1.5, -1.5)) 4) Any/all of the above on the left hand side of assignment statements. weird = ( 5, { "points": [Point(1,2), Point(3,4), Point(5,6)], "other": "this will be discarded" } ) n, {"points": [Point(x, y), *other_points] as all_points} = weird

At least it would make lambda parameters a little less unbearable. With `lambda a,b,/: b if b > 2 else a` instead of `lambda x: x[1] if x[1] > 2 else x[0]` I could live with. - Anselm

From a "hobbyist / programming aesthete" perspective, I think this is a great idea. I think pattern matching in ML-descended languages is wonderfully elegant. Packing and unpacking tuples is already a common syntactic idiom in Python, and now that we have Structural Pattern Matching (SPM), it's going to encourage people to embrace pattern matching even more as an element of Pythonic style. So this is a good fit for the Python of 2022 (what a futuristic-looking year!) and beyond. I believe that the "no hidden details" problem can be resolved by a combination of thoughtful help-string generation and a suitable syntax for type hints. --- From a "rank-and-file programmer" perspective, I don't think I want this in Python. The functional languages that depend heavily on pattern matching really embrace it as a core concept at the syntax level. I think I encounter a use case like this maybe once a week at most, and each time I think "heh, that'd be nice" and move on with my life. I want Python code (really all code) to be as visually easy to parse as possible, so I can read it quickly and have the damn syntax get out of my way. I think Python syntax right now has a nice balance between concise elegance and comfortably boring predictability. I wouldn't want to swing the pendulum too far toward the former, because it would make my day job harder. The benefits would be relatively minor or near-zero for ~90% of users, would be moderate at best for the 9% of users who do a lot of tuple-destructuring stuff for whatever reason, and would be an amazing victory for the 1% of users who really freakin' love unpacking and already overuse it. The cost would be a fixed and probably irreversible increase in syntactic complexity, burdening not only future code readers, future students, and the future teachers of those future students (!!), but also developers of language introspection and static analysis tools. There is also the issue of playing nice with type hints. It's not just a matter of hidden details; how do you type-hint a function like `move((x, y))` without making a mess of symbols and nesting? --- Might as well have some fun with the idea! Here are a couple of extensions I've thought of over the last few weeks. 1) As-patterns One possibly interesting/useful extension is some ML-like "as-pattern" syntax to obtain the full argument as well as the destructured values: def move(point :: (x: float, y: float), /) -> tuple[float, float]: print(point) return x + 1, y + 1 def move((x: float, y: float) as point, /) -> tuple[float, float]: print(point) return x + 1, y + 1 def move(point from (x: float, y: float), /) -> tuple[float, float]: print(point) return x + 1, y + 1 def move(point with (x: float, y: float), /) -> tuple[float, float]: print(point) return x + 1, y + 1 2) Dict unpacking. BTW I think this should also be part of SPM syntax. def move({"x": x, "y": y}, /) -> tuple[float, float]: return x + 1, y + 1 def move({"x": x, "y": y}, /) -> tuple[float, float]: return x + 1, y + 1 move({"x": -1.5, "y": 1.5}) 3) Full-scale match/case SPM syntax. def move(Point(x=x, y=y), /) -> tuple[float, float]: return x + 1, y + 1 move(Point(1.5, -1.5)) 4) Any/all of the above on the left hand side of assignment statements. weird = ( 5, { "points": [Point(1,2), Point(3,4), Point(5,6)], "other": "this will be discarded" } ) n, {"points": [Point(x, y), *other_points] as all_points} = weird

At least it would make lambda parameters a little less unbearable. With `lambda a,b,/: b if b > 2 else a` instead of `lambda x: x[1] if x[1] > 2 else x[0]` I could live with. - Anselm
participants (3)
-
Anselm Kiefner
-
Erik Demaine
-
Greg Werbin