8 ms·
PEP 622 – Structural Pattern Matching
- Spiritus 6y agoNote that this just a draft/proposal. And there's heavy activity on the mailing list. 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. [1] https://www.python.org/dev/peps/pep-0572/ https://www.python.org/dev/peps/pep-0572/
- uryga 6y agoglad to see this! though it's a shame that the proposed `match/case` is a statement, not an expression: > "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...)
- yowlingcat 6y agoI wonder if this is intentional. FP constructs are increasingly discouraged in Python, taking reduce [1] as an example. Not that this is necessarily a bad thing, per sé -- I think it does simplify the language. But it's an opinionated decision that will discourage FP enthusiasm from finding a home in Python. [1] http://lambda-the-ultimate.org/node/587 http://lambda-the-ultimate.org/node/587
- mdrachuk 6y agoBDFL made good points though.
- uryga 6y agothe Syntax section has a bit more: > "[...] 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...
- adamnew123456 6y ago> i, on the other hand, do think it's a bad thing :) but what can you do... Hy[1] I guess? It looks pretty lively although I'm not sure how seriously people take it these days. [1]: https://github.com/hylang/hy https://github.com/hylang/hy
- pjmlp 6y agoWith a good mix of comprehensions, itertools, functools and lambda, there is enough stuff to get creative with FP in Python anyway.
- andybak 6y agoYeah but constantly jumping between idioms means this is stuff I have to usually look up instead of just intuiting.
- uryga 6y agobtw, my favorite FP-hack: let-in expressions with lambdas OR list comprehensions: let x = foo in expr can be [expr for x in (foo,)][0] (kinda reads like a Haskell `where` clause!) or: (lambda x = foo: expr)() useful in a pre-walrus-operator REPL :)
- heavenlyblue 6y agoIf you want to program in Haskell, why don’t you get a job programming in Haskell rather than inventing something so ugly? You would not get through code review with that in any shop that has discipline.
- uryga 6y ago> You would not get through code review with that in any shop that has discipline. ... and i'd never think to try! that's why i mentioned using it in a REPL and called it a hack :) it's just fun. i played around with bytecode-rewriting too, doesn't mean i'd use it in production. PS. for a less extreme version, the 1-elem-tuple trick is useful if you need an intermediate variable in a listcomp: [ y+y for x in my_list for y in (foo(x),) ] but it's (obviously) ugly & only useful if you're in a REPL and don't want to rewrite the whole thing.
- goto11 6y agoNot sure about "increasingly" - note the comment you link to is 15 years old! It seems more like a recognition that map/filter/reduce never was idiomatic Python and there are better ways to do the same. Python have never tried to be a functional language.
- dragonwriter 6y ago> I wonder if this is intentional. FP constructs are increasingly discouraged in Python, taking reduce [1] as an example. 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.
- BiteCode_dev 6y ago> glad to see this! though it's a shame that the proposed `match/case` is a statement, not an expression: > 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.
- uryga 6y agowhat do you think about if-expressions? x if cond else y IMO match-expressions have basically the same benefits/trade-offs.
- BiteCode_dev 6y agoMatch are usually much more complex than a single ternary condition. Allow to inline them and you will see monsters in the wild. But I do wish they would have not set aside generalized unpacking.
- uryga 6y ago> Allow to inline them and you will see monsters in the wild. idk... you can write a huge ugly if-elif-else like this: a if foo else b if bar else c if quux else d but most people just don't.
- deleted 6y ago
- rattray 6y agoYeah, came here to say the same thing. Disappointing to have to write this: 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.
- whalesalad 6y agoThat works almost exactly in Ruby. I really wish Python and Ruby would have a child.
- rattray 6y agoFunny. Having worked extensively with Ruby and less so with Python, I think what I really want is expression-oriented Python. Ideally as a next version of Python. But I think Julia and Nim come to mind as related to these concepts.
- dragonwriter 6y agoJust as named functions replace multiline lambdas, I expect that named matcher functions (functions that consist entirely of a match where each arm is a return) will replace match expressions in python. Abstractly, I'd rather have match expressions, too, but this does fit the rest of Python better.
- uryga 6y agoit's a shame :( and honestly, i think the whole "if it needs statements, it should be a named function anyway" argument is misguided (and maybe kind of a post-hoc justification of the limitations of Python lambdas). introducing a named function for something that's only going to be used in one place messes up the order (you have to keep the function in your head until it gets used) thus often making the code harder to read.
- mkl 6y ago"The match and case keywords are proposed to be soft keywords, so that they are recognized as keywords at the beginning of a match statement or case block respectively, but are allowed to be used in other places as variable or argument names." 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/ https://www.python.org/dev/peps/pep-0530/ [2] https://github.com/python/cpython/pull/20370 https://github.com/python/cpython/pull/20370, https://github.com/python/cpython/pull/20370/files https://github.com/python/cpython/pull/20370/files
- justusw 6y agoVery interesting. This PEP is still in draft state, but I am interested to see how the community will react. For me, I have a few thoughts: 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.
- arusahni 6y agoWhat's interesting here is this is gated on the new parser from PEP-617 [1]. Based on mailing list discussions [2], and the PEP, the use of `match` will be context-sensitive, so it shouldn't be as disruptive of an introduction as `async`. 1: https://www.python.org/dev/peps/pep-617/ https://www.python.org/dev/peps/pep-617/ 2: https://lwn.net/Articles/816922/ https://lwn.net/Articles/816922/
- herman_toothrot 6y agomissed a zero: https://www.python.org/dev/peps/pep-0617/ https://www.python.org/dev/peps/pep-0617/
- wool_gather 6y agoOne difference I noticed from Elixir was 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.
- blondin 6y agofinally our own switch statement and a modern and powerful one at that. we needed this a long time ago!
- Narann 6y ago> case Node(children=[LParen(), RParen()]): 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.
- jackric 6y agoIt reads like a Node instance construction, but it's actually syntactic sugar for: isinstance(node, Node) and node.children == [LParen(), RParen()]
- theelous3 6y agoIs it not more of an all(hasattr(node, attr) for attr in attrs)) than the ==
- chrisshroba 6y agoIt's not quite syntactic sugar, but you're right that (probably) no new object would be created. Based on the "The __match__() Protocol" section [0] of the Pep, matching will call into `Node`'s `__match__` method with `node` as the arg. If Node doesn't have any custom logic here, it'll use the default __match__ implementation [1], which checks `isinstance(node, Node)` like you said, then returns `node` for the python interpreter to check that `node.children` a) is a sequence, b) has two elements, c) has its first element matching `LParen`, using `LParen`'s `__match__` method, and d) has its second element matching `RParen`, using `RParen`'s `__match__` method. If none of these `__match__` methods are overriden, then it does basically function as the parent poster said (though I think it would work even if node.children were some other sequence type (e.g. tuple) containing `LParen()`, `RParen()`). [0] https://www.python.org/dev/peps/pep-0622/#the-match-protocol https://www.python.org/dev/peps/pep-0622/#the-match-protocol [1] https://www.python.org/dev/peps/pep-0622/#default-object-match https://www.python.org/dev/peps/pep-0622/#default-object-mat...
- hnlmorg 6y agoCan someone explain to me the history behind Python's aversion to switch statements? I get Python is opinionated and I'm not trying to start a language war, it was just never clear to me why the `if ... elif` pattern was the preferred idiom.
- lucideer 6y agoTaking this question from the opposite angle, what benefits do switch statements give us over if...elif? Not 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.
- krapht 6y agoIn lower level languages, when you use switch, you make it easier for the compiler to generate a jump table. 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.
- lucideer 6y agoThese are good examples. Especially for a lower level languages this makes a lot of sense. For higher level languages, I would imagine typeguards would provide some of the desired functionality here, while being much more lightweight than a full alternative conditional syntax.
- nsajko 6y agoHaving both switch and if/elif allows writing code with less redundancy in it (unnecessary keywords and variables can be elided by choosing one selection construct over the other).
- lucideer 6y agoWhen you say "less redundancy", do you just mean terser code? Or what exactly? I don't see any DRY trade-offs, unless you're talking on a really micro character-count / code-golf level.
- mdrachuk 6y agoSo it’s actually a smart switch statement. 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{}, ...}}
- BiteCode_dev 6y agoI prefer the PEP syntax: it looks like the instanciation of the object I'm trying to match, so it makes sense to me.
- kevinmgranger 6y agoThat's their entire point: you're _not_ instantiating the object you're trying to match.
- BiteCode_dev 6y agoI know. It doesn't bother me since I see that match/case is a whole different context, I know I'm matching, and have no reason to instanciate anything here. But since Foo(a=1) looks like instanciation, it's obvious to me what it's supposed to match. I think that's one of the things that motivated to: - use new keywords, but existing ones like "as", nor symbols; - force 2 levels of indentation despite the cost of it. This makes it very clear, with little effort, that the context is completly different. It will make scanning the code easy, and avoid confusion.
- hombre_fatal 6y ago
- deleted 6y ago[deleted]
- lincolnq 6y agoThis is very exciting! 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.
- duckerude 6y agoYeah, that seems like it's going to cause trouble, because you can make a mistake without noticing. If you write `case Point:` when you mean `case Point():` you won't get an exception or a missing name, it'll just look like it thinks all objects are Points. 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.
- deleted 6y ago[deleted]
- sitkack 6y agoIf you like pattern matching and Python, I recommend you check out http://coconut-lang.org/ http://coconut-lang.org/ which compiles to Python.
- deleted 6y ago[deleted]
- csantini 6y agoIf you want it today, Pampy does most of it: https://github.com/santinic/pampy https://github.com/santinic/pampy Even match on Point(x, y, _)
- cobby 6y agoHow did they implement Point(x, y, _) matching? PEP proposes a special protocol [1] based on __match__ classmethod. Do they do something similar? [1] https://www.python.org/dev/peps/pep-0622/#runtime https://www.python.org/dev/peps/pep-0622/#runtime
- carapace 6y agoWow, that's way cooler than the PEP! "You can write a Lisp calculator in 5 lines" Oh my!
- JNRowe 6y agoThere is also switchlang¹ which isn't quite the same thing, but provides some of the functionality of PEP-622 and pampy. I believe it is notable for including a nice descriptive README, and also having a small/simple implementation. 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. 1. https://github.com/mikeckennedy/python-switch https://github.com/mikeckennedy/python-switch
- BiteCode_dev 6y agoSo far I like it. 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.
- jjice 6y agoRust was my first experience with pattern matching, and I really learned to love it there. Seeing it come to Python will be great as well. I'm glad this is on the docket, and I can't wait to see how this draft evolves.
- vslira 6y agoThis is great. I'm currently trying to rewrite a heavily object-oriented library into a more functional one (the rewrite is necessary because of licensing issues, and functional because the original code is a clusterf*ck of mutation) and despite the whole company working on top of Python, I was seriously considering implementing it in SML[1] specifically due to pattern matching making the underlying algorithm of the main data structure incredibly easier to reason about and implement. [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
- creativecupcak3 6y agoVery excited about this! Looking forward to using these super switches when they are released.
- MattConfluence 6y agoLately, Elixir has dethroned Python as the language that I get the most joy from using. Pattern matching in one of the big reasons. Great to see that Python core contributors (including Guido!) wants to see this feature in Python as well! Hopefully it will be well integrated and not feel like a tacked on feature. 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. [1] https://exercism.io/tracks/elixir https://exercism.io/tracks/elixir
- deleted 6y ago[deleted]
- aparmentier 6y agoInteresting proposal, but I'm cringing at yet another overload for the * symbol. 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.
- duckerude 6y agoIt's already used that way for unpacking, e.g. >>> [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.
- heavenlyblue 6y agoYou can also do [a, * b] when constructing a list so that b would be injected into that list after a - makes total sense.
- strbean 6y agoYou're missing def f(*args, **kwargs): doing the opposite of f(*a) f(**a)
- dragonwriter 6y ago> Am I missing something? 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”.
- 6y ago
- xiaodai 6y agoThis is trying to achieve a poor-human's multiple-dispatch with match-case syntax
- sicromoft 6y agoThis PEP is burying the lede -- it introduces a `@sealed` decorator, which gives python ADTs: https://www.python.org/dev/peps/pep-0622/#sealed-classes-as-adts https://www.python.org/dev/peps/pep-0622/#sealed-classes-as-...
- tayo42 6y agoI guess then python can have either and maybe? That would be real nice. working with exceptions sucks...
- whalesalad 6y agoThis is the first PEP I’m really excited about. I hope the design is given more careful consideration, though, because destructuring in this manner more broadly across the language would also be killer. 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.
- macintux 6y agoPattern matching in Python is such a mess. 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.
- fnord77 6y agofeels like languages are converging.
- fulafel 6y agoThe implicit isinstance the repurposing of the constructor syntax all seems very un-Pythonic.
- loa_in_ 6y ago>Note that because equality (__eq__) is used, and the equivalency between Booleans and the integers 0 and 1, there is no practical difference between the following two: >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.
- henryiii 6y agoI'm excited, but it seems like setting names by default is very odd. Quite a bit of the PEP is dedicated to the odd situations created by "case x" actually setting x rather than reading x ("case .x" would read x). Wouldn't this be a natural place for := ? So you would do: 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.
- dragonwriter 6y agoWith pattern matching you normally want bindings, at least local to the match construct (that the PEP proposes bindings with normal Python function scope, rather than local to the construct has plusses and minuses), so creating extra verbosity for bindings is complicating the normal case.