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@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@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@gmail.com> wrote:
On 20 September 2015 at 08:31, Steven D'Aprano <steve@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@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@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@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/



--
Mark E. Haase
202-815-0201