[Python-ideas] Yet Another Switch-Case Syntax Proposal

114 views
Skip to first unread message

Lucas Malor

unread,
Apr 17, 2014, 3:14:11 PM4/17/14
to python...@python.org
Hello everybody. I firstly proposed this syntax in the python-list. As I already wrote there, I read PEP 3103 and I'm not completely satisfied by any of the proposed solutions.

My first idea was a little different, this is my final proposal after a short but good brainstorm:

switch_stmt ::= "switch" switch_expr "case" case_expr ":" suite
("case" | "elcase" case_expr ":" suite)*
["else" ":" suite]
switch_expr ::= expression
case_expr ::= expression_list

- if case_expr is a tuple, the case suite will be executed if switch_expr is a member of the tuple
- if case_expr is not a tuple, the case suite will be executed if switch_expr == case_expr
- if a case_expr is checked, any subsequent elcase statements are skipped, and the next case statement is performed, of there's one. This is completely identical to if - elif.

Example:

briefing_days = ("Tue", "Thu")
normal_days = ("Mon", "Wed", "Fri")

switch day case normal_days + briefing_days:
go_to_work = True
day_type = "weekday"
case normal_days:
lunch_time = datetime.time(12)
meeting_time = datetime.time(14)
elcase briefing_days:
lunch_time = datetime.time(11, 30)
meeting_time = datetime.time(12, 30)
else:
go_to_work = False
day_type = "festive"
lunch_time = None
meeting_time =None

A simpler example:

switch tarot case 0:
card = "Fool"
elcase 1:
card = "Alan Moore"
elcase 2:
card = "High Priestess"
<etc....>

Some remarks:

1. switch is on the same line of the first case. This is because alternatives in PEP 3103 seems unpythonic to me
2. I decided to not use already existing keywords like "if" or "in", since this will be misleading
3. I preferred case / elcase instead of using break or continue keyword because:
a. break / continue can confuse, since they are used in loop statements, and this is not a loop statement, as Ethan Furman pointed out in the other mailing list
b. break / continue is useful only if it can be used not only as final command, so you can skip the rest of the current case suite. If it must be the final command, it's the same of case - elcase. IMHO there's no much real need to allow this
c. case - elcase syntax is much more readable and less confusing of a break / continue
d. case - elcase syntax is identical to if - elif, so people will be more familiar with it
4. notice that you can put a "case" statement after an "elcase" one. For example:

switch cpu case "A6-6400K", "A8-6600K"
clock = 3.9
elcase "A10-6790K", "A10-6800B"
clock = 4.1
case "A6-6400K"
tdp = 65
elcase "A8-6600K", "A10-6790K", "A10-6800B"
tdp = 100

is equivalent to

if cpu in ("A6-6400K", "A8-6600K")
clock = 3.9
elif cpu in ("A10-6790K", "A10-6800B")
clock = 4.1

if cpu == "A6-6400K"
tdp = 65
elif cpu in ("A8-6600K", "A10-6790K", "A10-6800B")
tdp = 100
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Guido van Rossum

unread,
Apr 17, 2014, 3:31:31 PM4/17/14
to Lucas Malor, Python-Ideas
I don't want to discourage you too much, but I think that adding a switch statement comes *very* low on the list of improvements we would like to make in Python 3.5.

We should probably focus on speed (or aspects of it, like startup), language features that help porting Python 2 code to it (e.g. bytes formatting), and things that improve the user experience of getting started with Python on a new machine (e.g. pip/venv). Or perhaps stdlib issues like an asyncio-infused variation of WSGI.

I've probably missed a few focus areas, but I still very much doubt we'll be adding a switch statement -- it's a "sexy" language design issue (like anonymous functions) but that's not what will help Python compete.


--
--Guido van Rossum (python.org/~guido)

Andrew Barnert

unread,
Apr 17, 2014, 5:41:56 PM4/17/14
to Lucas Malor, python...@python.org
On Apr 17, 2014, at 12:14, "Lucas Malor" <7vsfe...@snkmail.com> wrote:

> switch_stmt ::= "switch" switch_expr "case" case_expr ":" suite
> ("case" | "elcase" case_expr ":" suite)*
> ["else" ":" suite]
> switch_expr ::= expression
> case_expr ::= expression_list

Any reason to use the keywords that only exist in C-family languages when the functionality isn't actually like C?

> - if case_expr is a tuple, the case suite will be executed if switch_expr is a member of the tuple

So there's no way to switch on a tuple?

> - if case_expr is not a tuple, the case suite will be executed if switch_expr == case_expr
> - if a case_expr is checked, any subsequent elcase statements are skipped, and the next case statement is performed, of there's one. This is completely identical to if - elif.

While you can figure out what this means with a bit of work, it seems to add a lot of conceptual complexity, and I can't think of any other language that does this.

Meanwhile, if you're trying to design a better and more powerful switch statement than other languages have, why not consider features that other languages do have, like pattern matching cases? Maybe it would be too hard to implement, or would conflict with another part of the feature, or just wouldn't fit in with Python, but if so, a switch proposal should try to explain that.

Oleg Broytman

unread,
Apr 17, 2014, 5:53:43 PM4/17/14
to python...@python.org
While I don't think it's a good addition for Python...

On Thu, Apr 17, 2014 at 02:41:56PM -0700, Andrew Barnert <abar...@yahoo.com.dmarc.invalid> wrote:
> On Apr 17, 2014, at 12:14, "Lucas Malor" <7vsfe...@snkmail.com> wrote:
>
> > switch_stmt ::= "switch" switch_expr "case" case_expr ":" suite
> > ("case" | "elcase" case_expr ":" suite)*
> > ["else" ":" suite]
> > switch_expr ::= expression
> > case_expr ::= expression_list
>
> > - if case_expr is a tuple, the case suite will be executed if switch_expr is a member of the tuple
>
> So there's no way to switch on a tuple?

A tuple can be a member of a bigger tuple:

>>> (1, 'b') in ((1, 'a'), (1, 'b'), (1, 'a', 'b'))
True

Oleg.
--
Oleg Broytman http://phdru.name/ p...@phdru.name
Programmers don't die, they just GOSUB without RETURN.

Andrew Barnert

unread,
Apr 18, 2014, 1:03:11 AM4/18/14
to Oleg Broytman, python...@python.org
On Apr 17, 2014, at 14:53, Oleg Broytman <p...@phdru.name> wrote:

> While I don't think it's a good addition for Python...
>
> On Thu, Apr 17, 2014 at 02:41:56PM -0700, Andrew Barnert <abar...@yahoo.com.dmarc.invalid> wrote:
>> On Apr 17, 2014, at 12:14, "Lucas Malor" <7vsfe...@snkmail.com> wrote:
>>
>>> switch_stmt ::= "switch" switch_expr "case" case_expr ":" suite
>>> ("case" | "elcase" case_expr ":" suite)*
>>> ["else" ":" suite]
>>> switch_expr ::= expression
>>> case_expr ::= expression_list
>>
>>> - if case_expr is a tuple, the case suite will be executed if switch_expr is a member of the tuple
>>
>> So there's no way to switch on a tuple?
>
> A tuple can be a member of a bigger tuple:
>
>>>> (1, 'b') in ((1, 'a'), (1, 'b'), (1, 'a', 'b'))
> True

Well, yeah, but if you want to match (1, 'b') you'd have to write case ((1, 'b'),). Which is bad for static cases, and even worse for dynamic ones.

Lucas Malor

unread,
Apr 18, 2014, 10:42:52 AM4/18/14
to python...@python.org
On 17 April 2014 21:31, Guido van Rossum guido-at-python.org |python-ideas-at-python.org| <gpb6b...@sneakemail.com> wrote:
I don't want to discourage you too much, but I think that adding a switch statement comes *very* low on the list of improvements we would like to make in Python 3.5.

We should probably focus on speed (or aspects of it, like startup), language features that help porting Python 2 code to it (e.g. bytes formatting), and things that improve the user experience of getting started with Python on a new machine (e.g. pip/venv).. Or perhaps stdlib issues like an asyncio-infused variation of WSGI.

I completely agree.
 

I've probably missed a few focus areas, but I still very much doubt we'll be adding a switch statement -- it's a "sexy" language design issue (like anonymous functions) but that's not what will help Python compete.

Well, no, it will not be a "killer feature". But if you intend it's "sexy" because it's a syntax sugar, even if I'm not much experienced, I respectfully disagree. A switch-case statement is more DRY than an if-elif chain, in its use environment. Deltas are only quicker to code and potentially less DRY.


On 17 April 2014 23:41, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9k...@sneakemail.com> wrote:
Any reason to use the keywords that only exist in C-family languages when the functionality isn't actually like C?

Well, it's the same for the "for" statement.

> - if case_expr is not a tuple, the case suite will be executed if switch_expr == case_expr
> - if a case_expr is checked, any subsequent elcase statements are skipped, and the next case statement is performed, of there's one. This is completely identical to if - elif.

While you can figure out what this means with a bit of work, it seems to add a lot of conceptual complexity, and I can't think of any other language that does this.

Bash case statement is similar. IMHO this syntax is very simple, simpler than C-like switch statements. Why do you think it is complex?


why not consider features that other languages do have, like pattern matching cases?

I do not know much about pattern matching. Using my little knowledge, Python have recursive functions and regular expressions. You could also use generator expressions with my switch statement proposal. Symbolic pattern is powerful, but it's a bit too much for this little proposal.


if you want to match (1, 'b') you'd have to write case ((1, 'b'),). Which is bad for static cases, and even worse for dynamic ones.

Yes, it's not so elegant, but I do not think that it will really matters in real world usage. What do you mean exactly with "dynamic cases"?


Joao S. O. Bueno

unread,
Apr 18, 2014, 11:03:07 AM4/18/14
to Lucas Malor, Python-Ideas
On 17 April 2014 16:14, Lucas Malor <7vsfe...@snkmail.com> wrote:
> A simpler example:
>
> switch tarot case 0:
> card = "Fool"
> elcase 1:
> card = "Alan Moore"
> elcase 2:
> card = "High Priestess"
> <etc....>

It may be just me, but I fail - in a complete manner - to see how this
syntax can offer any
improvement on current if/elif chains. It seems to differ from that
only by reducing
the explicitness, and the flexibility of the test condition that can
be used in any
of the subconditions,

No, sorry, unlike others have mentioned, I don't find this
particularly "sexy". "Clumky"
is more like it:

if tarot ==0:
card = "Fool"
elif tarot == 1:
card = "Alan Moore"
elif 2 < tarot < 22: # expressiveness
card = "Surprise"
else:
raise ValueError

