"and if" and "or if" for trailing if statements - including explicit fallthrough

I'd like to see the ability to do: if x: 1 and if y: 2 or if z: 3 The truth table for these would be: x | y | z | result 0 | _ | 0 | (none) 0 | _ | 1 | 3 1 | 0 | _ | 1,3 1 | 1 | _ | 1,2,3 and each statement is evaluated once, when encountered. (as such, y and z may not be evaluated at all, if their evaluation is not necessary to determine the outcome.) This enables the usage of fallthrough - "or if" is a fallthrough case, and the "or" in it is because its body gets evaluated either if the parent if's body got evaluated, *or* if the orif expression is truthy. "and if" is only suggested here for the sake of analogy/consistency/something. we have "and" and "or" and it'd be kinda weird to have "or if" without an "and if". it's equivalent to having a nested "if" at the very end of the "if" body. (only runs if the parent body ran *and* the expression is truthy.) also, I'm sure some of you will argue that the truth table should look more like this instead: x | y | z | result 0 | _ | 0 | (none) 0 | _ | 1 | 3 1 | 0 | 0 | 1 1 | 0 | 1 | 1,3 1 | 1 | _ | 1,2,3 and I say, no it shouldn't. if it were to do this, you'd have a hard time defining the semantics of elif and else for this. (just try it, it'll make your head spin.)

