1) This is really close to Erlang/Elixir pattern matching and will make fail-early code much easier to write and easier to reason about.
2) match/case means double indentation, which I see they reasoned about later in the "Rejected ideas". Might have a negative impact on readability.
3) Match is an already used word (as acknowledged by the authors), but I think this could have been a good case for actually using hard syntax. For me, perhaps because I'm used to it, Elixir's "{a, b, c} = {:hello, "world", 42}" just makes sense.
4) I hope there won't be a big flame-war debacle like with :=
5) And then finally there is the question of: "It's cool, but do we really need it? And will it increase the surprise factor?" And here I'm not sure. And again, this was the concern with the new assignment expression. The assignment expression is legitimately useful in some use cases (no more silly while True), but it might reduce the learnability of Python. Python is often used as an introductory programming language, so the impact would be that curricula need to be adjust or beginner programmers will encounter some surprising code along the road.
I can't say this is a good or bad proposal, I want to see what other opinions are out there, and what kind of projects out there in the world would really benefit from syntax like this.
> While matching against each case clause, a name may be bound at most once, having two name patterns with coinciding names is an error.
match data:
case [x, x]: # Error!
...
Which is a bit of a shame. This comes in handy in Elixir to say "the same value must appear at these places in the collection". I.e. for a Python tuple pattern `(x, y, x)`, `(3, 4, 5)` would not match but `(3, 4, 3)` would.Overall, though, I think this will be a great addition to Python. Pattern matching is generally a huge boost for expressiveness and readability, in my opinion.
match data:
case [x1, x2] if x1 == x2:
...
I agree on this being a fantastic addition to the language. I've sorely missed not having pattern matching in Python after using Rust.What if `(x, y, x)` is matched against some `(a, b, c)` where `a == c`, but not `a is c`? If yes, and you mutate `x` in the body, does it mutate `a` or does it mutate `c`?
I think at least making it an error now can leave it open to defining it later.
You may argue that I am simply not versed enough in pattern matching. “You should study harder.” I would argue that simplicity is worth striving for.
I hope this PEP never moves beyond draft.
It’s also shocking that most people here seem to be tacitly supporting this, or happy about it. Yes, it’s cool. Yes, it might simplify a few cases. But it will also give birth to codebases that you can’t read in about, say, 5 years. And then you’ll have a bright line between people in the camp of “This is perfectly readable; it does so and so” and the rest of us regular humans that just want to build reliable systems.
And oh yes, it becomes impossible to backport to older python versions. Lovely.
Firstly, even basic list destructuring with destructuring-bind is an improvement over a soup of car/cadar/caddr/.
Suppose we are in a compiler and would like to look for expressions of he pattern:
(not (and (not e0) (not e1) (not e2) ...))
in order to apply DeMorgan's and rewrite them to (or e0 e1 ...)
I would rather have a nice pattern matching case like this: (match-case expr
...
((not (and @(zeromore not @term)))
`(or ,term)) ;; rewrite done!
...)
than: (if (and (consp expr)
(eq (car expr) 'not))
(consp (cdr expr))
(consp (cadr (expr)))
(null (cddr expr))
(eq (caadr (expr)) 'and)
(eq (cadr expr)
... ad nauseum)
Even if a fail-safe version of destructuring-bind is used to validate and get the basic shape, it's still tedious: (destructuring-case expr
...
((a (b c))
(if (and (eq a 'and)
(eq b 'not))
... now check that c is a list of nothing but (not x) forms
)))
I don't have a pattern matcher in TXR Lisp. That is such a problem that it's holding up compiler work! Because having to write grotty code just to recognize patterns and pull out pieces is demotivating. It's not just demotivating as in "I don't feel like doing the gruntwork", but demotivating as in, "I don't want to saddle my project with the technical debt caused by cranking out that kind of code", which will have to be rewritten into pattern matching later.Seems like it doesn’t create instances when you’re doing
Node(children=[Leaf(value="("), Node(), Leaf(value=")")])
instead:1. Node means "is instance of Node".
2. Everything in between () is "has an attribute with value".
3. List means "the attribute should be treated as a tuple of".. etc..
Very confusing, this definitely needs another syntax, because both newcomers and experienced devs will be prone to read it as plain `==`, since that's how enums and primitives will be working.
This syntax goes against Zen: It’s implicit -- when using match case expressions don't mean what they regularly mean. It’s complicated -- basically it’s another language (like regex) which is injected into Python.
I’m a big believer in this feature, it just needs some other syntax. Using {} instead of () makes it a lot better. Now no way to confuse it with simple equality.
match node:
case Node{children=[{Leaf{value="(", Node{}, ...}}I understand the worry that newcomers might struggle, but I don't think it's going to be the case: newcomers regularly learn the languages listed above without stumbling across that problem. And if Python did choose a syntax like the one you're proposing, it'd also be the odd one out among dozens of mainstream languages including this feature, which I think would be even more confusing!
I can't wait for this feature!
The entire point of structural pattern matching is that structuring and destructuring look the same.
> This syntax goes against Zen: It’s implicit -- when using match case expressions don't mean what they regularly mean.
There's nothing implicit to it. The match/case tells you that you're in a pattern-matching context.
> I’m a big believer in this feature, it just needs some other syntax. Using {} instead of () makes it a lot better. Now no way to confuse it with simple equality.
Makes it even better by… looking like set literals and losing the clear relationship between construction and deconstruction?
I used to feel like I could define __getattr__ or __setattr__ and understand the implications, but that's getting increasingly terrifying.
Behavior doesn't change at all.
match shape:
case Point(x, y):
...
case Rectangle(x, y, _, _):
...
print(x, y) # This works match node:
on (time _ 1 @ 0, foo _ 2, bar _ 3, baz _ any, status _ 1 @ -1):
do_something()
rematch do_something_2(foo)
'_' indicating span, and '@' indicating position of the first item. Both can be omitted.> "We propose the match syntax to be a statement, not an expression. Although in many languages it is an expression, being a statement better suits the general logic of Python syntax."
no matching in lambdas unless those get overhauled too :(
instead, let's get excited for a whole bunch of this:
match x
case A:
result = 'foo'
case B:
result = 'bar'
(i guess i'm a little salty...) match shape:
case Square(l):
area = l * l
case Rectangle(l, w):
area = l * w
case Circle(r):
area = (PI * r) ** 2
when I want to just write this: area = match shape:
case Square(l):
l * l
case Rectangle(l, w):
l * w
case Circle(r):
(PI * r) ** 2
I'm almost guaranteed to forget (or mistype) the `area = ` at least once in any match clause of length.> no matching in lambdas unless those get overhauled too :(
Hate it or like it, but it's congruent with the rest of Python design.
Guido has been hostile with introducing too much FP in Python.
While I find it frustrating from time to time, espacially when I come back from another language to Python, on the long run, I must say I understand.
I don't like to work with FP languages, because unless your team is really good, the code ends up hard to read and debug. It's possible to make very beautiful code in FP, but it makes it so easy to create huge chains of abstract things.
And devs are not reasonable creatures. Give them a gun with 6 bullets, they'll shoot them all, and throw some stones once it's empty.
To me, the average dev is not responsible enought with code quality, and should not be trusted. I'm all for tooling enforcing as much as you can. Linters and formatters help a lot. But sometimes, language design limiting them is a god send.
I've seen the result with Python: you can put a math teacher, a biologist and frontend dev on their first backend project, and they will make something I can stand reading.
x if cond else y
IMO match-expressions have basically the same benefits/trade-offs.That single comment from Guido 15 years ago, which was largely backtracked on—map and filter remain in the core library and reduce moved to a library in stdlib, lambda remains—is not really an example of something accurately described as “increasingly” now. In fact, given the backtracking, it's arguably decreasingly true from the high point of that post.
> "[...] making it an expression would be inconsistent with other syntactic choices in Python. All decision making logic is expressed almost exclusively in statements, so we decided to not deviate from this."
so it's just keeping in line with Python's general imperativess.
> FP constructs are increasingly discouraged in Python [...] Not that this is necessarily a bad thing, per sé
i, on the other hand, do think it's a bad thing :) but what can you do...
Abstractly, I'd rather have match expressions, too, but this does fit the rest of Python better.
If you're curious about why this is so useful, and reading the (quite dry) PEP isn't your thing, I would heartily recommend playing with Elixir for a few hours. Pattern matching is a core feature of the language, you won't be able to avoid using it. The language is more Ruby-like than Python-like, but Python programmers should still have an easy time grokking it. When I was getting started I used Exercism [1] to have some simple tasks to solve.
That's interesting. Python 3.6 had "async" and "await" as soft keywords, before they became reserved keywords in 3.7 [1]. However, soft keywords have just been added to Python more generally [2], so aren't such a special case anymore.
[1] https://www.python.org/dev/peps/pep-0530/
[2] https://github.com/python/cpython/pull/20370, https://github.com/python/cpython/pull/20370/files
a * b == "a times b"
a * * b == "a to the power of b"
f(* a) == "call f by flattening the sequence a into args of f"
f(* * a) == "call f by flattening the map a into key value args for f"
[* a] == "match a sequence with 0 or more elements, call them a"
Am I missing something? I know these all occur in different contexts, still the general rule seems to be "* either means something multiplication-y, or means 'having something to do with a sequence' -- depends on the context". It's getting to be a bit much, no?
Note: HN is making me put spaces between * to avoid interpretation as italics.
>>> [x, *other] = range(10)
>>> x
0
>>> other
[1, 2, 3, 4, 5, 6, 7, 8, 9]
So this instance of the syntax is not that novel. If it's a mistake, it's too late to fix it.A unary asterisk before a name means the name represents a sequence of comma-separated items. If it's a name you're assigning to (LHS) that means packing the sequence into the name, if it's a name you're reading from (RHS) that means unpacking a sequence out of the name.
def f(*args, **kwargs):
doing the opposite of f(*a)
f(**a)Yes, the form used in function declaration (the inverse of the form in function calls) which is pretty much exactly the same as the new use, “collect a sequence of things specified individually into a list with the given name”.
Unpacking was huge 10 years ago, but nowaday even JS has object destructuring, so python was lagging behind.
It feels like they really spent a lot of time in designing this: Python has clearly not be made for that, and they have to balance legacy design with the new feature.
I think the matching is a success in that regard, and the __match__ method is a great idea. The guards will be handy, while the '_' convention is finally something official. And thanks god for not doing the whole async/await debacle again. Breaking people's code is bad.
On the other hand, I understand the need for @sealed, but this is the kind of thing that shows that Python was not designed with type hints from the begining. Haskell devs must have a laught right now.
We can thank Guido for the PEG parser in 3.9 whichs allows him to co-author this as well.
I expect some ajustments to be made, because we will discover edge cases and performance issues, for sure. Maybe they'll change their mind on generalized unpacking: I do wish to be able to use that for dicts without having to create a whole block.
But all in all, I believe it will be the killer feature of 3.10, and while I didn't see the need to move from 3.7, walrus or not, 3.10 will be my next target for upgrade.
https://github.com/santinic/pampy
Even match on Point(x, y, _)
I personally prefer the pampy internals, but quite like the context manager usage from switchlang. I don't even know which bikeshed I want to paint, let alone the colour.
My biggest concern is the class matching syntax. I feel like that would be much better deferred to a lambda style function or similar. The syntax matches instantiating a new class instance exactly, which seems like it could cause a lot of problems for tools that read and manipulate syntax.
When I discovered there wasn't one, I was really annoyed and went digging for an explanation. The one I found was a suggestion if you are reaching for a switch statement, you're (probably) doing something wrong. This is not to impugn anyone else's approach or style and I will freely admit there are times when all you need is a switch and if/ else if/ else gets ugly, but most times I find the replacement for switch is not that but a dictionary holding a callable or similar. I recently showed this approach to a peer in PR and watching it click for her was awesome. She ripped out most of what she'd done and replaced some of our more ponderous permission checking with a dictionary of functions to apply.
I'd say the other thing I do when I wish I had a switch statement is realize I am writing code that is halfway to doing things The Right Way and refactor the block into smaller functions.
I came up with the following, and that definitely doesn't convince me.
def call_a():
pass
def call_b():
pass
def default_func():
pass
myswitch = {
"a": call_a
"b": call_b
}
myfunc = myswitch.get(x, default_func)
myfunc()
vs if x == "a":
pass
elif x == "b":
pass
else:
passNot actually a heavy Python user, but even though most languages I use regularly are more switch/case-heavy, I've never quite grasped why there's two largely interchangeable ways to do the one thing.
In higher level langues with strong, static types, the switch statement can indicate to the compiler you want to match on the type of the variable; it can do analysis then to make sure your pattern match is exhaustive.
Making an example is simpler than formulating a defition :-):
case instance
when MyClass...
when MyOtherClass...
...
end
which would otherwise be: if instance.is_a?(MyClass)
...
elif instance.is_a?(MyOtherClass)
...
...
end
if you consider that this has support for many other expressions (regular expressions, ranges...), you'll see a very coincise construct for the purpose.To put it another way: when you're branching on the value of a single variable by comparing it to an enumerated set of values, the obvious way to do so is with switch/case.
Clarity of intent, much as comprehensions provide over imperative loops (even though switch is still imperative in most langauges). Though whether it's enough benefit to be warranted is another question; I think basic switch is not clearly compelling, though adding even basic smarter matching (like Ruby has long had, for instance) makes it moreso.
But Python's if/elif/else syntax is already clean enough that there's just not much clutter to remove. And C-style semantics on switch statements wouldn't be acceptable. And Python is a very dynamic language. So, in the end, you would end up with something that's generally the same line count and the same semantics as an equivalent if-statement, meaning it's would be more like semantic Splenda than semantic sugar.
> A quick poll during my keynote presentation at PyCon 2007 shows this proposal has no popular support. I therefore reject it.
case x := _
to match and assign to x. "_" would always be the matcher. You always have access to the original item that the match was made on, so pulling out the matched items is often not needed, AFAICT. This would be explicit, and not too surprising. Then the whole dotted names part can be dropped - it works like normal Python at that point.The PEP already suggests this for capturing parts of the match, why not just use it for all saved matches? It's more verbose, but consistent, with fewer caveats, and not always needed.
Disclaimer: My languages don't happen to include one with good pattern matching, so I'm not strongly familiar with it.
One subtle thing which I noticed is the distinction between class patterns and name patterns (bindings). In particular, it is possibly confusing that the code `case Point:` matches anything and binds it to the value Point, whereas `case Point():` checks if the thing is an instance of Point and doesn’t bind anything.
Linters could help. You're shadowing `Point`, and because `case Point:` matches any value, if there's another case after that then something is wrong. But you can't always rely on linters.
With that said, this has been suggested and discussed many times in the past. I imagine this will be as controversial as the walrus operator[1] was.
Is this will create a second Node instance and compare it to node?
If so, is it not less efficient performance wise than it's "counterpart" isinstance() + properties comparison?
If this method is less efficient, it could be confusing, specially for newcomer.
Am I missing something.
[0] https://www.python.org/dev/peps/pep-0622/#the-match-protocol [1] https://www.python.org/dev/peps/pep-0622/#default-object-mat...
all(hasattr(node, attr) for attr in attrs))
than the ==
[1] Yes I know coconut-lang is a thing, but I didn't want to introduce something that looks a lot like Python but isn't in our codebase
>case True: ... case 1: ...
From practical perspective this is great, but I can imagine many cases where one could want to differentiate between those.
On one hand the number usually can be nested inside a structure and matching on == is more flexible. On the other hand matching on 'is' is still letting users relax this behaviour and allows matching on type of primitives as well.
Python 3 eliminated the ability to match on tuples in function heads.
For loops can match on tuples, but lambda headers can’t.
As an Erlang fan, it’s maddening.