Re: [Python-ideas] Yet Another Switch-Case Syntax Proposal
On 18 April 2014 16:58, Ed Kellett edk141-at-gmail.com | python-ideas-at-python.org| <baswps8ght@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| <0ucjz7dbjt@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| <3en9kh2cbt@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
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| <0ucjz7dbjt@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
On 19 April 2014 13:12, Steven D'Aprano <steve@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 <aj@erisian.com.au>
On 19 April 2014 05:12, Steven D'Aprano steve-at-pearwood.info | python-ideas-at-python.org| <6hqclkasyt@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| <85q573xayt@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| <vajg1g2cqt@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.
On Sat, Apr 19, 2014 at 10:42 AM, Lucas Malor <7vsfeu4pxg@snkmail.com>wrote:
On 19 April 2014 08:52, Bruce Leban bruce-at-leapyear.org | python-ideas-at-python..org <http://python-ideas-at-python.org>| < vajg1g2cqt@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') --- Bruce Learn how hackers think: http://j.mp/gruyere-security https://www.linkedin.com/in/bruceleban
On 20 April 2014 04:17, Bruce Leban bruce-at-leapyear.org | python-ideas-at-python.org| <vajg1g2cqt@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.
On Wed, Apr 23, 2014 at 1:01 AM, Lucas Malor <7vsfeu4pxg@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
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. On 22 April 2014 17:15, Chris Angelico rosuav-at-gmail.com | python-ideas-at-python.org| <iv1yq22odt@sneakemail.com> wrote:
On Wed, Apr 23, 2014 at 1:01 AM, Lucas Malor <7vsfeu4pxg@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 _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Apr 22, 2014 8:28 AM, "Lucas Malor" <7vsfeu4pxg@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)
On 22 April 2014 18:59, Bruce Leban bruce-at-leapyear.org |python-ideas-at-python.org| <vajg1g2cqt@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.
On Tue, Apr 22, 2014 at 10:37 AM, Lucas Malor <7vsfeu4pxg@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.
On Apr 22, 2014, at 8:01, "Lucas Malor" <7vsfeu4pxg@snkmail.com> wrote:
On 20 April 2014 04:17, Bruce Leban bruce-at-leapyear.org |python-ideas-at-python.org| <vajg1g2cqt@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" <7vsfeu4pxg@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.
I suggested the switch statement for a simpler alternative to the if-elif chain.
On 22 April 2014 17:15, Chris Angelico rosuav-at-gmail.com |python-ideas-at-python.org| <iv1yq22odt@sneakemail.com> wrote:
On Wed, Apr 23, 2014 at 1:01 AM, Lucas Malor <7vsfeu4pxg@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 _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 2014-04-22 18:39, Andrew Barnert wrote:
On Apr 22, 2014, at 8:01, "Lucas Malor" <7vsfeu4pxg@snkmail.com <mailto:7vsfeu4pxg@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>| <vajg1g2cqt@sneakemail.com <mailto:vajg1g2cqt@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" <7vsfeu4pxg@snkmail.com <mailto:7vsfeu4pxg@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.
[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.
On Apr 18, 2014, at 16:08, "Lucas Malor" <7vsfeu4pxg@snkmail.com> wrote:
On 18 April 2014 18:51, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9kh2cbt@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.
On 19 April 2014 06:54, Andrew Barnert abarnert-at-yahoo.com | python-ideas-at-python.org| <3en9kh2cbt@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 ";&"
http://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.htm... 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.
On Apr 19, 2014, at 10:54, "Lucas Malor" <7vsfeu4pxg@snkmail.com> wrote:
On 19 April 2014 06:54, Andrew Barnert abarnert-at-yahoo.com |python-ideas-at-python.org| <3en9kh2cbt@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 ";&"
http://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.htm...
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: 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.
About recursive function, I wronged. Sorry but it was late. 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.
On Fri, Apr 18, 2014 at 9:54 PM, Andrew Barnert <abarnert@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
On Tue, Apr 22, 2014 at 03:06:55PM -0700, Devin Jeanpierre <jeanpierreda@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/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.
On Apr 22, 2014, at 15:20, Oleg Broytman <phd@phdru.name> wrote:
On Tue, Apr 22, 2014 at 03:06:55PM -0700, Devin Jeanpierre <jeanpierreda@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.
Fun fact: we've had pattern matching on 2.7 using MacroPy for a year now https://github.com/lihaoyi/macropy#pattern-matching @caseclass Nil(): pass @caseclass 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)) On Tue, Apr 22, 2014 at 4:29 PM, Andrew Barnert < abarnert@yahoo.com.dmarc.invalid> wrote:
On Apr 22, 2014, at 15:20, Oleg Broytman <phd@phdru.name> wrote:
On Tue, Apr 22, 2014 at 03:06:55PM -0700, Devin Jeanpierre < jeanpierreda@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. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Apr 22, 2014, at 15:06, Devin Jeanpierre <jeanpierreda@gmail.com> wrote:
On Fri, Apr 18, 2014 at 9:54 PM, Andrew Barnert <abarnert@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.
On Tue, Apr 22, 2014 at 4:20 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
On Apr 22, 2014, at 15:06, Devin Jeanpierre <jeanpierreda@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
On Tue, Apr 22, 2014 at 8:58 PM, Devin Jeanpierre <jeanpierreda@gmail.com> wrote:
On Tue, Apr 22, 2014 at 4:20 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
On Apr 22, 2014, at 15:06, Devin Jeanpierre <jeanpierreda@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).
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
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
On Tue, Apr 22, 2014 at 11:13:44PM -0700, Devin Jeanpierre wrote:
On Tue, Apr 22, 2014 at 8:58 PM, Devin Jeanpierre <jeanpierreda@gmail.com> wrote:
On Tue, Apr 22, 2014 at 4:20 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
On Apr 22, 2014, at 15:06, Devin Jeanpierre <jeanpierreda@gmail.com> wrote: 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).
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__).
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
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.
On Sat, Apr 26, 2014 at 11:03 PM, Philipp A. <flying-sheep@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
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. 2014-04-26 15:27 GMT+02:00 Chris Angelico <rosuav@gmail.com>:
On Sat, Apr 26, 2014 at 11:03 PM, Philipp A. <flying-sheep@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 _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Sat, Apr 26, 2014 at 11:43 PM, Philipp A. <flying-sheep@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. ChrisA
interesting. it’s still assignment, even if nothing gets assigned (and only __iadd__ gets called behind the scenes). 2014-04-26 15:54 GMT+02:00 Chris Angelico <rosuav@gmail.com>:
On Sat, Apr 26, 2014 at 11:43 PM, Philipp A. <flying-sheep@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.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 4/26/2014 10:44 AM, Philipp A. wrote:
interesting. it’s still assignment, even if nothing gets assigned (and only |__iadd__| gets called behind the scenes).
Augmented assignment statements are specialized assignment statements. They are documented in a subsection of the assignment statement section. https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-s... Something is always rebound, even if it is the same object. The interpreter does not know whether __iadd__ will return the same object or a new object -- and it does not check after. Not understanding that augmented assignment always assigns trips up beginners who mistakenly and unnecessarily try to use it to mutate a member of a tuple.
t = ([],) t[0] += [1,2] Traceback (most recent call last): File "<pyshell#1>", line 1, in <module> t[0] += [1,2] TypeError: 'tuple' object does not support item assignment t ([1, 2],)
This is equivalent to* and effectively executed as
t = ([],) t[0] = t[0].__iadd__ ([1,2]) Traceback (most recent call last): File "<pyshell#13>", line 1, in <module> t[0] = t[0].__iadd__ ([1,2]) TypeError: 'tuple' object does not support item assignment t ([1, 2],)
* The difference is that 't' is only evaluated once and the resulting reference is used for both subscriptions. The proper way to mutate a tuple member is to directly mutate it.
t = ([],) t[0].extend([1,2]) t ([1, 2],)
-- Terry Jan Reedy
On 04/26/2014 09:54 AM, Chris Angelico wrote: > On Sat, Apr 26, 2014 at 11:43 PM, Philipp A.<flying-sheep@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. 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
"Philipp A." <flying-sheep@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?
2014-04-26 17:07 GMT+02:00 Stephen J. Turnbull <stephen@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.
On 4/26/2014 11:07 AM, Stephen J. Turnbull wrote:
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.
In my view, we should only add new syntax if it add substantial expressives -- making it easy to say something that is difficult now -- and not just be an alternative to if-elif-else. So I am one of 'some people'. -- Terry Jan Reedy
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
2014-04-26 17:17 GMT+02:00 Steven D'Aprano <steve@pearwood.info>:
Also, folks might like to review the wide range of semantics and syntax for switch/case in different languages:
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)
participants (16)
-
Andrew Barnert
-
Anthony Towns
-
Bruce Leban
-
Chris Angelico
-
David Mertz
-
Devin Jeanpierre
-
Haoyi Li
-
Lucas Malor
-
MRAB
-
Oleg Broytman
-
Phil Connell
-
Philipp A.
-
Ron Adam
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy