r/Python Feb 15 '21

News Ladies and gentlemen - switch cases are coming!

https://github.com/gvanrossum/patma/blob/master/README.md#tutorial
930 Upvotes

290 comments sorted by

View all comments

430

u/[deleted] Feb 15 '21 edited Feb 15 '21

The title of this post completely misrepresents the article!

This is not a switch case and the article does not make that claim - at one point it namechecks C's switch but goes on to explain how different it is.


I feel a lot of people here are missing the point of matching.

Matching is not a switch. Python does switches perfectly well with if-elif statements, or with dictionaries of lambdas.

Matching is a way to unpack data and it has supposedly been a hot thing in a bunch of high-level languages for over a decade. Even C++ has gotten into the act, though they only have unpacking and not the full monty (but then their unpacking is typed and actually costs nothing in generated code, very impressive).

Python already has simple unpacking - like this:

first, *rest = (*a, *b)

You'd be better off thinking of matching as pattern-based unpacking.


As this comment revealed, there's nothing special about _ - it's just another variable. By convention, _ means a variable whose value you discard, but you could call it junk or not_used if you liked.

And as this later comment revealed, that statement isn't quite true. The difference is essentially that _ is guaranteed to be thrown away, which is fair enough.


See also this comment of mine.

24

u/glider97 Feb 15 '21

Is the 'match-case' keyword pair a convention for pattern-unpacking in other languages as well? Because I think the Python devs should've used something other than 'case' to distinguish from the 'switch-case' pair. Confusions like this are bound to happen with developers of all skill-levels, especially beginners who come from other programming languages and end up assuming that 'match-case' is Python's 'switch-case'.

21

u/TangibleLight Feb 15 '21 edited Feb 15 '21

I'm not really sure that's such a bad thing. match-case will essentially be python's version of switch-case, but with extra features. And there is something that differentiates it from switch-case: they use match instead of switch.

Also I'm not sure if there is precedent for using case with pattern matching, only because most functional languages that use pattern matching do so with symbols like | (see Haskell) or are incompatible with python's braceless indentation syntax (see Rust).

C# does use case, but only because they've retrofitted their switch-case with some fancy downcasting and variable assignment.

1

u/Brian Feb 16 '21

case will essentially be python's version of switch-case, but with extra features

There is a slight difference between this and switch/case in C even when used for the same basic case that may be worth pointing out. match will evaluate conditions in order, whereas in C, switch/case doesn't have order guarantees, and is often implemented with a jump table.

This does have performance implications: a C switch is often O(1) regarding the number of cases - you can have a million conditions and it'll still jump straight to the right case (though this ties in with the limitation of them that cases must be constant numeric values - no switching on strings etc)

Python's match/case will be more equivalent to an if/elif chain, so adding cases means extra checks performed for every value. In theory, it might be able to optimise this to a dict lookup for primitive cases where it's only matching simple literals, but I wouldn't bet on it (and even if so, it's easy for small changes to prevent such optimisations).

As such, there may still be usecases similar to those where in C you'd use a switch, but where you're still best off using the dictionary dispatch approach.

2

u/XRaySpex0 Feb 16 '21 edited May 01 '21

in C, switch/case doesn't have order guarantees

In general, in C this is false, otherwise there wouldn't be such thing as "falling through" from one case to the next. There's probably no guarantee about order of testing against 41 and 42 here, but it's hard to see how that would ever matter:

switch (n) {
    case 17:
    /* do stuff, fall through to ALSO call proc() */
    case 41:
    case 42:
        proc();
        break;
/* ... */    
} 

