Extrapolating PEP 634: The walrus was waiting for patmat all along

Pattern-matching is great. I think PEP 634 is on the right track, but it would be a waste to only use pattern-matching for choosing a branch in a match statement. Let’s look at Rust: if let [x, y] = my_array { ... } Rust "if let" constructs are an alternative to full-blown match statements that make it less verbose to match a single pattern. We have a similar problem. The syntax proposed by PEP 634 is pretty verbose for matching a single pattern: match my_list: case [x, y]: ... Two keywords and two indentation levels. We can do better: if [x, y] := my_list: ... Yes, your first intuition is right. This looks similar to the Rust version but would work completely differently. But hear me out. Let's look past my terrible example and focus on the idea. 1. The walrus operator was purposefully designed to create bindings and not to perform assignments to arbitrary lvalues. 2. Matching a pattern only introduces bindings as well, no assignments. 3. The current behavior of the walrus operator is equivalent to matching the right-side operand to an "irrefutable" capture pattern. Allowing the walrus operator to do pattern-matching would simply make the returned value conditional. If the pattern doesn't match, the walrus operator returns None. print(x := 42) # 42 print(1 := 42) # None The current PEG parser would backtrack when encountering the walrus operator to interpret the left-side as a pattern. Finally, more examples: # Buy every pizza on the menu sum( price for food in menu if ({"type": "pizza", "price": price} := food) ) # Download all images from document {url: download(url) for tag in html if (Img(src=url) := tag)} # Monitor service health while Response(status=200, json={"stats": stats}) := health_check(): print(stats) time.sleep(5) I'm convinced that making the walrus operator a pattern-matching operator would turn it into the perfect companion for PEP 634. What do you think? References: - PEP 634: https://www.python.org/dev/peps/pep-0634/ - Rust "if let": https://doc.rust-lang.org/book/ch06-03-if-let.html -- Valentin

If you look in PEP 622 you'll see that there was a rejected idea `if match ...` that's pretty similar. We nixed it because it just made the PEP larger. For 3.11 we can consider something like this. On Fri, Oct 23, 2020 at 4:12 PM Valentin Berlier <berlier.v@gmail.com> wrote:
Pattern-matching is great. I think PEP 634 is on the right track, but it would be a waste to only use pattern-matching for choosing a branch in a match statement.
Let’s look at Rust:
if let [x, y] = my_array { ... }
Rust "if let" constructs are an alternative to full-blown match statements that make it less verbose to match a single pattern.
We have a similar problem. The syntax proposed by PEP 634 is pretty verbose for matching a single pattern:
match my_list: case [x, y]: ...
Two keywords and two indentation levels. We can do better:
if [x, y] := my_list: ...
Yes, your first intuition is right. This looks similar to the Rust version but would work completely differently. But hear me out. Let's look past my terrible example and focus on the idea.
1. The walrus operator was purposefully designed to create bindings and not to perform assignments to arbitrary lvalues. 2. Matching a pattern only introduces bindings as well, no assignments. 3. The current behavior of the walrus operator is equivalent to matching the right-side operand to an "irrefutable" capture pattern.
Allowing the walrus operator to do pattern-matching would simply make the returned value conditional. If the pattern doesn't match, the walrus operator returns None.
print(x := 42) # 42 print(1 := 42) # None
The current PEG parser would backtrack when encountering the walrus operator to interpret the left-side as a pattern.
Finally, more examples:
# Buy every pizza on the menu sum( price for food in menu if ({"type": "pizza", "price": price} := food) )
# Download all images from document {url: download(url) for tag in html if (Img(src=url) := tag)}
# Monitor service health while Response(status=200, json={"stats": stats}) := health_check(): print(stats) time.sleep(5)
I'm convinced that making the walrus operator a pattern-matching operator would turn it into the perfect companion for PEP 634. What do you think?
References:
- PEP 634: https://www.python.org/dev/peps/pep-0634/ - Rust "if let": https://doc.rust-lang.org/book/ch06-03-if-let.html
-- Valentin _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@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/MJ7JHY... Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>

If you look in PEP 622 you'll see that there was a rejected idea `if match ...` that's pretty similar. We nixed it because it just made the PEP larger. For 3.11 we can consider something like this.
Of course I understand the PEP is already pretty big. But if it goes through and we start thinking about extending it with `if match` statements, my point is that it would be worth considering extending the walrus operator instead :)

On 2020-10-23 23:25, Valentin Berlier wrote:
Pattern-matching is great. I think PEP 634 is on the right track, but it would be a waste to only use pattern-matching for choosing a branch in a match statement.
Let’s look at Rust:
if let [x, y] = my_array { ... }
Rust "if let" constructs are an alternative to full-blown match statements that make it less verbose to match a single pattern.
We have a similar problem. The syntax proposed by PEP 634 is pretty verbose for matching a single pattern:
match my_list: case [x, y]: ...
Two keywords and two indentation levels. We can do better:
if [x, y] := my_list: ...
Yes, your first intuition is right. This looks similar to the Rust version but would work completely differently. But hear me out. Let's look past my terrible example and focus on the idea.
1. The walrus operator was purposefully designed to create bindings and not to perform assignments to arbitrary lvalues. 2. Matching a pattern only introduces bindings as well, no assignments. 3. The current behavior of the walrus operator is equivalent to matching the right-side operand to an "irrefutable" capture pattern.
Allowing the walrus operator to do pattern-matching would simply make the returned value conditional. If the pattern doesn't match, the walrus operator returns None.
print(x := 42) # 42 print(1 := 42) # None
Why should a failed match return None? That's not helpful if it matches but the value itself is None. [snip]

Why should a failed match return None? That's not helpful if it matches but the value itself is None.
The only pattern that would match `None` is this one: print(None := get_value()) # Always None Here, the walrus operator would always return `None`. Either because the function returned `None` or the function returned something else and the pattern didn't match. The behavior is consistent, this particular pattern is just not that useful. Pattern-matching shouldn't exempt you from checking for `None` with the `is` operator anyway: print(get_value() is None) # True or False The proposed semantics don't get in the way of idiomatic python. Most of the time you only care about the truthiness of the value returned by the walrus operator. The most common example would be with regular expressions: price_tag = "Price: $7" if match := re.match(r".*(\d+).*", price_tag): print(match[1]) By the way I'm hoping that with PEP 634 `Match` objects can become proper `collections.abc.Sequence` instances. This would allow regex destructuring: if [_, amount] := re.match(r".*(\d+).*", price_tag): print(amount) -- Valentin
participants (3)
-
Guido van Rossum
-
MRAB
-
Valentin Berlier