The implicit "in" operation case is even more weird.

js
-><-

Skip Montanaro

unread,
Apr 18, 2014, 11:22:14 AM4/18/14
to Joao S. O. Bueno, Python-Ideas
On Fri, Apr 18, 2014 at 10:03 AM, Joao S. O. Bueno
<jsb...@python.org.br> wrote:
> It may be just me, but I fail - in a complete manner - to see how this
> syntax can offer any
> improvement on current if/elif chains.

I haven't been following this thread closely, so I don't know if Lucas
has mentioned more than syntax, however...

In other languages, the semantics of the switch statement allow the
compiler to generate more efficient code. Instead of testing each
branch of the if statement in succession looking for a match, you
evaluate the switch expression once, then use it as an index into a
jump table of some sort which points directly to the matching case
label. If nothing matches, you jump to the default branch (if one was
defined) or just jump over the entire switch statement (if not).

Skip

Skip Montanaro

unread,
Apr 18, 2014, 11:26:57 AM4/18/14
to python-ideas
I should also mention that the use of a jump table depends on the use
of case labels which can be evaluated at compile time. This might make
it challenging (or impossible) for this scheme to be implemented in
Python.

Andrew Barnert

unread,
Apr 18, 2014, 12:51:26 PM4/18/14
to Lucas Malor, python...@python.org
On Apr 18, 2014, at 7:42, "Lucas Malor" <7vsfe...@snkmail.com> wrote:

On 17 April 2014 23:41, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9k...@sneakemail.com> wrote:
Any reason to use the keywords that only exist in C-family languages when the functionality isn't actually like C?

Well, it's the same for the "for" statement.

No it isn't. First, the "for ... in" keywords are not the same as just "for" and a bunch of parens and semicolons. If you just mean the word "for" itself, the exact same word is used in a wide variety of languages that have nothing to do with C, and some of them use it with the same meaning as Python. 

By contrast, most languages that are not related to C don't spell this statement "switch ... case ..."; the most common is probably "case ... of ..." or "case ... in ...".

Also, notice that if you try to read the switch statement, or your Python version, as English, it's nonsense. Compare that to for, while, if, try, etc. While there are some oddities (like condensing "else" and "if" into "elif"), as a general rule they make sense. That's not even remotely true with a C for statement.

> - if case_expr is not a tuple, the case suite will be executed if switch_expr == case_expr
> - if a case_expr is checked, any subsequent elcase statements are skipped, and the next case statement is performed, of there's one. This is completely identical to if - elif.

While you can figure out what this means with a bit of work, it seems to add a lot of conceptual complexity, and I can't think of any other language that does this.

Bash case statement is similar.

Bash doesn't have separate "case" and "elcase" cases. After one case is done, the rest are skipped, just as in most other languages. 

IMHO this syntax is very simple, simpler than C-like switch statements. Why do you think it is complex?

C cases always fall through to the next case. Almost every other language only executes a single case. I don't see how skipping over any elcase but falling through to the next case is in any way simpler than C.

why not consider features that other languages do have, like pattern matching cases?

I do not know much about pattern matching.

Well, then at least look at the limited form of pattern matching Python has in the for and assignment statements and parameter matching, and maybe look at how pattern matching is used with case statements in other languages; don't try to suggest language designs based on guesses.

Using my little knowledge, Python have recursive functions and regular expressions.

... and? Are you suggesting that if the switch expression is a string and the case expression a compiled regex you could automatically call match instead of testing for equality? If not, how is having regexp even relevant here? And how are recursive functions relevant?

You could also use generator expressions with my switch statement proposal.

How? Where? A generator expression is equal to anything except itself, and doesn't contain anything.

Symbolic pattern is powerful, but it's a bit too much for this little proposal.

I don't know what you mean by "symbolic pattern" here.

if you want to match (1, 'b') you'd have to write case ((1, 'b'),). Which is bad for static cases, and even worse for dynamic ones.

Yes, it's not so elegant, but I do not think that it will really matters in real world usage.

Really? Have you never wanted to switch on the results of a function that returns two values?

What do you mean exactly with "dynamic cases"?

Exactly what it sounds like: when the case value is a variable set somewhere else in the program, possibly by some program calling my library or even selected by user input or config, so at the point of the switch statement you (meaning a reader of the code) have no idea whether the value is a tuple; it's just some variable or lookup expression or the like.

Lucas Malor

unread,
Apr 18, 2014, 7:08:58 PM4/18/14
to python...@python.org
On 18 April 2014 16:58, Ed Kellett edk141-at-gmail.com |python-ideas-at-python.org| <baswp...@sneakemail.com> wrote:
    case foo():

would have to become

    case (foo(),):

to work as expected when foo() returned a tuple, which would mean
wrapping things in 1-tuples whenever you wanted to reliably match a
case that is determined dynamically.

To obviate this, instead of tuples case could check membership of CaseTuples. CaseTuple will be a class that extends tuple, identical to it, and a case expression list with commas will yield a CaseTuple. To use other iterables as case expression list you'll have to unpack them, or they will be matched for equality.



On 18 April 2014 17:03, Joao S. O. Bueno jsbueno-at-python.org.br |python-ideas-at-python.org| <0ucjz...@sneakemail.com> wrote:
It may be just me, but I fail - in a complete manner - to see how this
syntax can offer any
improvement on current if/elif chains. It seems to differ from that
only by reducing
the explicitness, and the flexibility of the test condition that can
be used in any
of the subconditions

It's more simple to use when you can use it, as switch statement in the other languages. And it somewhat adheres to the DRY principle: why repeat the subject? If I'm checking what type of tarot card I have, why should I repeat every time I'm trying to identify a tarot card?
I also thought about a syntax like this:

"case" comparator case_expr ":"

but IMHO it's too verbose for the typical uses of switch. If you want flexibility, you can always use if-elif.


@Skip Montanaro: yes, switch statement is used in C also for code optimization. Frankly, I think this aspect is unimportant for CPython in the present time.



On 18 April 2014 18:51, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9k...@sneakemail.com> wrote:
No it isn't. First, the "for ... in" keywords are not the same as just "for" and a bunch of parens and semicolons.

My propose has elcase, that is not present in other languages.



Also, notice that if you try to read the switch statement, or your Python version, as English, it's nonsense.

Yes, the fact a case will behave differently for tuples and non-tuples will be difficult to translate in English. I think that with CaseTuple proposal it will be more understandable, since you have to explicitly unpack an iterable, or use elements separated by commas.



Bash doesn't have separate "case" and "elcase" cases. After one case is done, the rest are skipped, just as in most other languages.

But it has ;& and ;;& similarly to break and continue of C, that it's equivalent to case and  elcase of my proposal.


I don't see how skipping over any elcase but falling through to the next case is in any way simpler than C.

Well, because it's coherent with if-elif. See my last example in my first message.



Well, then at least look at the limited form of pattern matching Python has in the for and assignment statements and parameter matching, and maybe look at how pattern matching is used with case statements in other languages; don't try to suggest language designs based on guesses.

Excuse me? I know list comprehensions, lambdas and argument unpacking. And I do not think you can see what I do before I post a message. If so, you could see me googling before writing about something that I don't know very well or I don't remember very well. So don't guess about what I do or not do or know and not know, thank you.
About pattern matching in the for statement, I really don't know what they are.



... and? Are you suggesting that if the switch expression is a string and the case expression a compiled regex you could automatically call match instead of testing for equality? If not, how is having regexp even relevant here? And how are recursive functions relevant?

I'm suggesting to use if-elif with re module, if you want to use regular expression, and to use recursive functions if you want... recursive functions. To be more clear, IMHO switch-case is useful if it's simple.



A generator expression is equal to anything except itself, and doesn't contain anything.

You can convert it to an iterable. Probably an overkill, but you can do it.


I don't know what you mean by "symbolic pattern" here.

For what I know (not too much), in Mathematica pattern matching can be used for symbols, and symbols can be used as identifiers:
https://reference.wolfram.com/mathematica/guide/Patterns.html

Steven D'Aprano

unread,
Apr 18, 2014, 11:12:24 PM4/18/14
to python...@python.org
On Sat, Apr 19, 2014 at 01:08:58AM +0200, Lucas Malor wrote:

> On 18 April 2014 17:03, Joao S. O. Bueno jsbueno-at-python.org.br |
> python-ideas-at-python.org| <0ucjz...@sneakemail.com> wrote:
>
> > It may be just me, but I fail - in a complete manner - to see how this
> > syntax can offer any
> > improvement on current if/elif chains. It seems to differ from that
> > only by reducing
> > the explicitness, and the flexibility of the test condition that can
> > be used in any
> > of the subconditions
> >
>
> It's more simple to use when you can use it, as switch statement in the
> other languages. And it somewhat adheres to the DRY principle: why repeat
> the subject? If I'm checking what type of tarot card I have, why should I
> repeat every time I'm trying to identify a tarot card?

I think that is a misunderstanding of the DRY principle. I'll explain
further below, but even if the idea is to avoid writing anything twice,
the case syntax fails. Consider your example:

switch tarot case 0:
card = "Fool"
elcase 1:
card = "Alan Moore"
elcase 2:
card = "High Priestess"
<etc....>


Here, you are repeating "elcase" and "card =" each time, so you are
still repeating yourself. We can avoid that by using the lookup table
approach:

table = {0: "Fool", 1: "Alan Moore", 2: "High Priestess", ...}
card = table[tarot]

Now that truly is a DRY solution!

I think that your interpretation of DRY does not match the intention of
the people who invented it. DRY is not (as I understand it) concerned
with trivial, mechanical duplication like chained if...elif:

if tarot == 0: ...
elif tarot == 1: ...
elif tarot == 2: ...

and objecting to chained if...elif as a DRY-violation is, I believe, a
misunderstanding of DRY.

I'm going to quote Dave Thomas, co-inventer of the DRY principle:

Most people take DRY to mean you shouldn't duplicate code.
*That's not its intention.* [emphasis added] The idea
behind DRY is far grander than that.

DRY says that every piece of system knowledge should have
one authoritative, unambiguous representation. Every piece
of knowledge in the development of something should have a
single representation. A system's knowledge is far broader
than just its code. It refers to database schemas, test
plans, the build system, even documentation.

http://www.artima.com/intv/dry.html

DRY is also known as "Single Source Of Truth", which is perhaps a better
name, since it emphasises the fact that there is a single canonical
source of each piece of knowlege in the system, rather than putting the
emphasis on mere mechanical duplication. In the case of your tarot
example, it is *not* a violation of DRY, because the various elif lines
are not *sources* of knowledge which may contradict each other. The
worst that will happen is that if you change the variable name "tarot"
to something else, your code will fail with a name error. DRY is not
about reducing the amount of mechanical edits you do when you rename a
variable.

