
PEP 622 – Structural Pattern Matching - kragniz
https://www.python.org/dev/peps/pep-0622/
======
justusw
Very 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.

~~~
sillysaurusx
I have a visceral dislike of pattern matching. Lisp shows just how much people
will abuse it in real-world production codebases. It becomes impossible to
understand even simple logic without comments. I’d link to some examples, but
I’m on mobile; suffice to say, pull up the emacs codebase and read through
some of the more advanced modules like edebug.el. I’m not certain that one
uses pattern matching, but it’s a perfect example of “this codebase cannot be
understood without extensive study of language features.”

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.

~~~
logicchains
Apart from Clojure, lisps generally do not support destructuring pattern
matching on an object/dict.

~~~
sillysaurusx
The defining feature of lisp is that it can support whatever you want, because
the AST is available at compile time and is completely regular. If your point
is about dicts specifically, then you might be technically correct, but I
assure you that the majority of lisp codebases do support exactly the sort of
pattern matching in this PEP. And the abuses are frankly egregious. Racket is
the worst offender of them all, with syntax matching.

------
mdrachuk
So 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{}, ...}}

~~~
andolanra
It's worth noting that there's a _truly massive_ amount of precedent in other
languages for Python implementing it using the syntax as proposed. Languages
that have or are planning to include pattern-matching where the pattern syntax
exactly mirrors the expression syntax like this include Rust, Swift, OCaml,
Haskell, C++, Ruby, Erlang, and many, many more.

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!

~~~
qorrect
Yes I think thats what the original commenter was missing. Pattern matching is
not new ( but it is awesome ), there are already expectations of how it would
look in Python.

I can't wait for this feature!

------
uryga
glad 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
I 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)

~~~
pjmlp
With a good mix of comprehensions, itertools, functools and lambda, there is
enough stuff to get creative with FP in Python anyway.

~~~
uryga
btw, 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
If 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
> 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.

------
MattConfluence
Lately, 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)

------
sicromoft
This 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-...](https://www.python.org/dev/peps/pep-0622/#sealed-classes-as-adts)

~~~
tayo42
I guess then python can have either and maybe? That would be real nice.
working with exceptions sucks...

------
mkl
"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)

------
aparmentier
Interesting 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
It'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
You can also do [a, * b] when constructing a list so that b would be injected
into that list after a - makes total sense.

------
BiteCode_dev
So 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.

------
csantini
If 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, _)

~~~
JNRowe
There 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)

------
sitkack
If you like pattern matching and Python, I recommend you check out
[http://coconut-lang.org/](http://coconut-lang.org/) which compiles to Python.

~~~
luttik
Only having the prettier lambda in python would make my so happy.

------
whalesalad
This 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.

------
hnlmorg
Can 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
Taking 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
In 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
These 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.

------
jjice
Rust 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.

------
henryiii
I'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
With 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.

------
lincolnq
This 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
Yeah, 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.

------
Spiritus
Note 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/)

------
Narann
> 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
It reads like a Node instance construction, but it's actually syntactic sugar
for: isinstance(node, Node) and node.children == [LParen(), RParen()]

~~~
chrisshroba
It'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-
mat...](https://www.python.org/dev/peps/pep-0622/#default-object-match)

------
fulafel
The implicit isinstance the repurposing of the constructor syntax all seems
very un-Pythonic.

------
vslira
This 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

------
loa_in_
>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.

------
xiaodai
This is trying to achieve a poor-human's multiple-dispatch with match-case
syntax

------
blondin
finally our own switch statement and a modern and powerful one at that. we
needed this a long time ago!

------
creativecupcak3
Very excited about this! Looking forward to using these super switches when
they are released.

------
macintux
Pattern 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
feels like languages are converging.

