Using "||" (doubled pipe) as the null coalescing operator?

This may just be my C programmer brain talking, but reading the examples in PEP 505 makes me think of the existing use of "|" as the bitwise-or operator in both Python and C, and "||" as the logical-or operator in C. Using || for None-coalescence would still introduce a third "or" variant into Python as PEP 505 proposes (for good reasons), but without introducing a new symbolic character that relates to "OR" operations: x | y: bitwise OR (doesn't short circuit) x or y: logical OR (short circuits based on bool(x)) x || y: logical OR (short circuits based on "x is not None") (An analogy with C pointers works fairly well here, as "x || y" in C is a short-circuiting operator that switches on "x != NULL" in the pointer case) Taking some key examples from the PEP: data = data ?? [] headers = headers ?? {} data ?= [] headers ?= {} When written using a doubled pipe instead: data = data || [] headers = headers || {} data ||= [] headers ||= {} Translations would be the same as proposed n PEP 505 (for simplicity, this shows evaluating the LHS multiple times, in practice that wouldn't happen): data = data if data is not None else [] headers = headers if headers is not None else [] data = data if data is not None else [] headers = headers if headers is not None else [] One additional wrinkle is that a single "|" would conflict with the bitwise-or notation in the case of None-aware index access, so the proposal for both that and attribute access would be to make the notation "!|", borrowing the logical negation "!" from "!=". In this approach, where "||" would be the new short-circuiting binary operator standing for "LHS if LHS is not None else RHS", in "!|" the logical negations cancel out to give "LHS if LHS is None else LHS<OP>". PEP 505 notation: title?.upper() person?['name'] Using the "is not not None" pipe-based notation: title!|.upper() person!|['name'] And the gist of the translation: title if title is None else title.upper() person if person is None else person['name'] If this particular syntax were to be chosen, I also came up with the following possible mnemonics that may be useful as an explanatory tool: "||" is a barrier to prevent None passing through an expression "!|" explicitly allows None to pass without error Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sep 23, 2015, at 02:00, Nick Coghlan <ncoghlan@gmail.com> wrote:
The connection with || as a falsey-coalescing operator in C--and C#, Swift, etc., which have a separate null-coalescing operator that's spelled ??--seems like it could be misleading. Otherwise, I like it, but that's a pretty big otherwise.
Maybe you should have given the examples first, because written on its own like this it looks unspeakably ugly, but in context below it's a lot nicer...
title!|.upper() person!|['name']
This actually makes me think of the ! from Swift and other languages ("I know this optionally-null object is not null even if the type checker can't prove it, so let me use it that way"), more than negation. Which makes the whole thing make sense, but in a maybe-unpythonically out-of-order way: the bang-or means "either title is not None so I get title.upper(), or it is so I get None". I'm not sure whether other people will read it that way--or, if they do, whether it will be helpful or harmful mnemonically.
That's definitely easy to understand and remember. But since Python doesn't exist in isolation, and null coalescing and null conditional operators exist in other languages and are being added to many new ones, it might be useful to use similar terms to other languages. (See https://msdn.microsoft.com/en-us/library/ms173224.aspx and https://msdn.microsoft.com/en-us/library/dn986595.aspx for how C# describes them.)

On 23 September 2015 at 19:21, Andrew Barnert <abarnert@yahoo.com> wrote:
One of the problems I occasionally see with folks migrating to Python from other languages is with our relatively expansive definition of "false" values. In particular, C/C++ developers expect all strings and containers (i.e. non-NULL pointers) to be truthy, with only primitive types (i.e. pointers and numbers) able to be false in a boolean content. Accordingly, the difference between C's || and a null-coalescing || in Python would be adequately covered by "Python has no primitive types, everything's an object or a reference to an object, so || in Python is like || with pointers in C/C++, where a reference to None is Python's closest equivalent to NULL". For example, a C/C++ dev might be tempted to write code like this: def example(env=None): env = env || {} ... With || as a null coalescing operator, that code's actually correct, while the same code with "or" would be incorrect: def example(env=None): env = env or {} # Also replaces a passed in empty dict ...
It could also just be a "!" on its own, as the pipe isn't really adding much here: title!.upper() person!['name'] Then the "!" is saying "I know this may not exist, if it doesn't just bail out of this whole subexpression and produce None". That said, it's mainly the doubled "??" operator that I'm not fond of, I'm more OK with the "gracefully tolerate this being None" aspect of the proposal: title?.upper() person?['name']
Those mnemonics are the "How would I try to explain this to a 10 year old?" version, rather than the "How would I try to explain this to a computer science student?" version. Assuming a null coalescing operator is added, I'd expect to see more formal language than that used in the language reference, regardless of the spelling chosen. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

*cough* Ruby and Perl *cough* Ruby has two 'or' operators. One is used normally: myval = a == 1 || a == 2 # same as myval = (a == 1 || a == 2) The other one is a bit different: myval = a == 1 or a == 2 # same as (myval = a == 1) or (a == 2) It's used for simple nil and false elision, since Ruby has a stricter concept of falseness than Python. But it's a bug magnet!! That's what I hated about Ruby. Type the wrong operator and get a hidden error. Sometimes, when I code in C++ a lot and then do something in Python, I'll do: if a || b: Then I realize my mistake and fix it. BUT, with this change, it wouldn't be a mistake. It would just do something entirely different. On September 23, 2015 4:00:41 AM CDT, Nick Coghlan <ncoghlan@gmail.com> wrote:
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.

On 24 September 2015 at 00:37, Ryan Gonzalez <rymg19@gmail.com> wrote:
The Perl, Ruby and PHP situation is a bit different from the one proposed here - "or" and "||" are semantically identical in those languages aside from operator precedence. That said, it does still count as a point in favour of "??" as the binary operator spelling - experienced developers are unlikely to assume they already know what that means, while the "||" spelling means they're more likely to think "oh, that's just a higher precedence spelling of 'or'". The only other potential spelling of the coalescence case that comes to mind is to make "?" available in conditional expressions as a reference to the LHS: data = data if ? is not None else [] headers = headers if ? is not None else {} title = user_title if ? is not None else local_default_title if ? is not None else global_default_title title?.upper() person?['name'] The expansions of the latter two would then be: title if ? is None else ?.upper() person if ? is None else ?['name'] Augmented assignment would still be a shorthand for the first two examples: data ?= [] headers ?= {} Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 24 Sep 2015 01:59, "Nick Coghlan" <ncoghlan@gmail.com> wrote:
One advantage of this more explicit spelling is that it permits sentinels other than None in the expanded form: data = data if ? is not sentinel else default() Only the shorthand cases (augmented assignment, attribute access, subscript lookup) would be restricted to checking specifically against None. Cheers, Nick.

On 24/09/15 04:00, Nick Coghlan wrote:
data = data if ? is not sentinel else default()
This reads OK in a short example like this and when using word-based operators such as "is not". However, it's a bit clumsy looking when using operators spelled with punctuation: data = data if ? != None else default() data = data if foo <= ? <= bar else default()
title = user_title if ? is not None else local_default_title if ? is not None else global_default_title
I don't think I like the way '?' changes its target during the line in this example. For example, the equivalent of the admittedly-contrived expression: foo = bar if foo is None else baz if baz is not None else foo.frobnicate() is: foo = bar if ? is None else baz if ? is not None else foo.frobnicate() ... so you still have to spell 'foo' repeatedly (and only due to the subtle switch of the '?' target, which might go away (or be added) during code maintenance or refactoring). Also, if '?' is sort of a short-cut way of referencing the LHS, then one might naively expect to be able to write this: [(x, y) for x in range(5) if ? < 3 for y in range(5) if ? > 2] Regs, E.

On 24/09/15 23:30, Guido van Rossum wrote:
Using "?" as a (pro)noun is even worse than using it as an operator/modifier.
That was what I was trying to say, but you did it more correctly and using far fewer characters. How very Pythonic of you ... ;) E.

I just read the PEP. As a user, I would prefer || until... [Nick]
data = data ?? [] I would prefer || again simply because of no new syntax. Actually, the
I am like eating my own words, !| is pretty hard to read, especially during code review. The two symbols look too similar. Do we really need to have one doesn't raise exception and one that does? Next, the example title?.upper() in the PEP, this is also kind of ugly and unclear to me what's the purpose. I do appreciate the idea of circuit, but I don't feel the syntax is right. To me this is the debate between defaultdict and primitive dict (but in that debate you don't have the option to raise or not raise exception, but Nick's proposal does). poke Star War and say "python awesome, it is" and our brain will adopt. At least that line is still readable. price computing example in PEP 505 is not too convincing from a contract standpoint . The proposal is shorter than writing if requested_quanity is not None, but if you have to think about using null coalescing operator, then aren't you already spotting a case you need to handle? The example shows how the bug can be prevented, so maybe requested_quanlity should really default to 0 from the beginning, not None. None shouldn't appear and if it appear it should be a bug, and using null coalescing in this very example is actually a bug from my view. You are just avoiding ever having to think about taking care of such case in your code. But then you have negative number to avoid too... so that still require a sanity check somewhere. Just my four cents. On Thu, Sep 24, 2015 at 6:40 PM, Erik <python@lucidity.plus.com> wrote:

Throwing this one out there in case it inspires someone to come up with a better variation (or to get it explicitly rejected): object.(<accessor> if <condition> else <expr>) ... where 'accessor' is anything normally allowed after 'object' ([], (), attr) and 'condition' can omit the LHS of any conditional expression (which is taken the associated object) or not (i.e., can be a complete condition independent of the associated object): foo = bar.((param0, param1) if not None else default()) foo = bar.([idx] if != sentinel else default()) And the perhaps more off-the-wall (as 'bar' is not involved in the condition): foo = bar.(attr if secrets.randint(0, 1023) & 1 else default()) E.

On Fri, Sep 25, 2015 at 01:30:54AM +0100, Erik wrote:
I think that your intention is for that to be equivalent to: if bar not None: # missing "is" operator foo = bar(param0, param1) else: foo = default() I had to read your description three times before I got to the point where I could understand it. Some problems: I thought `bar.(<accessor> ...)` meant attribute access, so I initially expected the true branch to evaluate to: foo = bar.(param0, param1) which of course is a syntax error. Presumably you would write `bar.(attr if ...)` for attribute access and not `bar.(.attr if ...)`. I'm still confused about the missing `is`. Maybe you meant: if not None: # evaluates to True ... which is a problem with your suggestion that the left hand side of the condition is optional -- it makes it harder to catch errors in typing. Worse, it's actually ambiguous in some cases: spam = eggs.(cheese if - x else "aardvark") can be read as: if eggs - x: # implied bool(eggs - x) spam = eggs.cheese else: spam = "aardvark" or as this: if -x: # implied bool(-x) spam = eggs.cheese else: spam = "aardvark"
foo = bar.([idx] if != sentinel else default())
I **really** hate this syntax. It almost makes me look more fondly at the || / !| syntax. Looking at this, I really want to interprete the last part as foo = bar.default() so I can see this being a really common error. "Why isn't my method being called?" -1 on this. -- Steve

Hi Steven, On 25/09/15 02:35, Steven D'Aprano wrote:
Yes, you are correct. I omitted the 'is'.
I chose ".()" on purpose because it was a syntax error. Not including the "." meant it looks like a function call, so that wasn't workable. ".()" was supposed to read "I'm doing something with this object, but what I'm doing is conditional, so read on".
I'm still confused about the missing `is`. Maybe you meant:
No, I meant to write 'is'.
Worse, it's actually ambiguous in some cases:
Hmmm. Yes, OK, I see the problem here.
foo = bar.([idx] if != sentinel else default())
I **really** hate this syntax.
"hate" is a very strong word. You've prefixed it with "really" (and emphasised that with several asterisks) - are you trying to tell me something? ;)
Yes, I can see that's a reasonable interpretation. I never expected my suggestion to be embraced as-is, but perhaps it will inspire someone else to come up with a more enlightened suggestion - I did say that at the top of the post ;) E.