# Single source of truth for the name of the variable is its binding:
tarotcard = draw_card() # was "tarot"
if tarot == 0: # now fails
...

Subsequent lines merely *use* the name, they don't act as potential
sources of knowledge which could contract each other. DRY doesn't really
have much to say about variable names, except perhaps "don't use the
same name (in the same namespace) for different things", so if your
motive in introducing a switch/case statement is to DRY, I think you
need a better motive.

One motive I'd like to see is, could a switch/case statement be used to
automate table lookups? Two problems -- albeit mild ones -- with the
table lookup idiom is that the table lives longer than needed, and it
puts the table in the wrong place. This motivated Nick to suggest a
"where" block:

card = table[tarot] where:
table = {0: "Fool", 1: "Alan Moore", 2: "High Priestess", ...}


solving both problems at once: the table is declared only when needed,
not before hand, and does not exist outside of the block. A third
problem, not solved by Nick's "where", is that the table requires every
case's value ahead of time, whether it will be needed or not. When the
values are constants, that's not a big deal, but they might be expensive
expressions.

Perhaps there is some way to optimize a case statement so as to avoid
these disadvantages of the table lookup idiom. To me, that is a
reasonable motive worth chasing. I wouldn't bother with a case statement
unless it was more efficient than a chain of if...elif.

> @Skip Montanaro: yes, switch statement is used in C also for code
> optimization. Frankly, I think this aspect is unimportant for CPython in
> the present time.

Then I think we're not going to agree on the use or need for switch.



--
Steven

Andrew Barnert

unread,
Apr 19, 2014, 12:54:56 AM4/19/14
to Lucas Malor, python...@python.org
On Apr 18, 2014, at 16:08, "Lucas Malor" <7vsfe...@snkmail.com> wrote:

On 18 April 2014 18:51, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9k...@sneakemail.com> wrote:
No it isn't. First, the "for ... in" keywords are not the same as just "for" and a bunch of parens and semicolons.

My propose has elcase, that is not present in other languages.

I can't understand why you're still not getting it, but I'll try again.

Most languages' case statements are of the form "case ... in ...", sometimes substituting "of" or punctuation in place of "in". C and its derivatives instead use "switch ... case ...". By using the unique C syntax (or a minor variation on it, which won't even be visible in any statement without an elcase) without the C semantics, you're creating a misleading parallel, and for no benefit that I can see.

Also, notice that if you try to read the switch statement, or your Python version, as English, it's nonsense.

Yes, the fact a case will behave differently for tuples and non-tuples will be difficult to translate in English.

No, that's not at the point. Consider:

    for ch in mystring:
        process(ch)

You can read the first one aloud and it sounds like an English sentence. Maybe a slightly stilted sentence, the kind of thing you'd hear from a mathematician speaking technically or someone speaking English as a foreign language, but clearly recognizable and understandable to anyone who understands English. And this generally works for all statements in Python.

The same is not at all true with the C equivalent:

    for (pch=mystring; *pch; ++pch)
        process(*pch)

No matter how you try to pronounce the punctuation, the result is not English, it's gibberish. C is not meant to be readable as English (or as "executable pseudocode", if you prefer).

Now try this with switch:

    switch mystring case "spam":
        process(thingy)

That is not even remotely interpretable as an English sentence. That's not a problem for C, but it is for Python.

Bash doesn't have separate "case" and "elcase" cases. After one case is done, the rest are skipped, just as in most other languages.

But it has ;& and ;;& similarly to break and continue of C, that it's equivalent to case and  elcase of my proposal.

First, break and continue are not even remotely equivalent of your case and elcase. A break means you skip over all subsequent cases; no break means you fall through to the next case; continue is a syntax error (unless of course the switch is inside a loop, in which case it's the loop that's continued); and there is no way short of abusing goto to skip over some cases but not others.

Second, bash does not have either ";;&" or ";&". It requires every case clause to end in ";;", which always skips over the rest of the cases. A ";" just separates statements within a clause. In some versions of bash, ";&" is interpreted basically the same as "&"--that is, it both ends a statement and makes a background job out of it--while in others (including all recent versions) it's a syntax error, as is ";;&". There is no way to either fall through to the next clause or skip some clauses but not others.

I don't see how skipping over any elcase but falling through to the next case is in any way simpler than C.

Well, because it's coherent with if-elif. See my last example in my first message.

No it isn't. An elif clause is embedded in an if statement; there is no if clause you can embed on an if statement, just an entirely new and unrelated if statement. 


Well, then at least look at the limited form of pattern matching Python has in the for and assignment statements and parameter matching, and maybe look at how pattern matching is used with case statements in other languages; don't try to suggest language designs based on guesses.

Excuse me? I know list comprehensions, lambdas and argument unpacking.

Who asked you about list comprehensions or lambdas? What do you think they have to do with anything? Can you give an example that shows how they're relevant?

And I do not think you can see what I do before I post a message.

No, all I can see is what you say. But you said that you don't know much about pattern matching, so I think it's fair to assume you don't know much about pattern matching.

And I think that also makes it fair to assume you didn't try to learn about it, because the only alternative is that you tried and were incapable, which I think would be very insulting, and I prefer not to assume that of people.

About pattern matching in the for statement, I really don't know what they are.

OK, first let's look at something you can do with a case statement in a language like ML or Haskell, translated into more Python-like syntax:

    case spam:
        of (x, y):
            process(x, y)

This case will match only if spam is a tuple of exactly two values, and will bind x and y to those values.

Obviously that syntax conflicts with your syntax for matching two distinct cases with the same clause, but ignore that for a moment. If there were suitable syntax for both, would you want that in Python?

Compare this perfectly valid Python code:

    if value == x, y:
        process(x, y)

    x, y = value
    process(x, y)

    for x, y in pairs_of_values:
        process(x, y)