So this is equivalent to: if x: if y: 1, 2, 3 else: 1, 3 elif z: 3 I can see how the former saves me having to repeat the 3 three times. But the cost is being less obvious about when exactly I get a 3 so I’m forced to work it through step by step—including the confusion about the 1,0,0 case, which, as you mentioned, is only clear if you imagine putting an else at the end (although maybe you’d get used to that once you’d read through enough of these?). It’s even less obvious if you do throw in an elif, or just add an and if to the end (so now the condition to get there is not “x and y or z and w” but, I think, “((x and y) or z) and w”? Does that advantage outweigh the disadvantage? Certainly not for this example. But that’s probably because even the rewritten example is meaningless and useless. Maybe it would be different with a realistic use case, but I can’t imagine what that would be. Surely you must have some case where you really wanted this, that motivated you to propose it?

On 2019-12-21 4:15 p.m., Andrew Barnert wrote:
"1" and "2" and "3" are pieces of code, ofc. it's actually more like: if flag := x: 1 if y: 2 if flag or z: # note that "or" is short-circuiting. 3 but more efficiently implemented in bytecode without that "flag" local showing up. it's literally meant for easing the translation of switch statements from other languages and nothing else. (well, at least the "or if" part. the "and if" part isn't very useful. maybe to reduce deep indentation I guess?)

On Sun, Dec 22, 2019 at 6:35 AM Soni L. <fakedme+py@gmail.com> wrote:
I'm not sure I understand what switch construct would translate into this style. Can you show an example of code in some other language, how you'd translate that same logic into today's Python, and how you'd translate it into this "and if" / "or if" style? (BTW, it's probably clearer to represent your blocks of code as "spam()" and "ham()" rather than using numbers. I'm sure I wasn't the only person who was confused by those.) ChrisA

On 2019-12-21 4:52 p.m., Chris Angelico wrote:
Lua's VM had a case that went like this: switch (op) { [...] case OP_TAILCALL: { adjust_regs(); some_other_stuff(); /* fallthrough */ } case OP_CALL: { make_the_call_happen(); break; } } This is for what's known as "tail-call optimization". Anyway, if one were to translate this with "or if", it would look like: if op == OP_TAILCALL: adjust_regs() some_other_stuff() or if op == OP_CALL: make_the_call_happen() Importantly, note how there's no need for a "break" statement, and the fallthrough is explicitly stated in the use of an "or if" statement.

On Sun, Dec 22, 2019 at 7:06 AM Soni L. <fakedme+py@gmail.com> wrote:
Ah, okay. I get you. So you'd translate a switch block into a series of "elif" statements, but with an "or if" being a fallthrough case. So in effect, an if block would look like: if first_condition: first() elif second_condition: second() # fallthrough or if third_condition: third() elif fourth_condition: fourth() else: default_action() After calling second(), this would skip the third condition check, and go straight into third(). That's a concept that currently is quite hard to express cleanly in Python. (In contrast, the "and if" logic doesn't seem as useful; just nest it inside.) I'm not a fan of the "or if" spelling, but that's a matter of bikeshedding. The main question is: How common is this code, in situations where there is no better way to represent the entire block (ie you can't just rewrite it as a dict lookup)? I'd be curious to hear from anyone who's writing compilers in Python (including but not restricted to PyPy), and how they go about writing key constructs like this. ChrisA

On 22/12/19 9:04 am, Soni L. wrote:
Relying on fall-through in a switch is a micro-optimisation that may help make things go faster in C, but not so much in Python, so trying to find a literal translation is probably wrongheaded. I would be inclined to separate it into two independent cases: if op == OP_TAILCALL: adjust_regs() some_other_stuff() elif op == OP_CALL: adjust_regs() some_other_stuff() make_the_call_happen() If the parts being duplicated are more than just one or two calls as above, I would factor them out into a separate function. Decoupling the different branches makes the code easier to read and maintain, and in Python probably doesn't cost anything significant in performance. -- Greg

On Sun, Dec 22, 2019 at 10:46 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
(You have the translation backwards here; a tail call should do three things while a non-tail call should do one.)
It's not just about performance. It's also about expressing a concept. A tail call IS a call, and the correct way to write this is to do stuff, and then do whatever a call does. (Based on this implementation, of course.) "Refactor the common code into a function" isn't a solution to all problems, because if you have to go look elsewhere for the meaning of something, that stops you understanding it. It's hard to see in a trivial example, but the cost is very real in real code - unless you can give the refactored function a name that completely represents its purpose, all you've done is trade one form of hard-to-read code for another. ChrisA

On Dec 21, 2019, at 15:59, Chris Angelico <rosuav@gmail.com> wrote:
Looking at languages that try to express similar semantics without just aping C, Swift and Go both have a fallthrough statement that jumps to the start of the next case in a switch. This is similar: Once you think about it, an orif is just like an elif whose previous block ended in a fallthrough. But I’m not sure it actually reads the same. A fallthrough says “tail call does this stuff, and then does the stuff from the next (call) case”. An orif says “call, or also tail call which did some other stuff, does this stuff.” It seems backward to think of it that way.

On 21/12/2019 23:59:27, Chris Angelico wrote:
Or, if you want the two cases to continue to share code (which might be desirable if the shared code was (a lot) longer than one line): if op == OP_TAILCALL or op == OP_CALL: if op == OP_TAILCALL: adjust_regs() some_other_stuff() make_the_call_happen() While there is a repeated test which might offend purists, IMO the intent is clearer than the original "switch" version where it is easy to overlook that execution is supposed to fall through. IMO it is also clearer than "or if", though we wouldn't know for sure until/unless "or if" was adopted in Python and we had had time to get used to it. Rob Cliffe

Years ago there was an interesting movement called anti-if campaign, now it's more promotional, but the concept of "anti if" may help you find ways to remove the cases where your suggest "and if" and "or if" can apply. This article is particularly well written: https://code.joejag.com/2016/anti-if-the-missing-patterns.html If you have cases where you think "and if" and "or if" can be helpful, you probably underuse oop. Le sam. 21 déc. 2019 à 20:32, Soni L. <fakedme+py@gmail.com> a écrit :

Python is not an OOP language. If you want to breathe and preach OOP, you should quite frankly just use Java.
Python isn't *strictly* or *exclusively* an object-oriented programming language, but since it does have strong support for OOP features, saying that "Python is not an OOP language" simply isn't true. As a highly general purpose language, Python allows its users to choose between paradigms based on unique needs and preferences. I understand where you're coming from here, but I don't think it's particularly productive to push those who have a strong preference for OOP over to Java. Instead, try to explain why you prefer a different paradigm in this particular situation. If you can't see eye-to-eye with the other person, that's perfectly fine, but at least you have the chance of engaging in a productive discussion and perhaps learning a new perspective. On Sat, Dec 21, 2019 at 3:10 PM Soni L. <fakedme+py@gmail.com> wrote:

It's not a matter of paradigm in this case precisely, all the proposed refactoring are not specific to oop: - replace switch cases by polymorphism (polymorphism also in functional paradigm) - replace nested conditionnals by functions with guard clauses (not specially oop) - regroup function arguments in objects or types for functionnal paradigm (when extract to function if you have to much arguments in you function it's another symptom of poor design you may encounter) Le sam. 21 déc. 2019 à 21:53, Kyle Stanley <aeros167@gmail.com> a écrit :

So this is equivalent to: if x: if y: 1, 2, 3 else: 1, 3 elif z: 3 I can see how the former saves me having to repeat the 3 three times. But the cost is being less obvious about when exactly I get a 3 so I’m forced to work it through step by step—including the confusion about the 1,0,0 case, which, as you mentioned, is only clear if you imagine putting an else at the end (although maybe you’d get used to that once you’d read through enough of these?). It’s even less obvious if you do throw in an elif, or just add an and if to the end (so now the condition to get there is not “x and y or z and w” but, I think, “((x and y) or z) and w”? Does that advantage outweigh the disadvantage? Certainly not for this example. But that’s probably because even the rewritten example is meaningless and useless. Maybe it would be different with a realistic use case, but I can’t imagine what that would be. Surely you must have some case where you really wanted this, that motivated you to propose it?

On 2019-12-21 4:15 p.m., Andrew Barnert wrote:
"1" and "2" and "3" are pieces of code, ofc. it's actually more like: if flag := x: 1 if y: 2 if flag or z: # note that "or" is short-circuiting. 3 but more efficiently implemented in bytecode without that "flag" local showing up. it's literally meant for easing the translation of switch statements from other languages and nothing else. (well, at least the "or if" part. the "and if" part isn't very useful. maybe to reduce deep indentation I guess?)

On Sun, Dec 22, 2019 at 6:35 AM Soni L. <fakedme+py@gmail.com> wrote:
I'm not sure I understand what switch construct would translate into this style. Can you show an example of code in some other language, how you'd translate that same logic into today's Python, and how you'd translate it into this "and if" / "or if" style? (BTW, it's probably clearer to represent your blocks of code as "spam()" and "ham()" rather than using numbers. I'm sure I wasn't the only person who was confused by those.) ChrisA

On 2019-12-21 4:52 p.m., Chris Angelico wrote:
Lua's VM had a case that went like this: switch (op) { [...] case OP_TAILCALL: { adjust_regs(); some_other_stuff(); /* fallthrough */ } case OP_CALL: { make_the_call_happen(); break; } } This is for what's known as "tail-call optimization". Anyway, if one were to translate this with "or if", it would look like: if op == OP_TAILCALL: adjust_regs() some_other_stuff() or if op == OP_CALL: make_the_call_happen() Importantly, note how there's no need for a "break" statement, and the fallthrough is explicitly stated in the use of an "or if" statement.

On Sun, Dec 22, 2019 at 7:06 AM Soni L. <fakedme+py@gmail.com> wrote:
Ah, okay. I get you. So you'd translate a switch block into a series of "elif" statements, but with an "or if" being a fallthrough case. So in effect, an if block would look like: if first_condition: first() elif second_condition: second() # fallthrough or if third_condition: third() elif fourth_condition: fourth() else: default_action() After calling second(), this would skip the third condition check, and go straight into third(). That's a concept that currently is quite hard to express cleanly in Python. (In contrast, the "and if" logic doesn't seem as useful; just nest it inside.) I'm not a fan of the "or if" spelling, but that's a matter of bikeshedding. The main question is: How common is this code, in situations where there is no better way to represent the entire block (ie you can't just rewrite it as a dict lookup)? I'd be curious to hear from anyone who's writing compilers in Python (including but not restricted to PyPy), and how they go about writing key constructs like this. ChrisA

On 22/12/19 9:04 am, Soni L. wrote:
Relying on fall-through in a switch is a micro-optimisation that may help make things go faster in C, but not so much in Python, so trying to find a literal translation is probably wrongheaded. I would be inclined to separate it into two independent cases: if op == OP_TAILCALL: adjust_regs() some_other_stuff() elif op == OP_CALL: adjust_regs() some_other_stuff() make_the_call_happen() If the parts being duplicated are more than just one or two calls as above, I would factor them out into a separate function. Decoupling the different branches makes the code easier to read and maintain, and in Python probably doesn't cost anything significant in performance. -- Greg

On Sun, Dec 22, 2019 at 10:46 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
(You have the translation backwards here; a tail call should do three things while a non-tail call should do one.)
It's not just about performance. It's also about expressing a concept. A tail call IS a call, and the correct way to write this is to do stuff, and then do whatever a call does. (Based on this implementation, of course.) "Refactor the common code into a function" isn't a solution to all problems, because if you have to go look elsewhere for the meaning of something, that stops you understanding it. It's hard to see in a trivial example, but the cost is very real in real code - unless you can give the refactored function a name that completely represents its purpose, all you've done is trade one form of hard-to-read code for another. ChrisA

On Dec 21, 2019, at 15:59, Chris Angelico <rosuav@gmail.com> wrote:
Looking at languages that try to express similar semantics without just aping C, Swift and Go both have a fallthrough statement that jumps to the start of the next case in a switch. This is similar: Once you think about it, an orif is just like an elif whose previous block ended in a fallthrough. But I’m not sure it actually reads the same. A fallthrough says “tail call does this stuff, and then does the stuff from the next (call) case”. An orif says “call, or also tail call which did some other stuff, does this stuff.” It seems backward to think of it that way.

On 21/12/2019 23:59:27, Chris Angelico wrote:
Or, if you want the two cases to continue to share code (which might be desirable if the shared code was (a lot) longer than one line): if op == OP_TAILCALL or op == OP_CALL: if op == OP_TAILCALL: adjust_regs() some_other_stuff() make_the_call_happen() While there is a repeated test which might offend purists, IMO the intent is clearer than the original "switch" version where it is easy to overlook that execution is supposed to fall through. IMO it is also clearer than "or if", though we wouldn't know for sure until/unless "or if" was adopted in Python and we had had time to get used to it. Rob Cliffe

Years ago there was an interesting movement called anti-if campaign, now it's more promotional, but the concept of "anti if" may help you find ways to remove the cases where your suggest "and if" and "or if" can apply. This article is particularly well written: https://code.joejag.com/2016/anti-if-the-missing-patterns.html If you have cases where you think "and if" and "or if" can be helpful, you probably underuse oop. Le sam. 21 déc. 2019 à 20:32, Soni L. <fakedme+py@gmail.com> a écrit :

Python is not an OOP language. If you want to breathe and preach OOP, you should quite frankly just use Java.
Python isn't *strictly* or *exclusively* an object-oriented programming language, but since it does have strong support for OOP features, saying that "Python is not an OOP language" simply isn't true. As a highly general purpose language, Python allows its users to choose between paradigms based on unique needs and preferences. I understand where you're coming from here, but I don't think it's particularly productive to push those who have a strong preference for OOP over to Java. Instead, try to explain why you prefer a different paradigm in this particular situation. If you can't see eye-to-eye with the other person, that's perfectly fine, but at least you have the chance of engaging in a productive discussion and perhaps learning a new perspective. On Sat, Dec 21, 2019 at 3:10 PM Soni L. <fakedme+py@gmail.com> wrote:

It's not a matter of paradigm in this case precisely, all the proposed refactoring are not specific to oop: - replace switch cases by polymorphism (polymorphism also in functional paradigm) - replace nested conditionnals by functions with guard clauses (not specially oop) - regroup function arguments in objects or types for functionnal paradigm (when extract to function if you have to much arguments in you function it's another symptom of poor design you may encounter) Le sam. 21 déc. 2019 à 21:53, Kyle Stanley <aeros167@gmail.com> a écrit :
participants (9)
-
Anders Hovmöller
-
Andrew Barnert
-
Chris Angelico
-
Greg Ewing
-
Gregory Salvan
-
Kyle Stanley
-
MRAB
-
Rob Cliffe
-
Soni L.