On Sep 23, 2015, at 02:00, Nick Coghlan <ncoghlan@gmail.com> wrote:
The connection with || as a falsey-coalescing operator in C--and C#, Swift, etc., which have a separate null-coalescing operator that's spelled ??--seems like it could be misleading. Otherwise, I like it, but that's a pretty big otherwise.
Maybe you should have given the examples first, because written on its own like this it looks unspeakably ugly, but in context below it's a lot nicer...
title!|.upper() person!|['name']
This actually makes me think of the ! from Swift and other languages ("I know this optionally-null object is not null even if the type checker can't prove it, so let me use it that way"), more than negation. Which makes the whole thing make sense, but in a maybe-unpythonically out-of-order way: the bang-or means "either title is not None so I get title.upper(), or it is so I get None". I'm not sure whether other people will read it that way--or, if they do, whether it will be helpful or harmful mnemonically.
That's definitely easy to understand and remember. But since Python doesn't exist in isolation, and null coalescing and null conditional operators exist in other languages and are being added to many new ones, it might be useful to use similar terms to other languages. (See https://msdn.microsoft.com/en-us/library/ms173224.aspx and https://msdn.microsoft.com/en-us/library/dn986595.aspx for how C# describes them.)

On 23 September 2015 at 19:21, Andrew Barnert <abarnert@yahoo.com> wrote:
One of the problems I occasionally see with folks migrating to Python from other languages is with our relatively expansive definition of "false" values. In particular, C/C++ developers expect all strings and containers (i.e. non-NULL pointers) to be truthy, with only primitive types (i.e. pointers and numbers) able to be false in a boolean content. Accordingly, the difference between C's || and a null-coalescing || in Python would be adequately covered by "Python has no primitive types, everything's an object or a reference to an object, so || in Python is like || with pointers in C/C++, where a reference to None is Python's closest equivalent to NULL". For example, a C/C++ dev might be tempted to write code like this: def example(env=None): env = env || {} ... With || as a null coalescing operator, that code's actually correct, while the same code with "or" would be incorrect: def example(env=None): env = env or {} # Also replaces a passed in empty dict ...
It could also just be a "!" on its own, as the pipe isn't really adding much here: title!.upper() person!['name'] Then the "!" is saying "I know this may not exist, if it doesn't just bail out of this whole subexpression and produce None". That said, it's mainly the doubled "??" operator that I'm not fond of, I'm more OK with the "gracefully tolerate this being None" aspect of the proposal: title?.upper() person?['name']
Those mnemonics are the "How would I try to explain this to a 10 year old?" version, rather than the "How would I try to explain this to a computer science student?" version. Assuming a null coalescing operator is added, I'd expect to see more formal language than that used in the language reference, regardless of the spelling chosen. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

*cough* Ruby and Perl *cough* Ruby has two 'or' operators. One is used normally: myval = a == 1 || a == 2 # same as myval = (a == 1 || a == 2) The other one is a bit different: myval = a == 1 or a == 2 # same as (myval = a == 1) or (a == 2) It's used for simple nil and false elision, since Ruby has a stricter concept of falseness than Python. But it's a bug magnet!! That's what I hated about Ruby. Type the wrong operator and get a hidden error. Sometimes, when I code in C++ a lot and then do something in Python, I'll do: if a || b: Then I realize my mistake and fix it. BUT, with this change, it wouldn't be a mistake. It would just do something entirely different. On September 23, 2015 4:00:41 AM CDT, Nick Coghlan <ncoghlan@gmail.com> wrote:
-- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.

On 24 September 2015 at 00:37, Ryan Gonzalez <rymg19@gmail.com> wrote:
The Perl, Ruby and PHP situation is a bit different from the one proposed here - "or" and "||" are semantically identical in those languages aside from operator precedence. That said, it does still count as a point in favour of "??" as the binary operator spelling - experienced developers are unlikely to assume they already know what that means, while the "||" spelling means they're more likely to think "oh, that's just a higher precedence spelling of 'or'". The only other potential spelling of the coalescence case that comes to mind is to make "?" available in conditional expressions as a reference to the LHS: data = data if ? is not None else [] headers = headers if ? is not None else {} title = user_title if ? is not None else local_default_title if ? is not None else global_default_title title?.upper() person?['name'] The expansions of the latter two would then be: title if ? is None else ?.upper() person if ? is None else ?['name'] Augmented assignment would still be a shorthand for the first two examples: data ?= [] headers ?= {} Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 24 Sep 2015 01:59, "Nick Coghlan" <ncoghlan@gmail.com> wrote:
One advantage of this more explicit spelling is that it permits sentinels other than None in the expanded form: data = data if ? is not sentinel else default() Only the shorthand cases (augmented assignment, attribute access, subscript lookup) would be restricted to checking specifically against None. Cheers, Nick.

On 24/09/15 04:00, Nick Coghlan wrote:
data = data if ? is not sentinel else default()
This reads OK in a short example like this and when using word-based operators such as "is not". However, it's a bit clumsy looking when using operators spelled with punctuation: data = data if ? != None else default() data = data if foo <= ? <= bar else default()
title = user_title if ? is not None else local_default_title if ? is not None else global_default_title
I don't think I like the way '?' changes its target during the line in this example. For example, the equivalent of the admittedly-contrived expression: foo = bar if foo is None else baz if baz is not None else foo.frobnicate() is: foo = bar if ? is None else baz if ? is not None else foo.frobnicate() ... so you still have to spell 'foo' repeatedly (and only due to the subtle switch of the '?' target, which might go away (or be added) during code maintenance or refactoring). Also, if '?' is sort of a short-cut way of referencing the LHS, then one might naively expect to be able to write this: [(x, y) for x in range(5) if ? < 3 for y in range(5) if ? > 2] Regs, E.

On 24/09/15 23:30, Guido van Rossum wrote:
Using "?" as a (pro)noun is even worse than using it as an operator/modifier.
That was what I was trying to say, but you did it more correctly and using far fewer characters. How very Pythonic of you ... ;) E.