Here, each element in pairs_of_values has to be an iterable (not necessarily a tuple) of exactly two values, and x and y are bound to the two values. That's very close to what's happening in the ML-style case statement (and the difference--the fact that it takes any iterable instead of a tuple--is probably what you'd expect from Python duck typing vs. ML static typing).

Meanwhile, ML and friends let you go farther, matching by partial values:

    of (True, y):
        process(y)
    of Eggs(z):
        fry(z)

The first matches only if it's a tuple of exactly two values and the first is equal to True, binding y to the second; the second matches only if it's equal to an Eggs instance constructed with exactly one argument, and binds z to that argument.

Clearly not all of this fits into Python. (The last example isn't even conceptually possible, given the way object initialization works.) But it's worth considering what does and what doesn't fit rather than trying to design a feature without even knowing what the options are.

... and? Are you suggesting that if the switch expression is a string and the case expression a compiled regex you could automatically call match instead of testing for equality? If not, how is having regexp even relevant here? And how are recursive functions relevant?

I'm suggesting to use if-elif with re module, if you want to use regular expression, and to use recursive functions if you want... recursive functions.

I have no idea why you think recursive functions are relevant to anything being discussed here. Maybe if you can give and example of what you mean?

To be more clear, IMHO switch-case is useful if it's simple.


A generator expression is equal to anything except itself, and doesn't contain anything.

You can convert it to an iterable. Probably an overkill, but you can do it.

A generator expression is already an iterable; no conversion is necessary.

But your proposal was to treat tuples specially and match all other iterables as single values, which means that a generator expression would be matched as a single value, meaning it would only match itself. 

And again, I don't understand what the relevance is supposed to be.

I don't know what you mean by "symbolic pattern" here.

For what I know (not too much), in Mathematica pattern matching can be used for symbols, and symbols can be used as identifiers:
https://reference.wolfram.com/mathematica/guide/Patterns.html

You're mixing up different terms here. Symbolic patterns in Mathematica match symbolic structures, not identifiers. The idea is to provide ML-style structural pattern matching, in a way that looks like intuitive "fill-in-the-blanks" in the simplest cases, but is then extended in a way that's similar to regular expressions, but more verbose and readable whenever regexps become too obtuse. Mathematica uses these patterns in all kinds of places beyond what Python and ML do, and it's a pretty cool feature, but I think you want to look at some examples instead of trying to get the idea from the formal reference docs.

Stephen J. Turnbull

unread,
Apr 19, 2014, 1:11:16 AM4/19/14
to Skip Montanaro, Python-Ideas
Skip Montanaro writes:

> In other languages, the semantics of the switch statement allow the
> compiler to generate more efficient code. Instead of testing each
> branch of the if statement in succession looking for a match, you
> evaluate the switch expression once, then use it as an index into a
> jump table of some sort which points directly to the matching case
> label.

Sure, but Python already has such a jump table: a function-valued
hash.

Don't-even-think-of-mentioning-Ruby-blocks-now-ly y'rs,

Bruce Leban

unread,
Apr 19, 2014, 2:52:59 AM4/19/14
to Lucas Malor, Python-Ideas
Here's a simple class that provides case like syntax. How would modifying the language be better than this? This code makes it clear that all the checks are intended to be against the same value. It can easily be extended to have structural matching or regex matching. I think case/elcase would be confusing as it would be unique to python and it's backwards -- the elcase keywords is used for the normal way that people use case statements while the case keyword implements the broken code that inexperienced C programmers frequently write. With this class, you use if/elif/else exactly the way you usually do, and notice that there is no ambiguity when checking multiple values.

class Case(object):
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def __call__(self, *values):
        return self.value in values

    def __eq__(self, value):
        return self.value == value
    def __lt__(self, value):
        return self.value < value
    def __gt__(self, value):
        return self.value > value
    # etc.
    # can easily add structural matching or regex matching as well

for i in range(5):
    print(i, end=' => ')
    with Case(i) as case:
        if case(1):
            print('one')
        elif case((2,3)):
            print('tuple(two, three)')
        elif case(2, 3):
            print('two or three')
        elif case > 3:
            print('more than three')
        else:
            print('unmatched')
            
produces

0 => unmatched
1 => one
2 => two or three
3 => two or three
4 => more than three

Is it worth using a context manager for this? Maybe not. But if not, I think it's even less worthwhile to modify the language to add a case statement.



Michael Foord

unread,
Apr 19, 2014, 5:56:14 AM4/19/14
to Skip Montanaro, Python-Ideas
On 18 April 2014 16:22, Skip Montanaro <sk...@pobox.com> wrote:
On Fri, Apr 18, 2014 at 10:03 AM, Joao S. O. Bueno
<jsb...@python.org.br> wrote:
> It may be just me, but I fail - in a complete manner - to see how this
> syntax can offer any
> improvement on current if/elif chains.

I haven't been following this thread closely, so I don't know if Lucas
has mentioned more than syntax, however...

In other languages, the semantics of the switch statement allow the
compiler to generate more efficient code. Instead of testing each
branch of the if statement in succession looking for a match, you
evaluate the switch expression once, then use it as an index into a
jump table of some sort which points directly to the matching case
label. If nothing matches, you jump to the default branch (if one was
defined) or just jump over the entire switch statement (if not).


In which case you compile in the switch values at "compile time", so the semantics are "surprising" (if you switch on a name instead of a fixed value then changing the object the name is bound to doesn't change the behaviour of the switch). So you either get fast but surprising (unpythonic) or dynamic and no different to an if/elif chain.

If you restrict switch to hashable literals then you can make it fast, and nicer syntactically than the standard pattern - building a dictionary of functions. But that's quite a restriction.

(You basically note this in your follow up email - but this has been the problem with previous discussions of switch/case for Python.)

Michael

 
Skip
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/



--
http://www.voidspace.org.uk/

May you do good and not evil
May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give.
-- the sqlite blessing http://www.sqlite.org/different.html

Anthony Towns

unread,
Apr 19, 2014, 7:12:47 AM4/19/14
to Steven D'Aprano, python...@python.org
On 19 April 2014 13:12, Steven D'Aprano <st...@pearwood.info> wrote:
> One motive I'd like to see is, could a switch/case statement be used to
> automate table lookups? Two problems -- albeit mild ones -- with the
> table lookup idiom is that the table lives longer than needed, and it
> puts the table in the wrong place. This motivated Nick to suggest a
> "where" block:
>
> card = table[tarot] where:
> table = {0: "Fool", 1: "Alan Moore", 2: "High Priestess", ...}
>
>
> solving both problems at once: the table is declared only when needed,
> not before hand, and does not exist outside of the block. A third
> problem, not solved by Nick's "where", is that the table requires every
> case's value ahead of time, whether it will be needed or not. When the
> values are constants, that's not a big deal, but they might be expensive
> expressions.

A related problem is that a table lookup can only hold expressions,
not statement blocks. If you want statement blocks you'd have to
define functions first, then reference back to them in the table, then
lookup the table. (And if you just want late/lazy evaluation but not
blocks, you end up having lambda expressions in your tables)

It might be reasonable to consider a "switch" statement to solve just
those problems; ie:

case X in:
1: print("Hello, world")
2: print("Goodbye, world")
3: print("Whatever")
...
else: raise Exception("unexpected value for X")

would be equivalent to:

__cases = {}
def _():
print("Hello, world")
__cases[1] = _
...
def _():
raise Exception("unexpected value for X")
__cases.default = _

__cases.get(X, __cases.default)()

If you treated it literally that way, building a dict up from nothing
every time the "case" block was hit, it would just be syntactic sugar
for if/elif, which doesn't seem valuable. (It'd save you repeating the
"X ==" part of every condition, and you might get an error if you had
"1:" as two different cases, which could be useful)

if you had the __selector dict populated at parse time, you'd be able
to reuse it on each iteration/call to the function and could get a
performance win (you'd only evaluate the right hand side of the
expressions once, and you'd just be doing a hash lookup on each
iteration/invocation). The downside is that dynamic values in the
cases would then be confusing:

y = 0
def foo(x):
case x in:
y:
return "matched y"
else:
return "didn't match y"

foo(0) # matched y
y = 1
foo(1) # didn't match y

or:

for i in range(10):
case i in:
i:
print("i == i")
else:
print("i != i")

might only print i == i when i is 0 (or might give a syntax error that
i is undefined when first compiling the select block).

(The above would be Alternative 4, with case instead of switch as the
keyword, Option 3 from PEP 3103 I think)

C/C++ doesn't have that problem because it can just issue an error at
compile time that "i" isn't a constant expression. I don't think you
could do anything similar in python, because you couldn't distinguish
between symbolic constants and variables... You could argue that it's
similar to when you have mutable default arguments to functions and
expect people to just deal with it, but that doesn't seem ideal
either...

You could always just refactor your "switch" statement out of your
function body and into a separate definition, in which case you could
just define a class:

def card_id(id):
def dec(f):
f = staticmethod(f)
TarotActor_card_actions[id] = f
return f
return dec
TarotActor_card_actions = {}

class TarotActor:
@classmethod
def dispatch(cls, card_id):
return TarotActor_card_actions[id]()

@card_id(0)
def Fool():
...
@card_id(1)
def Magician():
...

and then just invoke TarotActor.dispatch(tarot_card) from wherever.
Having to do two separate lines for the code block introduction
(decorator and def, versus just a case statement) is irritating I
suppose, but it's not that bad. And since it's three different levels
of indentation (switch statement, cases, and code blocks), moving it
to module level is arguably a good idea just for indentation's sake...

Cheers,
aj

--
Anthony Towns <a...@erisian.com.au>

Lucas Malor

unread,
Apr 19, 2014, 1:42:54 PM4/19/14
to python...@python.org
On 19 April 2014 05:12, Steven D'Aprano steve-at-pearwood.info |python-ideas-at-python.org| <6hqcl...@sneakemail.com> wrote:
In the case of your tarot
example, it is *not* a violation of DRY, because the various elif lines
are not *sources* of knowledge which may contradict each other. The
worst that will happen is that if you change the variable name "tarot"
to something else, your code will fail with a name error.

You could also use another existing variable. Anyway

if your
motive in introducing a switch/case statement is to DRY, I think you
need a better motive.

I completely agree. I think the main good reason to introduce a switch statement is that is more simple to use, when it can be used.


> @Skip Montanaro: yes, switch statement is used in C also for code
> optimization. Frankly, I think this aspect is unimportant for CPython in
> the present time.

Then I think we're not going to agree on the use or need for switch.

To be clear: I'm not saying you can't improve the performance of a switch statement, but IMHO currently Python has worse speed problems.


On 19 April 2014 07:11, Stephen J. Turnbull stephen-at-xemacs.org |python-ideas-at-python.org| <85q57...@sneakemail.com> wrote:
Skip Montanaro writes:

 > In other languages, the semantics of the switch statement allow the
 > compiler to generate more efficient code.  Instead of testing each
 > branch of the if statement in succession looking for a match, you
 > evaluate the switch expression once, then use it as an index into a
 > jump table of some sort which points directly to the matching case
 > label.

Sure, but Python already has such a jump table: a function-valued
hash.

Yes, I think it's more befitting than a switch.


On 19 April 2014 08:52, Bruce Leban bruce-at-leapyear.org |python-ideas-at-python..org| <vajg1...@sneakemail.com> wrote:
Here's a simple class that provides case like syntax. How would modifying the language be better than this?

I don't like the additional unneeded Case indentation. That's why I'm proposing a syntax with switch on the same line with the first case.



I think case/elcase would be confusing as it would be unique to python and it's backwards -- the elcase keywords is used for the normal way that people use case statements while the case keyword implements the broken code that inexperienced C programmers frequently write.

This is the point. When I was a beginner I have not tried to code Python directly, since it was an "exotic" language for me; so I read tutorials and docs. In tutorial there will be an example with elcase, and if I have C background I'll think "what damn is elcase"? And I'll read docs more carefully.
And what if I read the code? If I don't know how switch statement works in Python and I read a Python code with elcase, the result will be the same. If I read an example without elcase, since it will work as a C switch statement, there's no possibility of misunderstanding.

The problem with C switch is that you have to write break; at the end of each case suite, and typically you forget to do it. With my proposal you can't forget it if you know the syntax.

Lucas Malor

unread,
Apr 19, 2014, 1:54:50 PM4/19/14
to python...@python.org
On 19 April 2014 06:54, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9k...@sneakemail.com> wrote:
    switch mystring case "spam":
        process(thingy)

That is not even remotely interpretable as an English sentence. That's not a problem for C, but it is for Python.

I'll think about a possible solution.



I don't see how skipping over any elcase but falling through to the next case is in any way simpler than C.
Well, because it's coherent with if-elif. See my last example in my first message.
No it isn't. An elif clause is embedded in an if statement; there is no if clause you can embed on an if statement, just an entirely new and unrelated if statement. 

I can assert, without fear of contradictions, that my proposed syntax is the closest to the if-elif syntax of all the switch statement proposed until now in the age of Python.



About off-topic arguments:


bash does not have either ";;&" or ";&"
 
Who asked you about list comprehensions or lambdas? What do you think they have to do with anything?
[...]

I have no idea why you think recursive functions are relevant to anything being discussed here.
[...]your proposal was to treat tuples specially and match all other iterables as single values, which means that a generator expression would be matched as a single value, meaning it would only match itself. And again, I don't understand what the relevance is supposed to be.

I premise that pattern recognition is outside the scope of my switch proposal. Anyway:
  1. About list comprehensions and lambdas, you talked about assignment statements, and AFAIK the only pattern matching things that are somewhat related to assignment statement are the possibility to filter a list comprehension and the use of lambdas. If you mean something different you should be more explicit, instead of saying "what? where? why?"
  2. About recursive function, I wronged. Sorry but it was late.
  3. About generators, you can create a generator, convert it to an iterable and unpack it in a case_expr. I could also extend the current syntax and support a "generator unpacking". This way you'll have a limited alternative to pattern matching in some cases in a switch statement without the need to create a static iterable.

Bruce Leban

unread,
Apr 19, 2014, 10:17:23 PM4/19/14
to Lucas Malor, Python-Ideas

On Sat, Apr 19, 2014 at 10:42 AM, Lucas Malor <7vsfe...@snkmail.com> wrote:
On 19 April 2014 08:52, Bruce Leban bruce-at-leapyear.org |python-ideas-at-python..org| <vajg1...@sneakemail.com> wrote:
Here's a simple class that provides case like syntax. How would modifying the language be better than this?

I don't like the additional unneeded Case indentation. That's why I'm proposing a syntax with switch on the same line with the first case.

I found that the ugliest part of your proposal since it hides the first case and at the same time makes it appear special when it isn't.

And merely saving indentation is not IMHO a sufficient reason to add a new feature to the language especially since you're not actually saving any. The indentation isn't required with my little Case class. The following works just as well but lacks the visible scoping that the with statement provides (of course the scope of the case variable leaks in either version, but I could modify my class to fail if invoked after __exit__ or __del__).

for i in range(5):
    print(i, end=' => ')
    case = Case(i)
    if case(1):
        print('one')
    elif case((2,3)):
        print('tuple(two, three)')
    elif case(2, 3):
        print('two or three')
    elif case > 3:
        print('more than three')
    else:
        print('unmatched')

João Bernardo

unread,
Apr 20, 2014, 12:30:08 AM4/20/14
to Lucas Malor, Python-Ideas
Python already supports switch...case statements.

with switch(foo) as case:
    with case(10):
        print('foo is 10')
    with case.in_(15, 16, 17):
        print('foo might be 15 or 16 or 17')
    with case.default:
        print('Whatever...')

You just need to implement the switch and case things to match the code above... Also, an if..elif..else block will be simpler to use

Lucas Malor

unread,
Apr 22, 2014, 11:01:38 AM4/22/14
to python...@python.org
On 20 April 2014 04:17, Bruce Leban bruce-at-leapyear.org |python-ideas-at-python.org| <vajg1...@sneakemail.com> wrote:

I found that the ugliest part of your proposal since it hides the first case and at the same time makes it appear special when it isn't.

Myabe yes, but if you "translate" it in an if-elif, also the if seems to be "special".
 

The indentation isn't required with my little Case class. The following works just as well but lacks the visible scoping that the with statement provides (of course the scope of the case variable leaks in either version, but I could modify my class to fail if invoked after __exit__ or __del__)..

for i in range(5):
    print(i, end=' => ')
    case = Case(i)
    if case(1):
        print('one')
    elif case((2,3)):
        print('tuple(two, three)')
    elif case(2, 3):
        print('two or three')
    elif case > 3:
        print('more than three')
    else:
        print('unmatched')

Ok, but you have to nest it inside a for loop. Your class is a good solution, but a new syntax does not need nesting in a with or a for statement.


Chris Angelico

unread,
Apr 22, 2014, 11:15:51 AM4/22/14
to python-ideas
On Wed, Apr 23, 2014 at 1:01 AM, Lucas Malor <7vsfe...@snkmail.com> wrote:
>> for i in range(5):
>> print(i, end=' => ')
>> case = Case(i)
>> if case(1):
>> print('one')
>> elif case((2,3)):
>>
>> print('tuple(two, three)')
>>
>> elif case(2, 3):
>> print('two or three')
>> elif case > 3:
>> print('more than three')
>>
>> else:
>>
>> print('unmatched')
>
>
> Ok, but you have to nest it inside a for loop. Your class is a good
> solution, but a new syntax does not need nesting in a with or a for
> statement.
>

I don't think he *has* to nest it. My reading of the above is that
it's the For-Case Paradigm [1], normally considered an anti-pattern
but viable for demonstrating what happens in each case.

ChrisA

[1] http://thedailywtf.com/Articles/The_FOR-CASE_paradigm.aspx

Lucas Malor

unread,
Apr 22, 2014, 11:27:20 AM4/22/14
to python...@python.org
Sorry, I didn't read it carefully. Anyway, youe example can be written as:

for i in range(5):
    print(i, end=' => ')
    if i == 1:
        print('one')
    elif i == (2,3):
        print('tuple(two, three)')
    elif i in (2, 3):
        print('two or three')
    elif i > 3:
        print('more than three')
    else:
        print('unmatched')

and it's much simpler to read. I suggested the switch statement for a simpler alternative to the if-elif chain.

Lucas Malor

unread,
Apr 22, 2014, 1:37:30 PM4/22/14
to python...@python.org
On 22 April 2014 18:59, Bruce Leban bruce-at-leapyear.org
|python-ideas-at-python.org| <vajg1...@sneakemail.com> wrote:
> My point is that this Case class does what you want with no language
> changes.

This is not correct. As I already said, I _would_ have a switch
statement that is simpler to code than an if-elif chain. On the
contrary your class creates code that is more complicated than an
if-elif chain.

Furthermore your class implements comparators other than "in" and
"==", and this is not my goal. I quote what I wrote before:
> I also thought about a syntax like this:
> "case" comparator case_expr ":"
> but IMHO it's too verbose for the typical uses of switch. If you want
> flexibility, you can always use if-elif.

And about simplicity, if I sacrifice the fallback and make the "break"
the default and unique behaviour, I don't need "elcase" and my syntax
will be more easy than before:

switch tarot case 0:
card = "Fool"
case 1:
card = "Alan Moore"
case 2:
card = "High Priestess"
<etc....>

I think this is less complicated to read and it's more practical,
since usually you want to "break". If you don't want to "break", you
can create another switch.
Probably I have to not use "switch" and "case", since it seems a C
switch, while its behaviour is completely different now (C "case"
falls back by default and can break, while this "case" breaks by
default and can't fall back). Maybe something like dispatch-case, or
inspect-case.

Andrew Barnert

unread,
Apr 22, 2014, 1:39:32 PM4/22/14
to Lucas Malor, python...@python.org
On Apr 22, 2014, at 8:01, "Lucas Malor" <7vsfe...@snkmail.com> wrote:

On 20 April 2014 04:17, Bruce Leban bruce-at-leapyear.org |python-ideas-at-python.org<vajg1...@sneakemail.com> wrote:

I found that the ugliest part of your proposal since it hides the first case and at the same time makes it appear special when it isn't.

Myabe yes, but if you "translate" it in an if-elif, also the if seems to be "special".

Yes, there is a difference, but it's not nearly as dramatic as with your version. The conditions still look roughly parallel, only one short word away from the indent point, instead of the first one being way off to the right.

On Apr 22, 2014, at 8:27, "Lucas Malor" <7vsfe...@snkmail.com> wrote:

Sorry, I didn't read it carefully. Anyway, youe example can be written as:

for i in range(5):
    print(i, end=' => ')
    if i == 1:
        print('one')
    elif i == (2,3):
        print('tuple(two, three)')
    elif i in (2, 3):
        print('two or three')
    elif i > 3:
        print('more than three')
    else:
        print('unmatched')

and it's much simpler to read.

That's pretty much his point. The same can be said for all of your examples.

Your version is only "simpler" in that your "case" and "elcase" are spelled "if case" and "elif case" in his. Meanwhile, your version is more complex in that the first case is crammed on the same line with the "switch". Also, instead of automatically handling tuples in a natural and obvious way, you're forced to invent a new builtin class and a new display form just to distinguish tuples inline in a case label, and still can't easily handle the distinction in a natural and readable way. And, while his can be trivially extended to handle other types of comparison besides == and in, yours can't. So, you've come up with something that's more complex, less natural, and less powerful than what we can already do, and the only benefit is a little bit of brevity.

Andrew Barnert

unread,
Apr 22, 2014, 1:56:42 PM4/22/14
to Lucas Malor, python...@python.org
On Apr 19, 2014, at 10:54, "Lucas Malor" <7vsfe...@snkmail.com> wrote:

On 19 April 2014 06:54, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9k...@sneakemail.com> wrote:
    switch mystring case "spam":
        process(thingy)

That is not even remotely interpretable as an English sentence. That's not a problem for C, but it is for Python.

I'll think about a possible solution.


I don't see how skipping over any elcase but falling through to the next case is in any way simpler than C.
Well, because it's coherent with if-elif. See my last example in my first message.
No it isn't. An elif clause is embedded in an if statement; there is no if clause you can embed on an if statement, just an entirely new and unrelated if statement. 

I can assert, without fear of contradictions, that my proposed syntax is the closest to the if-elif syntax of all the switch statement proposed until now in the age of Python.

So what? All of those other syntaxes were rejected, and not because they weren't close enough to if-elif syntax.

And besides, that's not actually true. Look at how if statements are defined in the grammar. You've attempted to match the syntax of a sequence of separate if statements within a single statement.


About off-topic arguments:


bash does not have either ";;&" or ";&"
 

OK, I didn't know about this GNU extension. But it still doesn't answer any of my points. They are not the same as C break and continue, not are they the same as your proposed semantics. I won't repeat all the stuff you snipped out and didn't reply to, just the big one: you've cited bash and C as justification for your feature of skipping over some cases but then continuing to check others, but neither C not bash has any way to do that at all, much less a way that's similar to yours.

Who asked you about list comprehensions or lambdas? What do you think they have to do with anything?
[...]
I have no idea why you think recursive functions are relevant to anything being discussed here.
[...]your proposal was to treat tuples specially and match all other iterables as single values, which means that a generator expression would be matched as a single value, meaning it would only match itself. And again, I don't understand what the relevance is supposed to be.

I premise that pattern recognition is outside the scope of my switch proposal.

I already explained why it isn't. If you're looking to not just bring a case statement to Python, but invent a unique and more powerful one than any that have come before, you need to actually address what's come before. Especially since your syntax, by treating tuples specially, would prevent any future extensions along these lines, you need to explain why the multiple cases feature is more important than the pattern matching feature.

Anyway:
  1. About list comprehensions and lambdas, you talked about assignment statements, and AFAIK the only pattern matching things that are somewhat related to assignment statement are the possibility to filter a list comprehension and the use of lambdas. If you mean something different you should be more explicit, instead of saying "what? where? why?"
I still don't understand how you think filtering a list comprehension or using lambdas have anything to do with pattern matching, or what they have to do with assignment statements. Since you insist that you read up on what pattern matching means, I have no idea how to answer that.

Especiallly since I already showed you exactly what I mean:

    x, y = expression()

This requires the expression to return an iterable of exactly two values, and assigns those two values to x and y, in a way that's a analogous to (a duck-typed version of) ML-style pattern matching syntax.
  1. About recursive function, I wronged. Sorry but it was late.
  2. About generators, you can create a generator, convert it to an iterable and unpack it in a case_expr.
I already explained that a generator is already an iterable and doesn't need to be converted into one. Your proposed syntax doesn't have any way to unpack iterables, and I'm not sure what it would do if you added such a thing (especially since you want to treat inline tuple displays specially, but not normal tuples, much less other iterables or sequences).

I could also extend the current syntax and support a "generator unpacking". This way you'll have a limited alternative to pattern matching in some cases in a switch statement without the need to create a static iterable.

How does this provide an alternative to pattern matching? Show an example of what you mean.

Bruce Leban

unread,
Apr 22, 2014, 12:59:10 PM4/22/14
to Lucas Malor, Python-Ideas


On Apr 22, 2014 8:28 AM, "Lucas Malor" <7vsfe...@snkmail.com> wrote:
>
> Sorry, I didn't read it carefully. Anyway, youe example can be written as:
>
>> for i in range(5):
>>     print(i, end=' => ')
>>     if i == 1:
>>         print('one')
>>     elif i == (2,3):
>>         print('tuple(two, three)')
>>     elif i in (2, 3):
>>         print('two or three')
>>     elif i > 3:
>>         print('more than three')
>>     else:
>>         print('unmatched')
>
> and it's much simpler to read. I suggested the switch statement for a simpler alternative to the if-elif chain.

<sigh> My code can be written as a series of static print statements too and that would be even easier to read. It's an *example* to illustrate use not actual useful code!

My point is that this Case class does what you want with no language changes. And looking at your sample above exactly how would your new switch statement be better than either my code or your if/elif chain?

--- Bruce
(from my phone)

David Mertz

unread,
Apr 22, 2014, 3:35:23 PM4/22/14
to Lucas Malor, python-ideas
On Tue, Apr 22, 2014 at 10:37 AM, Lucas Malor <7vsfe...@snkmail.com> wrote:
switch tarot case 0:
    card = "Fool"
case 1:
    card = "Alan Moore"
case 2:
    card = "High Priestess"
<etc....>

-100.

I have to say "yuck" to this sort of thing; it feels entirely unpythonic. Each 'case' is some sort of comparison, but you can't tell locally what is being compared.  Of course in the simple one-line blocks it's easy enough to glance up to the 'switch', but if you had 20-30 lines of code within each 'case' you could look at 'case 20' and not remember/know what is being switched on.

Using the Case class idea seems much more clear, as well as being much more flexible.  It's true the initialization of the instance might be a ways up, but at least you know that it's an argument passed to a callable, so you already *know*  that you'd have to look at the creation of that callable.  Moreover, being generalizable is nice, e.g. (with a slightly enhanced class, implementation left to the reader):

    case = Case(my_color(some_complex_expression(this, that, other) + more_stuff(foo, bar))
    if case('red'):
        some_red_stuff()
    elif case.in('green', 'blue'):
        some_bluegreen_stuff()
    elif '#00AAAA' < case < '#EEBBAA':  # some definition of color range
        something_with_color_range()
    else:
        paint_it_black()

Of course, in my example, maybe a better name for the 'case' instance is actually 'color'.  The fact I can choose a name that fits the context adds even more clarity over all "cases" needing to be named 'case'.
 


--
Keeping medicines from the bloodstreams of the sick; food
from the bellies of the hungry; books from the hands of the
uneducated; technology from the underdeveloped; and putting
advocates of freedom in prisons.  Intellectual property is
to the 21st century what the slave trade was to the 16th.

MRAB

unread,
Apr 22, 2014, 3:44:34 PM4/22/14
to python...@python.org
On 2014-04-22 18:39, Andrew Barnert wrote:
> On Apr 22, 2014, at 8:01, "Lucas Malor" <7vsfe...@snkmail.com
> <mailto:7vsfe...@snkmail.com>> wrote:
>
>> On 20 April 2014 04:17, Bruce Leban bruce-at-leapyear.org
>> <http://bruce-at-leapyear.org> |python-ideas-at-python.org
>> <http://python-ideas-at-python.org>| <vajg1...@sneakemail.com
>> <mailto:vajg1...@sneakemail.com>> wrote:
>>
>>
>> I found that the ugliest part of your proposal since it hides the
>> first case and at the same time makes it appear special when it isn't.
>>
>>
>> Myabe yes, but if you "translate" it in an if-elif, also the if seems
>> to be "special".
>
> Yes, there is a difference, but it's not nearly as dramatic as with your
> version. The conditions still look roughly parallel, only one short word
> away from the indent point, instead of the first one being way off to
> the right.
>
> On Apr 22, 2014, at 8:27, "Lucas Malor" <7vsfe...@snkmail.com
[snip]
How about a slightly modified 'if' statement:

for i in range(5):
print(i, end=' => ')
case if i == 1:
print('one')
elif (2, 3): # Same as elif == (2, 3)
print('tuple(two, three)')
elif in (2, 3):
print('two or three')
elif > 3:
print('more than three')
elif is None:
print('None')
elif == 0: # Or just elif 0
print('zero')
else:
print('unmatched')

The 'case' says that you want to test the value of part preceding the
comparator, in this case 'i'.

In the 'elif' conditions, '==' is assumed unless you say otherwise.

Devin Jeanpierre

unread,
Apr 22, 2014, 6:06:55 PM4/22/14
to Andrew Barnert, python...@python.org
On Fri, Apr 18, 2014 at 9:54 PM, Andrew Barnert
<abar...@yahoo.com.dmarc.invalid> wrote:
> Meanwhile, ML and friends let you go farther, matching by partial values:
>
> of (True, y):
> process(y)
> of Eggs(z):
> fry(z)
>
> The first matches only if it's a tuple of exactly two values and the first
> is equal to True, binding y to the second; the second matches only if it's
> equal to an Eggs instance constructed with exactly one argument, and binds z
> to that argument.
>
> Clearly not all of this fits into Python. (The last example isn't even
> conceptually possible, given the way object initialization works.) But it's
> worth considering what does and what doesn't fit rather than trying to
> design a feature without even knowing what the options are.

I'm really glad you brought this up, and it isn't conceptually
impossible. Eggs(x) inside a pattern probably won't actually
initialize an Eggs object; it probably would create a pattern object
instead, and the value being matched can decide whether or not it
obeys that pattern, and (if so) what object should be assigned to z.

Why is it that every time someone brings up switch/case, they're being
inspired by C and Java instead of ML or Haskell? People are looking
backwards instead of forwards. Python doesn't have a nice way of
pattern matching on tagged unions in general, not just C-style enums
-- and it really ought to. I think pattern matching could fit in
Python, where switch doesn't really. Switch replaces if/elif, pattern
matching replaces the if/elif plus any work unpacking contents. The
format of a pattern match encourages code to be written in a way that
disjointly separates groups of disparate values, in a way that isn't
difficult and doesn't involve weird type checks or whatever.

Looking at the difference in e.g. AST manipulation code with/without
is a nice example of how code can be made more readable. And looking
at the problems Python code has today with this kind of thing is also
illustrative -- for example, how do you tell the difference between no
result, and a result whose value is None? In some APIs, you can't (!),
in some, you need to specify a special sentinel object and check
identity (ew), and in some, no result is represented by an exception
-- which is fine, but in some cases very error prone (an uncaught
StopIteration, for example, will terminate any generator that calls
you, which can be difficult to notice.) Pattern matching offers a
real alternative option here that wasn't realistic before.

But, maybe this is one of those things that won't happen because
Haskell licked the cookie and now everyone thinks it's gross and
unreadable, and can't be convinced otherwise. I'm more worried that
people don't even know this exists...

-- Devin

Oleg Broytman

unread,
Apr 22, 2014, 6:20:50 PM4/22/14
to python...@python.org
On Tue, Apr 22, 2014 at 03:06:55PM -0700, Devin Jeanpierre <jeanpi...@gmail.com> wrote:
> Why is it that every time someone brings up switch/case, they're being
> inspired by C and Java instead of ML or Haskell?

How many people know imperative, procedural and OO languages? And how
many know functional or logic-based languages?

Oleg.
--
Oleg Broytman http://phdru.name/ p...@phdru.name
Programmers don't die, they just GOSUB without RETURN.

Andrew Barnert

unread,
Apr 22, 2014, 7:20:15 PM4/22/14
to Devin Jeanpierre, python...@python.org
On Apr 22, 2014, at 15:06, Devin Jeanpierre <jeanpi...@gmail.com> wrote:

> On Fri, Apr 18, 2014 at 9:54 PM, Andrew Barnert
> <abar...@yahoo.com.dmarc.invalid> wrote:
>> Meanwhile, ML and friends let you go farther, matching by partial values:
>>
>> of (True, y):
>> process(y)
>> of Eggs(z):
>> fry(z)
>>
>> The first matches only if it's a tuple of exactly two values and the first
>> is equal to True, binding y to the second; the second matches only if it's
>> equal to an Eggs instance constructed with exactly one argument, and binds z
>> to that argument.
>>
>> Clearly not all of this fits into Python. (The last example isn't even
>> conceptually possible, given the way object initialization works.) But it's
>> worth considering what does and what doesn't fit rather than trying to
>> design a feature without even knowing what the options are.
>
> I'm really glad you brought this up, and it isn't conceptually
> impossible. Eggs(x) inside a pattern probably won't actually
> initialize an Eggs object; it probably would create a pattern object
> instead, and the value being matched can decide whether or not it
> obeys that pattern, and (if so) what object should be assigned to z.

That's a promising idea that I'd love to see fleshed out. Forgive me for dumping a whole lot of questions on you as I think this through...

First, does this really need to be restricted to case statements, or can constructor patterns just become another kind of target, which can be used anywhere (most importantly, assignment statements), and then cases just use the same targets? (Notice the potential parallels--and also differences--with __setattr__ and __setitem__ type assignments, not just tuple-style assignments.)

I can see how this could work for complete matching. Let's say I assign spam = Spam(2, 3), and I later try to match that against Spam(x, y) where x and y are not bound names. So, Python calls spam.__match__(Spam, 'x', 'y'), which returns {'x': 2, 'y': 3}, so the case matches and x and y get locally bound to 2 and 3.

But what about partial matching? What if I want to match against Spam(2, y)? If you want that to work with literals, it might be possible. (Note that "2, y = eggs" is caught by the early stages of the compiler today, because the grammar doesn't allow literals in assignment targets.) Maybe something like spam.__match__(Spam, (2, True), ('y', False))? That's not conceptually too hard, but it would get really ugly inside the __match__ method of every class, unless you had some good types and helper functions similar to what you have in the opposite direction with inspect.Signature.bind.

If you want partial matching with dynamic values, however, it's a lot harder. You can't put expressions in target lists today, for obvious reasons (they have a partially parallel but simpler grammar), and targets are never treated as values directly. In particular, "x = 2" doesn't raise a NoItIsntError if x is already bound to 1, it just rebinds it. This means that if we do have partial patterns, they may still not be the same as (or as powerful as) you'd like. For example, if matching Spam(x, y) always matches and binds x as a name, rather than partially matching on its value (unless you can think of some syntax for that?) it's a lot harder to write dynamic patterns. (You can probably do something with partial, but that just makes matching even more complicated and leaves the syntax for using it in these cases unfriendly.)

Also, keep in mind that tuple-like targets always expand an iterable into a list, so "x, *y = range(3)" matches 0 and [1, 2], and "x, *y = itertools.count()" raises a MemoryError, so you're never going to have the full power of Haskell patterns here.

Beyond that, is there anything else to add to target lists besides call-like forms to match constructor patterns, or is that sufficient for everything you need from pattern matching?

> Why is it that every time someone brings up switch/case, they're being
> inspired by C and Java instead of ML or Haskell? People are looking
> backwards instead of forwards.

I agree, but it's certainly more complex, and harder to fit into Python. Thanks for showing me that it's not actually intractable, though...

> Python doesn't have a nice way of
> pattern matching on tagged unions in general, not just C-style enums
> -- and it really ought to. I think pattern matching could fit in
> Python, where switch doesn't really. Switch replaces if/elif, pattern
> matching replaces the if/elif plus any work unpacking contents. The
> format of a pattern match encourages code to be written in a way that
> disjointly separates groups of disparate values, in a way that isn't
> difficult and doesn't involve weird type checks or whatever.
>
> Looking at the difference in e.g. AST manipulation code with/without
> is a nice example of how code can be made more readable. And looking
> at the problems Python code has today with this kind of thing is also
> illustrative -- for example, how do you tell the difference between no
> result, and a result whose value is None? In some APIs, you can't (!),
> in some, you need to specify a special sentinel object and check
> identity (ew), and in some, no result is represented by an exception
> -- which is fine, but in some cases very error prone (an uncaught
> StopIteration, for example, will terminate any generator that calls
> you, which can be difficult to notice.) Pattern matching offers a
> real alternative option here that wasn't realistic before.

I think coming up with just enough of a proposal to then show how you could rewrite some existing AST (or DOM?) manipulation code, might make the case a lot better than trying to explain it.

If you're not up for doing that, I might try to tackle it, but it's your idea and you're the one with use cases in his head.

> But, maybe this is one of those things that won't happen because
> Haskell licked the cookie and now everyone thinks it's gross and
> unreadable, and can't be convinced otherwise. I'm more worried that
> people don't even know this exists...

The main objection to bringing in declarative features from functional languages, especially Haskell, is that it often forces people to read things at a higher level of abstraction. That isn't true for all such features (list comprehensions, anyone?), but it's a default assumption you have to overcome.

I think you can overcome it here; reading the actual __match__ methods is one thing, but the (presumably much more common, especially in novice code) uses of case statements or other matching targets can be just as explicit and concrete as if-based switching code, and a lot more readable. We just need to make that case for the specific feature strongly enough to overcome the general presumption. I think your AST example would go a long way toward doing that.

Andrew Barnert

unread,
Apr 22, 2014, 7:29:02 PM4/22/14
to Oleg Broytman, python...@python.org
On Apr 22, 2014, at 15:20, Oleg Broytman <p...@phdru.name> wrote:

> On Tue, Apr 22, 2014 at 03:06:55PM -0700, Devin Jeanpierre <jeanpi...@gmail.com> wrote:
>> Why is it that every time someone brings up switch/case, they're being
>> inspired by C and Java instead of ML or Haskell?
>
> How many people know imperative, procedural and OO languages? And how
> many know functional or logic-based languages?

I think most people know multi-paradigm languages. C++ has similar features, even if many of them are restricted to it's compile-time template engine. Most JavaScript programs are all about passing anonymous functions to higher-order functions as callbacks, of abstractions on top of that like futures. And so on.

Anyway, people don't have a problem learning map, any, comprehensions, unpacking, or other functional-derived features in Python even if they come from C; in fact, those features are often why they don't want to go back to C. The issue is always finding the right balance, borrowing features that fit into the language well and make code easier to read but not trying to encourage recursive folds over loops or rebuild I/O around monads or pure but nondeterministic functions.

Haoyi Li

unread,
Apr 22, 2014, 7:38:16 PM4/22/14
to Andrew Barnert, python...@python.org
Fun fact: we've had pattern matching on 2.7 using MacroPy for a year now


@case
class Nil():
    pass

@case
class Cons(x, xs):
    pass

def reduce(op, my_list):
    with switch(my_list):
        if Cons(x, Nil()):
            return x
        elif Cons(x, xs):
            return op(x, reduce(op, xs))

Devin Jeanpierre

unread,
Apr 22, 2014, 11:58:46 PM4/22/14
to python...@python.org
On Tue, Apr 22, 2014 at 4:20 PM, Andrew Barnert <abar...@yahoo.com> wrote:
> On Apr 22, 2014, at 15:06, Devin Jeanpierre <jeanpi...@gmail.com> wrote:
>> I'm really glad you brought this up, and it isn't conceptually
>> impossible. Eggs(x) inside a pattern probably won't actually
>> initialize an Eggs object; it probably would create a pattern object
>> instead, and the value being matched can decide whether or not it
>> obeys that pattern, and (if so) what object should be assigned to z.
>
> That's a promising idea that I'd love to see fleshed out. Forgive me for dumping a whole lot of questions on you as I think this through...
>
> First, does this really need to be restricted to case statements, or can constructor patterns just become another kind of target, which can be used anywhere (most importantly, assignment statements), and then cases just use the same targets? (Notice the potential parallels--and also differences--with __setattr__ and __setitem__ type assignments, not just tuple-style assignments.)

Making assignment statements do a lot of work would slow down Python
way too much. The restricted sort of destructuring bind python already
does is sufficiently limited that the compiler can do all the heavy
lifting.

> I can see how this could work for complete matching. Let's say I assign spam = Spam(2, 3), and I later try to match that against Spam(x, y) where x and y are not bound names. So, Python calls spam.__match__(Spam, 'x', 'y'), which returns {'x': 2, 'y': 3}, so the case matches and x and y get locally bound to 2 and 3.
>
> But what about partial matching? What if I want to match against Spam(2, y)? If you want that to work with literals, it might be possible. (Note that "2, y = eggs" is caught by the early stages of the compiler today, because the grammar doesn't allow literals in assignment targets.) Maybe something like spam.__match__(Spam, (2, True), ('y', False))? That's not conceptually too hard, but it would get really ugly inside the __match__ method of every class, unless you had some good types and helper functions similar to what you have in the opposite direction with inspect.Signature.bind.

That's a bit complicated, and another example of when a simpleish API
is made hard without nice union types. Why not use pattern matching?
:)

spam.__match__(ConstructorPattern(Spam, Var('x'), Var('y'))) and
spam.__match__(ConstructorPattern(Spam, Literal(2), Var('y')))

in __match__ you can check "match pat: case ConstructorPattern(t,
Var(a), Var(b)): if t == Spam: ...", etc. (This is not cyclic; the
base cases are the __match__ for ConstructorPattern, Var, and Literal,
which can do some difficult manual work instead).

In most cases people would not be writing __match__ by hand, so it
doesn't need to be easy to write. You'd probably be defining matchable
cases using something like what Haoyi Li wrote, except with a
namedtuple-analogue instead of AST macros:

Nil = constructor('Nil', ())
Cons = constructor('Cons', ('x', 'xs'))
value = Cons('hello world', Nil())
match value:
case Cons(a, Nil()):
print(a) # a == value.x == 'hello world'


> If you want partial matching with dynamic values, however, it's a lot harder. You can't put expressions in target lists today, for obvious reasons (they have a partially parallel but simpler grammar), and targets are never treated as values directly. In particular, "x = 2" doesn't raise a NoItIsntError if x is already bound to 1, it just rebinds it. This means that if we do have partial patterns, they may still not be the same as (or as powerful as) you'd like. For example, if matching Spam(x, y) always matches and binds x as a name, rather than partially matching on its value (unless you can think of some syntax for that?) it's a lot harder to write dynamic patterns. (You can probably do something with partial, but that just makes matching even more complicated and leaves the syntax for using it in these cases unfriendly.)

I am not sure what you're getting at here, exactly. x should never be
asked if it's OK to do x = 2. If you want to use variables as values
to compare with, rather than targets to assign to, usually that's done
by adding a conditional. Although, Racket has a match extension for
using a variable to check for equality in a pattern match, like so:

> (define (check_eq x y)
(match x
[ (== y) (displayln "x == y")]
[ _ (displayln "x != y")]))
> (check_eq 1 1)
x == y
> (check_eq 1 2)
x != y

One could add such a thing (maybe $x instead of (== x) ? ;O), or use
guard clauses, or just use a nested if.

> Also, keep in mind that tuple-like targets always expand an iterable into a list, so "x, *y = range(3)" matches 0 and [1, 2], and "x, *y = itertools.count()" raises a MemoryError, so you're never going to have the full power of Haskell patterns here.
>
> Beyond that, is there anything else to add to target lists besides call-like forms to match constructor patterns, or is that sufficient for everything you need from pattern matching?

You haven't presented an organized list :(

I think a good set of features is:

- variables (e.g. matching against x)
- constructor/term patterns (e.g. matching against foo(a, b))
- literals (e.g. matching against (a, b) or even (a, 2))
- repetition operator (e.g. matching against *a) # nonessential, but powerful
- combining patterns (e.g. match against foo(a) or bar(a, 2)) # convenient
- binding subpatterns to names (e.g. match against foo(bar(z) as x);
now x equals the thing that matched bar(z)) # convenient

And take the transitive closure of combining those operations. That's
a pretty big wishlist, with implications that maybe would weird people
out, so I won't explain the details. They aren't important.

> I think coming up with just enough of a proposal to then show how you could rewrite some existing AST (or DOM?) manipulation code, might make the case a lot better than trying to explain it.

I doubt such a proposal would be accepted, but I am OK writing what I
did above and showing an example.

compare http://hg.python.org/cpython/file/v2.7.3/Lib/ast.py#l52 with:

def _convert(node):
match node:
case Str(v) or Num(v):
return v
case Tuple(vs):
return tuple(map(_convert, vs))
case List(vs):
return list(map(_convert, vs))
case Dict(_):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
case Name(id):
if id in _safe_names:
return _safe_names[id]
case BinOp(Num(left), op, Num(right)):
if isinstance(left, (int,long,float)) and
isinstance(right, (int,long,float))):
match op:
case Add():
return left + right
case Sub():
return left - right
case _: pass # presumably failure to match is an error? idk

raise ValueError('malformed string')

This is definitely more readable, in particular in the BinOp case. And
you'll note that a mere switch statement really isn't very helpful. ;)

-- Devin

Devin Jeanpierre

unread,
Apr 23, 2014, 2:13:44 AM4/23/14
to python...@python.org
Sorry, this is unsound / doesn't specify where the recursion is
allowed to happen. Instead, have __match__ accept a constructor and a
list of identifiers and return a dict mapping identifiers to values --
it never sees anything other than variables in the constructor
parameters. (No literals.)

If you try to match foo with Spam(2, y), then __match__ gets Spam,
generated_variable_12345, and y. When it fills in
generated_variable_12345, the thing it assigns to that variable is
subsequently pattern matched against 2 (probably using __eq__ instead
of an overloaded __match__).

Stupid mistake I make a lot when I'm not thinking about recursive
algorithms... :(

-- Devin

Phil Connell

unread,
Apr 23, 2014, 4:38:03 AM4/23/14
to python...@python.org
This looks promising :)


I see two possibilities for the match protocol:

1. Have a single __match__ classmethod that is responsible for 'binding'
unbound values, and work out whether there is a match *outside* of the
__match__ method (using __eq__).

2. Split the protocol into two methods:
__match_check__, responsible for checking whether there is a match.
__match_unpack__, responsible for extracting the match parameters of an
object.


I've got a strong suspicion that #2 would turn out to be the better solution:

- It should be more efficient: __match_check__ can short-circuit after the
first non-matching parameter, whereas a standalone __match__ would have to
unpack/bind all parameters.

- It allows greater flexibility, for example fuzzy matching: is the value in an
allowed range?

- I *think* the methods would probably have simpler implementations (ultimate
proof needs code :)


The protocol methods would look something like this:

class Spam:
@classmethod
def __match_check__(cls, other, a, b, c):
...

@classmethod
def __match_unpack__(cls, other):


Used something like this:

match foo:
case Spam(1, 2, 3):
...
case Spam(42, x, y):
...


Semantics of __match_check__:
- 'other' is the object being matched (whatever 'foo' refers to).
- a, b, c come directly from 'case Spam(...)', i.e. 1, 2, 3 in the first
case.
- If some of the args are 'unbound' the corresponding parameters refer to a
new Unbound singleton (better name?) , i.e. in the second case a=42,
b=Unbound, c=Unbound.
- Should return True if 'other' matches given the parameters' values, False
otherwise.

Semantics of __match_unpack__:
- 'other' is the object being matched, again.
- The return signature is less obvious. Discussion below.
- From the returned value, unbound parameters are filled in (i.e. x and y
in the second case).


Options for the return value of __match_unpack__:

A. Simplest case would be a tuple, something like (other.a, other.b, other.c)
in the example.

B. More flexible would be a pair (values, kwvalues) where 'values' is an
iterable of 'positional' matchable values, and 'kwvalues' is a dict of
'keyword' matchable values.

This implies a more complex case syntax, e.g.
case Eggs(a as x)

would extract the kwvalue 'a' and bind it to the name 'x' in the case block.
Not sure this is worth it.

C. Have __match_unpack__ accept *args, filled in with strings giving the names
of matchable parameters to be unpacked?

(Not convinced this is a good idea :)


Finally, one thing that's worth noting: this only needs language support to
allow 'unbound' values in case statements.

It should be completely possible to implement this as a third party module with
a couple of differences:

- 'case Spam(x, y)' would need to be spelt
'elif case(Spam(Var("x"), Var("y")))'

- The match/case syntactic sugar would obviously not work ('with Case() as
case' etc needed instead).



Cheers,
Phil

Philipp A.

unread,
Apr 26, 2014, 9:03:32 AM4/26/14
to Phil Connell, Python-Ideas

what y’all are missing about switch/case is that it’s not necessarily a series of comparisons in most languages.

in essence, it’s a hashmap lookup with (in some languages) optional comparisons.

in python, the most similar thing is something like this:

def branch_2_3():
    spam(1)
    spam(6)

{
    'key1': lambda: eggs += lay_eggs(), #branch 1
    'key2': branch_2_3,
    'key3': branch_2_3,
}[value]()

which is of course completely ugly and unnecessarily complicated for cases where multiple keys lead to the same branch.

Chris Angelico

unread,
Apr 26, 2014, 9:27:47 AM4/26/14
to Python-Ideas
On Sat, Apr 26, 2014 at 11:03 PM, Philipp A. <flying...@web.de> wrote:
> in python, the most similar thing is something like this:
>
> 'key1': lambda: eggs += lay_eggs(), #branch 1

Except that lambda doesn't create a code block, it creates a nested
function. This example won't work, and nor will anything else that
needs to mutate locals; you'd have to declare an out-of-line function
(as with branch_2_3) to be able to assign to 'nonlocal eggs'.

ChrisA

Philipp A.

unread,
Apr 26, 2014, 9:43:27 AM4/26/14
to Chris Angelico, Python-Ideas
sure it works if `eggs` has a `__iadd__` method. why shouldn’t it use the outer local?

but that’s irrelevant. as i said: what i did is closest thing. yet in some other respects, if/else is closer, as there is no real alternative to a switch statement in python.

my only point is that apart from being cleaner, a switch block also behaves differently than a if/else chain, and performs better.

Chris Angelico

unread,
Apr 26, 2014, 9:54:19 AM4/26/14
to Python-Ideas
On Sat, Apr 26, 2014 at 11:43 PM, Philipp A. <flying...@web.de> wrote:
> sure it works if `eggs` has a `__iadd__` method. why shouldn’t it use the
> outer local?

1) Operator precedence gets in the way. (Easily fixed.)
>>> lambda: eggs += lay_eggs()
SyntaxError: can't assign to lambda

2) Assignment is a statement, so you can't do it in a lambda.
>>> lambda: (eggs += lay_eggs())
SyntaxError: invalid syntax

