<div dir="ltr">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.<div><br></div><div>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.<div><br></div><div><div style="font-size:12.8px"><div>Â Â response = json.dumps({</div><div>Â Â Â Â 'created': created?.isoformat(),<br></div><div>Â Â Â Â 'updated': updated?.isoformat(),</div><div>Â Â Â Â ...</div><div>Â Â })</div><div><br></div><div><div style="font-size:12.8px"><div>Â Â response = json.dumps({</div><div>Â Â Â Â 'created': maybe(created).isoformat(),<br></div><div>Â Â Â Â 'updated': maybe(updated).isoformat(),</div><div>Â Â Â Â ...</div><div>Â Â })</div><div><br></div></div></div></div><div>Pros:</div><div>1. No modification to Python grammar.</div><div>2. More readable: it's easy to overlook ? when skimming quickly, but "maybe()" is easy to spot.</div><div>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.)</div><div><br></div><div>Cons:</div><div>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.</div><div><br></div><div>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.).</div></div></div><div><br></div><div>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?)</div><div><br></div><div><b>In the interest of finding some common ground, I'd like to get some feedback on the coalesce operator.</b> Maybe that conversation will yield some insight into the other "None aware" operators.</div><div><br></div><div>A) Is coalesce a useful feature? (And what are the use cases?)</div><div>B) If it is useful, is it important that it short circuits? (Put another way, could a function suffice?)</div><div>C) If it should be an operator, is "??" an ugly spelling?</div><div><br></div><div>Â Â >>> retries = default ?? cls.DEFAULT</div><div><br></div><div><div>D) If it should be an operator, are any keywords more aesthetically pleasing? (I expect zero support for adding a new keyword.)</div></div><div><br></div><div>Â Â >>> retries = default else cls.DEFAULT<br></div><div>Â Â >>> retries = try default or cls.DEFAULT</div><div><div><div>Â Â >>> retries = try default else cls.DEFAULT</div><div><div>Â Â >>> retries = try default, cls.DEFAULT</div><div><div></div></div></div><div></div></div><div>Â Â >>> retries = from default or cls.DEFAULT</div></div><div><div>Â Â >>> retries = from default else cls.DEFAULT</div></div><div><div>Â Â >>> retries = from default, cls.DEFAULT</div></div><div><br></div><div><br></div><div>My answers:</div><div><br></div><div>A) It's useful: supplying default instances for optional values is an obvious and common use case.</div><div>B) It should short circuit, because the patterns it replaces (using ternary operator or "or") also do.</div><div>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.</div><div>D) I don't find any of these easier to read or write than "??".</div><div><br></div><div><br></div><div><br></div><div><br></div><div>[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: "<span style="color:rgb(0,0,0);font-family:monospace">TypeError: None is not JSON serializable". </span>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.</div><div><br></div><div>[2]Â <a href="https://github.com/shazow/urllib3/blob/master/urllib3/util/retry.py#L148">https://github.com/shazow/urllib3/blob/master/urllib3/util/retry.py#L148</a></div><div><br></div><div>[3]Â <a href="https://github.com/kennethreitz/requests/blob/46ff1a9a543cc4d33541aa64c94f50f0a698736e/requests/hooks.py#L25">https://github.com/kennethreitz/requests/blob/46ff1a9a543cc4d33541aa64c94f50f0a698736e/requests/hooks.py#L25</a></div><div><br></div><div>[4]Â <a href="http://stackoverflow.com/a/4978745/122763">http://stackoverflow.com/a/4978745/122763</a></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Sun, Sep 20, 2015 at 6:50 PM, Guido van Rossum <span dir="ltr"><<a href="mailto:guido@python.org" target="_blank">guido@python.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div>Actually if anything reminds me of Haskell it's a 'Maybe' type. :-(<br><br></div><div>But I do side with those who find '?' too ugly to consider.<br></div></div><div class="gmail_extra"><div><div class="h5"><br><div class="gmail_quote">On Sun, Sep 20, 2015 at 2:47 PM, David Mertz <span dir="ltr"><<a href="mailto:mertz@gnosis.cx" target="_blank">mertz@gnosis.cx</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">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.<div class="gmail_extra"><div><div><br><div class="gmail_quote">On Sun, Sep 20, 2015 at 4:05 AM, Paul Moore <span dir="ltr"><<a href="mailto:p.f.moore@gmail.com" target="_blank">p.f.moore@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span>On 20 September 2015 at 08:31, Steven D'Aprano <<a href="mailto:steve@pearwood.info" target="_blank">steve@pearwood.info</a>> wrote:<br>
> I'm not convinced that we should generalise this beyond the three<br>
> original examples of attribute access, item lookup and function call. I<br>
> think that applying ? to arbitrary operators is a case of "YAGNI". Or<br>
> perhaps, "You Shouldn't Need It".<br>
<br>
</span>Agreed.<br>
<br>
Does this need to be an operator? How about the following:<br>
<br>
  class Maybe:<br>
    def __getattr__(self, attr): return None<br>
    def __getitem__(self, idx): return None<br>
    def __call__(self, *args, **kw): return None<br>
<br>
  def maybe(obj):<br>
    return Maybe() if obj is None else obj<br>
<br>
  attr = maybe(obj).spam<br>
  elt = maybe(obj)[n]<br>
  result = maybe(callback)(args)<br>
<br>
The Maybe class could be hidden, and the Maybe() object a singleton<br>
(making my poor naming a non-issue :-)) and if it's felt sufficiently<br>
useful, the maybe() function could be a builtin.<br>
<br>
Usage of the result of maybe() outside of the above 3 contexts should<br>
simply be "not supported" - don't worry about trying to stop people<br>
doing weird things, just make it clear that the intent is only to<br>
support the 3 given idiomatic usages.<br>
<span><font color="#888888"><br>
Paul.<br>
</font></span><div><div>_______________________________________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org" target="_blank">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/codeofconduct/</a><br>
</div></div></blockquote></div><br><br clear="all"><div><br></div></div></div><span><font color="#888888">-- <br><div>Keeping medicines from the bloodstreams of the sick; food <br>from the bellies of the hungry; books from the hands of the <br>uneducated; technology from the underdeveloped; and putting <br>advocates of freedom in prisons. Intellectual property is<br>to the 21st century what the slave trade was to the 16th.<br></div>
</font></span></div></div>
<br>_______________________________________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org" target="_blank">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/codeofconduct/</a><br></blockquote></div><br><br clear="all"><br>-- <br></div></div><div>--Guido van Rossum (<a href="http://python.org/~guido" target="_blank">python.org/~guido</a>)</div>
</div>
<br>_______________________________________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/codeofconduct/</a><br></blockquote></div><br><br clear="all"><div><br></div>-- <br><div class="gmail_signature"><div dir="ltr">Mark E. Haase<br><a style="color:rgb(17,85,204)">202-815-0201</a><br></div></div>
</div>