I just read the PEP. As a user, I would prefer || until... [Nick]
data = data ?? [] I would prefer || again simply because of no new syntax. Actually, the
I am like eating my own words, !| is pretty hard to read, especially during code review. The two symbols look too similar. Do we really need to have one doesn't raise exception and one that does? Next, the example title?.upper() in the PEP, this is also kind of ugly and unclear to me what's the purpose. I do appreciate the idea of circuit, but I don't feel the syntax is right. To me this is the debate between defaultdict and primitive dict (but in that debate you don't have the option to raise or not raise exception, but Nick's proposal does). poke Star War and say "python awesome, it is" and our brain will adopt. At least that line is still readable. price computing example in PEP 505 is not too convincing from a contract standpoint . The proposal is shorter than writing if requested_quanity is not None, but if you have to think about using null coalescing operator, then aren't you already spotting a case you need to handle? The example shows how the bug can be prevented, so maybe requested_quanlity should really default to 0 from the beginning, not None. None shouldn't appear and if it appear it should be a bug, and using null coalescing in this very example is actually a bug from my view. You are just avoiding ever having to think about taking care of such case in your code. But then you have negative number to avoid too... so that still require a sanity check somewhere. Just my four cents. On Thu, Sep 24, 2015 at 6:40 PM, Erik <python@lucidity.plus.com> wrote:

Throwing this one out there in case it inspires someone to come up with a better variation (or to get it explicitly rejected): object.(<accessor> if <condition> else <expr>) ... where 'accessor' is anything normally allowed after 'object' ([], (), attr) and 'condition' can omit the LHS of any conditional expression (which is taken the associated object) or not (i.e., can be a complete condition independent of the associated object): foo = bar.((param0, param1) if not None else default()) foo = bar.([idx] if != sentinel else default()) And the perhaps more off-the-wall (as 'bar' is not involved in the condition): foo = bar.(attr if secrets.randint(0, 1023) & 1 else default()) E.

On Fri, Sep 25, 2015 at 01:30:54AM +0100, Erik wrote:
I think that your intention is for that to be equivalent to: if bar not None: # missing "is" operator foo = bar(param0, param1) else: foo = default() I had to read your description three times before I got to the point where I could understand it. Some problems: I thought `bar.(<accessor> ...)` meant attribute access, so I initially expected the true branch to evaluate to: foo = bar.(param0, param1) which of course is a syntax error. Presumably you would write `bar.(attr if ...)` for attribute access and not `bar.(.attr if ...)`. I'm still confused about the missing `is`. Maybe you meant: if not None: # evaluates to True ... which is a problem with your suggestion that the left hand side of the condition is optional -- it makes it harder to catch errors in typing. Worse, it's actually ambiguous in some cases: spam = eggs.(cheese if - x else "aardvark") can be read as: if eggs - x: # implied bool(eggs - x) spam = eggs.cheese else: spam = "aardvark" or as this: if -x: # implied bool(-x) spam = eggs.cheese else: spam = "aardvark"
foo = bar.([idx] if != sentinel else default())
I **really** hate this syntax. It almost makes me look more fondly at the || / !| syntax. Looking at this, I really want to interprete the last part as foo = bar.default() so I can see this being a really common error. "Why isn't my method being called?" -1 on this. -- Steve

Hi Steven, On 25/09/15 02:35, Steven D'Aprano wrote:
Yes, you are correct. I omitted the 'is'.
I chose ".()" on purpose because it was a syntax error. Not including the "." meant it looks like a function call, so that wasn't workable. ".()" was supposed to read "I'm doing something with this object, but what I'm doing is conditional, so read on".
I'm still confused about the missing `is`. Maybe you meant:
No, I meant to write 'is'.
Worse, it's actually ambiguous in some cases:
Hmmm. Yes, OK, I see the problem here.
foo = bar.([idx] if != sentinel else default())
I **really** hate this syntax.
"hate" is a very strong word. You've prefixed it with "really" (and emphasised that with several asterisks) - are you trying to tell me something? ;)
Yes, I can see that's a reasonable interpretation. I never expected my suggestion to be embraced as-is, but perhaps it will inspire someone else to come up with a more enlightened suggestion - I did say that at the top of the post ;) E.
participants (7)
-
Andrew Barnert
-
Erik
-
Guido van Rossum
-
John Wong
-
Nick Coghlan
-
Ryan Gonzalez
-
Steven D'Aprano