3) Assignment makes it local, so you'll get UnboundLocalError if you
don't declare it nonlocal.
>>> def f():
eggs += lay_eggs()
>>> f()
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
f()
File "<pyshell#4>", line 2, in f
eggs += lay_eggs()
UnboundLocalError: local variable 'eggs' referenced before assignment

Hence my previous statement that you need to write the function out of
line, which breaks the switch-ness of the block, and you definitely
need to declare eggs nonlocal. Now, if eggs is a list, you can extend
it with a method (which can be called in an expression), but if your
switch block can only do expressions, it's pretty limited.

Philipp A.

unread,
Apr 26, 2014, 10:44:51 AM4/26/14
to Chris Angelico, Python-Ideas

interesting. it’s still assignment, even if nothing gets assigned (and only __iadd__ gets called behind the scenes).

Ron Adam

unread,
Apr 26, 2014, 10:58:27 AM4/26/14
to python...@python.org

Another difference is a typical switch hash map is usually built at compile
time in other languages. In python, the example's dict would be built
every time it executes. That can be an advantage at times, but it can also
make it slower than an if-elif's Moving the dict (and function
definitions) to a place where it's only initialised once, may mean the
names it access may not be reachable, so in most cases any values it
depends on need to be passed as well.

Ron

Stephen J. Turnbull