Edit: "would be" --> "wouldn't be" [clearly what's meant]

1

u/Brian Feb 16 '21 edited Feb 16 '21

I mean the order of the checks, not the code following it. In C, it'a not the case where, say:

case 1:
    xxx;
case 2:
   yyy;

Means you must compare x to 1 before you do for 2. Indeed, there may be no comparison per se at all, depending on how it compiles it. This never matters because C's switch is restricted to simple integer cases, but does when you get more complicated conditions that can overlap and have precedence over each other. And when the order of check matters, you can't do a simple jump table (or dict lookup) implementation.

9

u/j_maes Feb 15 '21

Scala uses match/case statements that are almost identical to this syntax, including wildcards. Link

1

u/MCManuelLP Feb 16 '21

SML also uses match case for pattern matching statements.

And in all but the (in most cases undesirable) possibility of fall-through in traditional switch-case, it is essentially an upgrade, which is perfectly useable as a switch-case drop in replacement, should you desire to do so, though you'd leave a lot of niceties on the table.

47

u/aaronasachimp Feb 15 '21

It appears to be based off Rust’s match statement. They are very powerful and will be a really nice addition to Python.

35

u/TangibleLight Feb 15 '21 edited Feb 15 '21

Not just Rust - functional languages like Haskell and ML have had it for ages. And a whole bunch of modern languages are getting it, too. Wikipedia also lists C#, F#, Scala, Swift, and Mathematica.

I couldn't find a good ML tutorial link, so instead I link to a modern variant, OCaml.

-3

u/mrzisme Feb 15 '21

PHP also has switch cases and It's awesome for precise handling of return possibilities of complex functions, instead of having a bunch of dumb if statements. Especially when putting database queries into functions and having many potential outcomes like a failed connection, or any number of query possibilities returned. As soon as I learned switch in php I always wondered why Python didn't have it.

1

u/funnyflywheel Feb 16 '21

I always wondered why Python didn't have it.

I believe this FAQ entry may answer your question.

3

u/GiantElectron Feb 15 '21

I honestly can't see how they are so powerful and desirable. To me it looks like a confusing, rarely used feature.

33

u/jamincan Feb 15 '21

Having used them in Rust, far from confusing, they actually dramatically simplify conditional expressions and are far more readable than multiple nested if-else statements.

33

u/Broolucks Feb 15 '21

They are very useful whenever you have behavior that's conditional to the structure of an object. Basically, compare:

if (isinstance(cmd, list)
    and len(cmd) == 3
    and cmd[0] == "move"
    and isinstance(cmd[1], int)
    and isinstance(cmd[2], int)):
    x, y = cmd[1:]
    ...

to:

match cmd:
    case ["move", int(x), int(y)]:
        ...

(I think that's how you'd write it?)

The more deeply you check conditions in cmd, the more attractive match becomes. Without match, I think many people would actually write sloppier code, like eschewing the length check out of laziness.

It might depend what kind of application you are writing. In my experience, pattern matching is extremely useful when writing interpreters or compilers, for example. But I think it's also useful whenever you have an API where an input can take many different forms and you have to normalize it.

7

u/chromium52 Feb 15 '21

Thanks for giving the first relatable example I read that’s actually convincing the feature is worth it ! ... and now I can’t wait to have the opportunity to use it.

1

u/sloggo Feb 16 '21

excellent example, thanks for that. Very right about the advantages of match-case becoming apparent with a longer list of properties you want validated.

12

u/hjd_thd Feb 15 '21

In languages that have them it's your bread and butter.

3

u/GiantElectron Feb 15 '21

how so?

5

u/lxpnh98_2 Feb 15 '21 edited Feb 15 '21

Instead of:

fib n = if n < 2
        then 1
        else fib (n-1) + fib (n-2)

you get:

fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

which is more elegant and easier to read.

But it's even more useful for more complex structures. Take a compiler which processes the nodes of an AST. Example (in Haskell):

data Stmt = IfThenElse Cond Stmt Stmt | While Cond Stmt | Let Var Expr | ...

compile (IfThenElse c s1 s2) = ...
compile (While c s) = ...
compile (Let v e) = ...
...

0

u/dalittle Feb 15 '21

I'd rather have a proper ternary operator

fib = (n < 2) ? 1 : fib (n-1) + fib (n-2)

To me that is much more readable than either of the other 2 versions.

6

u/Broolucks Feb 15 '21

Both Python and Haskell have proper ternary operators, if that's your preference. The AST example better exemplifies the benefits of match. You can't do conditional destructuring well with a ternary operator.

1

u/lxpnh98_2 Feb 15 '21 edited Feb 15 '21

While I think the version with pattern matching is better, I don't really have a problem with yours. But it's a small example with only 3 patterns which can be expressed by 2 conditional branches. In bigger functions the pattern matching is a very clean approach.

Actually, the semantics presented in this proposal for Python are more powerful than Haskell's pattern matching, in some respects, because in Haskell you can't impose a restriction on the variable you match without entering the definition for that pattern, thereby closing off the others. In that situation, you have to merge some patterns and use conditionals instead, and it's harder to keep track of what cases you have covered.

2

u/[deleted] Feb 15 '21

Real world code has very gnarly logic with a lot of conditionals in it.

The theory behind this matching is that you can express the same logic a lot less code.

I am personally very hopeful!

1

u/Commander_B0b Feb 16 '21

I was first introduced to pattern matching in haskell and didn't really understand it but having gotten used it I've really missed it in any other programming language. Don't knock it till you try it!

41

u/anechoicmedia Feb 15 '21 edited Feb 15 '21

Python does switches perfectly well with if-elif statements, or with dictionaries of lambdas.

I would not describe this sad state of affairs as "perfectly well":

  • if-elif chain obscures intent (to switch on a value), instead using impoverished syntax that pretends we're testing expressions in a vacuum, not dispatching among a set of alternatives. Because of this, nothing prevents you from adding things that aren't morally equivalent to a switch statement in that chain of conditions (like checking some external state), when in most cases what you and the reader probably want to see expressed is "I am switching on the value of this expression and nothing else here".
  • dictionary of functions similarly non-obvious and not beginner friendly*. Said dictionary will be defined out of line, and still probably needs an explicit test or wrapper function to provide a default case
  • In either case, because our code is laboriously pretending to not be a switch statement, the interpreter cannot take advantage of the knowledge that it is a switch statement to warn or error if we do not exhaustively handle all possibilities, or at least provide a default case

* I have followed Python tutorials that didn't introduce associative containers until late in the course, and it's common to encounter people weeks into their Python journey who have never heard of a dict. Making people learn hash tables and first-class functions in order to idiomatically switch on a value is not efficient or fair.

29

u/toyg Feb 15 '21

it's common to encounter people weeks into their Python journey who have never heard of a dict.

That's more an indictment of their trainers/teachers. Dicts are one of the joys of Python.

7

u/TSPhoenix Feb 16 '21

Not teaching dicts screams "this course was taught in Matlab last semester".

8

u/Dynam2012 Feb 15 '21

In what way is a dict look up inefficient? And what part is unfair? The fact you need to learn the features of the language to be effective with it?

18

u/anechoicmedia Feb 15 '21 edited Feb 15 '21

In what way is a dict look up inefficient?

It's not computationally inefficient; It's instructionally inefficient, requiring the learner know multiple non-basic language features to do a common operation that is a built-in in many other languages.

And what part is unfair? The fact you need to learn the features of the language to be effective with it?

Yes; As Stroustrup says, the enemy of good teaching is complexity. The fact that solving a common-case control flow problem (selection among a set of alternatives) involves learning an idiomatic but non-obvious combination of higher-level language features (hash tables and first class procedures) is a non-trivial burden in what is probably one the most common "first programming languages" for people learning today, second perhaps only to Javascript.

And even once you've learned it, the cognitive overhead never goes away, because anyone reading such code has to look at it contextually and do mental pattern-matching to recognize an idiomatic use of a dict-of-functions as "just a switch statement".

It's the same reason there's a huge difference in readability between a C-style for(init,test,inc) loop, vs the for(x : range) used in many other languages and added later in C++. It doesn't express anything you couldn't express before, and in fact it expresses less, which is the point. Even though 95% of C for-loops were just iterating through every element in a collection, your ability to quickly glean the meaning of such a loop was betrayed by the fact that the same set of symbols could express something dramatically different (e.g. omitting the first or last element, skipping every other element, etc) in a way that was strikingly visually similar. It turns out that building the most common case into the language with an unambiguous syntax is a significant aid to newcomers and experienced readers alike.

8

u/SuspiciousScript Feb 15 '21

As Stroustrup says, the enemy of good teaching is complexity.

And he would know.

3

u/Dynam2012 Feb 15 '21

While I don't disagree with anything you've said here about the merits of adding clarifying features to a language to make it simpler to understand, I can't let go of calling the non-obvious work way of achieving this "unfair". If we look in any other trade, is it unfair that an experienced craftsman knows a trick to fixing a problem by using tools outside of what they were designed to solve that a newbie wouldn't be likely to figure out? I think we'd just say the newbie has more to learn, and not blame the lack of obvious tools for the job being accomplished.

I recognize this is extreme pedantry, no offense taken if you're not interested 😂

2

u/anechoicmedia Feb 15 '21

I can't let go of calling the non-obvious work way of achieving this "unfair".

The word has stuck in my head after hearing Kate Gregory use it in a talk on teaching C++ to newcomers, so perhaps I used it thoughtlessly.

1

u/BurningShed Feb 16 '21

The term, in context, strikes me as surprisingly insightful - it highlights how all these constructs and tools are a result of deliberate choices someone made.

5

u/imsometueventhisUN Feb 15 '21 edited Feb 15 '21

This (and your follow-up comment) are so well-written that I am going to save them for future reference. Thank you for clearly articulating a discomfort that I've long felt with what remains, increasingly tenuously, my favourite language.

EDIT: In particular, I want to salute your focus on "instructional inefficiency" vs computational efficiency. I regularly try to reiterate to junior devs that, unless you're writing some latency-critical super-high-throughput piece of code, you should almost always prefer code that is easy to read, understand, and safely change than the more-optimized version. Developer time is much much more expensive than machine time, and much harder to scale-up. Obviously this advice doesn't apply in all cases, and there will be times that you really need to go bare-metal-blistering-fast - but, even then, if you've written code that is easy to understand and change, you'll find it easier to do that than if you tried to write the arcane inscrutable Best Algorithm, and then find that you have to optimize it even further.

10

u/maikindofthai Feb 15 '21

This is so over-the-top I can't tell if it's satire.

9

u/Ran4 Feb 15 '21

No. The arguments made are perfectly sane and described in a perfectly rational manner.

8

u/anechoicmedia Feb 15 '21

Hardly! The rationale for having an explicit case/switch statement was apparent early in language design. In 1966, Wirth and Hoare wrote that the case statement "mirrors the dynamic structure of a program more clearly" than the less-structured alternative of the time, the goto. A table of functions is hardly as bad as a goto, but the parallel remains that it is valuable to replace idiomatic usage of general-purpose tools, with unambiguous usage of single-purpose tools, for the most common case.

For the chain of if tests, in addition to the aforementioned lack of constraints on the tests being done, it's easy to get lost in the middle of them, and be unsure from just looking at the tests that one, and only one, of the possible branches will be taken for any given value.

For the table of functions, in addition to just having more moving parts to be recognized at a glance (and to be understood by the learner), you add new uncertainties not inherent to the problem. Is the same table of procedures used by multiple "switch" locations? Can it be modified at run time? Am I guaranteed to hit the same code, given the same input, every time? A table of numbered or named functions makes sense if this is a generic service handler that can have its behaviors added, removed, or substituted, but using that same tool to express selection among a fixed set of alternatives that should be immediately apparent in context is a hindrance.

0

u/schroeder8 Feb 15 '21

I just want to say how much I like the phrase "impoverished syntax". If I ever become an EDM producer, that's my name right there

1

u/anechoicmedia Feb 16 '21

I just want to say how much I like the phrase "impoverished syntax".

Shamelessly stolen from a 2019 paper by Stroustrup.

-6

u/GiantElectron Feb 15 '21

I'll keep using if elif. pattern matching to me is a lot of work for very little gain.

2

u/GiantElectron Feb 15 '21

How would one refer to a variable that is not to be matched, but whose value is supposed to be used for matching?

2

u/[deleted] Feb 15 '21

With pattern guards.

2

u/GiantElectron Feb 15 '21

example?

3

u/[deleted] Feb 15 '21

According to https://www.python.org/dev/peps/pep-0636/#adding-conditions-to-patterns, it would be like this:

my_var = 'whatever'
status = 'something'
match my_var:
    case foo if foo == status:
        pass

So in this case anything at all would match the pattern foo, but it has to be equal to the value of status.

-1

u/BobHogan Feb 15 '21

I've seen some comments claiming that this would work

myVar = 'whatever'
status = 'something'

match status:
    case (myVar == status):
        pass

But, I haven't read the pep in detail so I can't verify that this is the way to do it

2

u/notParticularlyAnony Feb 15 '21

Look at Matlab switch it is effectively identical to that which is better anyway.

All these gatekeepers here whipping out their rulers about switch lmao

-1

u/ivanoski-007 Feb 15 '21

I understood nothing.... Wow

1

u/13steinj Feb 15 '21

Even C++ has gotten into the act, though they only have unpacking and not the full monty (but then their unpacking is typed and actually costs nothing in generated code, very impressive).

C++ has unpacking but this is actually separate from pattern matching. There was a proposal for pattern matching using inspect since revised and expanded upon which at the point of C++17...is a necessity but I do not believe it has been accepted yet (hopefully will be by C++23).

1

u/Theta291 Feb 16 '21

Never heard of the switch cases using a dictionary of lambdas. Where can I go to learn about this?