[Python-ideas] Null coalescing operators

Mark E. Haase mehaase at gmail.com
Mon Sep 21 04:35:03 CEST 2015


On the day I started this thread, I wrote a Python module that does what
maybe() does. I hadn't seen PyMaybe yet, and I couldn't think of any good
names for my module's functions, so my module was disappointingly ugly.

PyMaybe is exactly what I *wish* I had written that day. For comparison,
here's the code from my first post in this thread and it's maybe-ized
version.

    response = json.dumps({
        'created': created?.isoformat(),
        'updated': updated?.isoformat(),
        ...
    })

    response = json.dumps({
        'created': maybe(created).isoformat(),
        'updated': maybe(updated).isoformat(),
        ...
    })

Pros:
1. No modification to Python grammar.
2. More readable: it's easy to overlook ? when skimming quickly, but
"maybe()" is easy to spot.
3. More intuitive: the name "maybe" gives a hint at what it might do,
whereas if you've never seen "?." you would need to google it. (Googling
punctuation is obnoxious.)

Cons:
1. Doesn't short circuit: "maybe(family_name).upper().strip()" will fail if
family_name is None.[1] You might try
"maybe(maybe(family_name).upper()).strip()", but that is tricky to read and
still isn't quite right: if family_name is not None, then it *should* be an
error if "upper" is not an attribute of it. The 2-maybes form covers up
that error.

I'm sure there will be differing opinions on whether this type of operation
should short circuit. Some will say that we shouldn't be writing code that
way: if you need to chain calls, then use some other syntax. But I think
the example of upper case & strip is a good example of a perfectly
reasonable thing to do. These kinds of operations are pretty common when
you're interfacing with some external system or external data that has a
concept of null (databases, JSON, YAML, argparse, any thin wrapper around C
library, etc.).

This conversation has really focused on the null aware attribute access,
but the easier and more defensible use case is the null coalesce operator,
spelled "??" in C# and Dart. It's easy to find popular packages that use
something like "retries = default if default is not None else cls.DEFAULT"
to supply default instances.[2] Other packages do something like "retries =
default or cls.DEFAULT"[3], which is worse because it easy to overlook the
implicit coalescing of the left operand. In fact, the top hit for "python
null coalesce" is StackOverflow, and the top-voted answer says to use
"or".[4] (The answer goes on to explain the nuance of using "or" to
coalesce, but how many developers read that far?)

*In the interest of finding some common ground, I'd like to get some
feedback on the coalesce operator.* Maybe that conversation will yield some
insight into the other "None aware" operators.

A) Is coalesce a useful feature? (And what are the use cases?)
B) If it is useful, is it important that it short circuits? (Put another
way, could a function suffice?)
C) If it should be an operator, is "??" an ugly spelling?

    >>> retries = default ?? cls.DEFAULT

D) If it should be an operator, are any keywords more aesthetically
pleasing? (I expect zero support for adding a new keyword.)

    >>> retries = default else cls.DEFAULT
    >>> retries = try default or cls.DEFAULT
    >>> retries = try default else cls.DEFAULT
    >>> retries = try default, cls.DEFAULT
    >>> retries = from default or cls.DEFAULT
    >>> retries = from default else cls.DEFAULT
    >>> retries = from default, cls.DEFAULT


My answers:

A) It's useful: supplying default instances for optional values is an
obvious and common use case.
B) It should short circuit, because the patterns it replaces (using ternary
operator or "or") also do.
C) It's too restrictive to cobble a new operator out of existing keywords;
"??" isn't hard to read when it is separated by whitespace, as Pythonistas
typically do between a binary operator and its operands.
D) I don't find any of these easier to read or write than "??".




[1] I say "should", but actually PyMaybe does something underhanded so that
this expression does not fail: "maybe(foo).upper()" returns a "Nothing"
instance, not "None". But Nothing has "def __repr__(self): return
repr(None)". So if you try to print it out, you'll think you have a None
instance, but it won't behave like one. If you try to JSON serialize it,
you get a hideously confusing error: "TypeError: None is not JSON
serializable". For those not familiar: the JSON encoder can definitely
serialize None: it becomes a JSON "null". A standard implementation of
maybe() should _not_ work this way.

[2] https://github.com/shazow/urllib3/blob/master/urllib3/util/retry.py#L148

[3]
https://github.com/kennethreitz/requests/blob/46ff1a9a543cc4d33541aa64c94f50f0a698736e/requests/hooks.py#L25

[4] http://stackoverflow.com/a/4978745/122763

On Sun, Sep 20, 2015 at 6:50 PM, Guido van Rossum <guido at python.org> wrote:

> Actually if anything reminds me of Haskell it's a 'Maybe' type. :-(
>
> But I do side with those who find '?' too ugly to consider.
>
> On Sun, Sep 20, 2015 at 2:47 PM, David Mertz <mertz at gnosis.cx> wrote:
>
>> Paul Moore's idea is WAAYY better than the ugly ? pseudo-operator.
>>  `maybe()` reads just like a regular function (because it is), and we don't
>> need to go looking for Perl (nor Haskell) in some weird extra syntax that
>> will confuse beginners.
>>
>> On Sun, Sep 20, 2015 at 4:05 AM, Paul Moore <p.f.moore at gmail.com> wrote:
>>
>>> On 20 September 2015 at 08:31, Steven D'Aprano <steve at pearwood.info>
>>> wrote:
>>> > I'm not convinced that we should generalise this beyond the three
>>> > original examples of attribute access, item lookup and function call. I
>>> > think that applying ? to arbitrary operators is a case of "YAGNI". Or
>>> > perhaps, "You Shouldn't Need It".
>>>
>>> Agreed.
>>>
>>> Does this need to be an operator? How about the following:
>>>
>>>     class Maybe:
>>>         def __getattr__(self, attr): return None
>>>         def __getitem__(self, idx): return None
>>>         def __call__(self, *args, **kw): return None
>>>
>>>     def maybe(obj):
>>>         return Maybe() if obj is None else obj
>>>
>>>     attr = maybe(obj).spam
>>>     elt = maybe(obj)[n]
>>>     result = maybe(callback)(args)
>>>
>>> The Maybe class could be hidden, and the Maybe() object a singleton
>>> (making my poor naming a non-issue :-)) and if it's felt sufficiently
>>> useful, the maybe() function could be a builtin.
>>>
>>> Usage of the result of maybe() outside of the above 3 contexts should
>>> simply be "not supported" - don't worry about trying to stop people
>>> doing weird things, just make it clear that the intent is only to
>>> support the 3 given idiomatic usages.
>>>
>>> Paul.
>>> _______________________________________________
>>> Python-ideas mailing list
>>> Python-ideas at python.org
>>> https://mail.python.org/mailman/listinfo/python-ideas
>>> Code of Conduct: http://python.org/psf/codeofconduct/
>>>
>>
>>
>>
>> --
>> 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.
>>
>> _______________________________________________
>> Python-ideas mailing list
>> Python-ideas at python.org
>> https://mail.python.org/mailman/listinfo/python-ideas
>> Code of Conduct: http://python.org/psf/codeofconduct/
>>
>
>
>
> --
> --Guido van Rossum (python.org/~guido)
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>



-- 
Mark E. Haase
202-815-0201
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20150920/23821ff9/attachment.html>


More information about the Python-ideas mailing list