unread,
Apr 26, 2014, 11:07:56 AM4/26/14
to Philipp A., Python-Ideas
"Philipp A." <flying...@web.de> wrote:

> what y’all are missing about switch/case is that it’s *not*


> necessarily a series of comparisons in most languages.

I don't think anybody missed this. It's certainly been mentioned
several times.

Of course looking at a specific, full-featured example implementing
the idea of "switch on a value" using (and abusing, as pointed out by
Chris) existing Python features leads to the conclusion that such an
implementation

> is of course completely ugly and unnecessarily complicated for
> cases where multiple keys lead to the same branch.

I can't imagine anybody has missed this. It is presumably one reason
why the OP is requesting new syntax. This also has a more fundamental
flaw than "ugly" in many eyes: the need to define behavior *before*
defining the case.

The reason why people keep coming back to a series of comparisons
(actually more general: a series of branch on tests) is that a series
of comparisons is behaviorally equivalent to a switch. Written in
appropriate style it's obvious that it is intended to have the effect
of a switch. The code implementing a case is given "naturally" after
mentioning the case. Given all that, it seems unnecessary to add new
syntax.

However, it's been mentioned that *if* we're going to have a case
statement, it would be nice to give it "pattern matching" abilities
such as the globbing of sh's 'case' or Haskell's signature matching.
That power might be enough to get some people to swing over to
supporting a syntax addition, it seems.

Starting from that summary of the thread, what are you trying to
argue?

Steven D'Aprano

unread,
Apr 26, 2014, 11:17:23 AM4/26/14
to python...@python.org
On Sat, Apr 26, 2014 at 03:03:32PM +0200, Philipp A. wrote:
> what y’all are missing about switch/case is that it’s *not* necessarily a

> series of comparisons in most languages.

*Some* people might be missing that, but not *all* of us :-)

Before commenting further, anyone who isn't familiar with the two
rejected PEPs on this topic should read them:

http://legacy.python.org/dev/peps/pep-0275/
http://legacy.python.org/dev/peps/pep-3103/


Also, folks might like to review the wide range of semantics and syntax
for switch/case in different languages:

http://rosettacode.org/wiki/Conditional_Structures

By my count, there are:

50 languages with no case/switch at all
30 languages using the keyword "case"
19 using "switch"
15 using "select"
3 using "when"
2 using "match"
2 using "given" (treating Perl 5 and 6 as distinct languages)
1 using "evaluate"
1 using "caseof"
1 using "if" (with a switch-like syntax)

The above counts should be mostly accurate, but I don't guarantee that
they are exactly correct. Apart from the possibility of counting errors,
there were a few cases where I had to make a judgement call on whether
or not language feature X was analogous to a switch or not.

--
Steven

Philipp A.

unread,
Apr 26, 2014, 1:44:23 PM4/26/14
to Stephen J. Turnbull, Python-Ideas
2014-04-26 17:07 GMT+02:00 Stephen J. Turnbull <ste...@xemacs.org>:
The reason why people keep coming back to a series of comparisons
(actually more general: a series of branch on tests) is that a series
of comparisons is behaviorally equivalent to a switch.  Written in
appropriate style it's obvious that it is intended to have the effect
of a switch.  The code implementing a case is given "naturally" after
mentioning the case.  Given all that, it seems unnecessary to add new
syntax.

Starting from that summary of the thread, what are you trying to
argue?

only that all the discussion about series of comparisons don’t paint the full picture; that the Case class isn’t enough.

Philipp A.

unread,
Apr 26, 2014, 1:52:29 PM4/26/14
to Steven D'Aprano, Python-Ideas
2014-04-26 17:17 GMT+02:00 Steven D'Aprano <st...@pearwood.info>:
Also, folks might like to review the wide range of semantics and syntax
for switch/case in different languages:

http://rosettacode.org/wiki/Conditional_Structures

you’re missing the language calling this “pattern matching”: http://rosettacode.org/wiki/Pattern_matching

i especially like scala’s way of doing it (even while recognizing that it’s no good fit for python)

Mark Lawrence

unread,
Apr 26, 2014, 1:59:55 PM4/26/14