On Thu, Jul 19, 2018 at 2:36 PM Brendan Barnwell <brenbarn@brenbarn.net> wrote:
        People keep saying that this null-coalescing behavior is so common and
useful, etc., but that hasn't been my experience at all.  In my
experience, the desire to shortcut this kind of logic is more often a
sign of corner-cutting and insufficiently specified data formats, and is
likely to cause bugs later on.  Eventually it has to actually matter
whether something is None or not, and these operators just kick that can
down the road.  In terms of their abstract meaning, they are not
remotely close to as common or useful as operators like & and |.


Brendan,

I am sure you didn't intend any offense, but the phrase "corner-cutting" is pejorative, especially when stated as a generalization and not as a critique of a specific example. I have used these operators in professional projects in other languages (i.e. Dart), and I used them because they seemed like the best tool for the job at hand, not because I was shirking effort or short on time.

There are accepted PEPs that I don't find useful, e.g. PEP-465 (infix matrix multiplication operator). It's not because it's a bad PEP; it's just aimed at a type of programming that I don't do. That PEP had to address some of the same criticisms that arise from PEP-505: there's no precedent for that spelling, it's a small niche, and we can already to that in pure python.[1] But I trust the Python numeric computing community in their assertion that the @ syntax is valuable to their work.

In the same way that PEP-465 is valuable to a certain type of programming, None-coalescing (as an abstract concept, not the concrete proposal in PEP-505) is valuable to another type of programming. Python is often used a glue language.[2] In my own experience, it's very common to request data from one system, do some processing on it, and send it to another system. For example, I might run a SQL query, convert the results to JSON, and return it in an HTTP response. As a developer, I may not get to choose the database schema. I may also not get to choose the JSON schema. My job is to marshal data from one system to another. Consider the following example:

def get_user_json(user_id):
  user = user_table.find_by_id(user_id)
  user_dict = {
    # Username is always non-null in database.
    'username': user['username'],
    
    # Creation datetime is always non-null in database; must be ISO-8601 string in JSON.
    'created_at': user['created'].isoformat(),
    
    # Signature can be null in database and null in JSON.
    'signature': user['signature'],
    
    # Background color can be null in database but must not be null in JSON.
    'background_color': user.get('background_color', 'blue')
    
    # Login datetime can be null in database if user has never logged in. Must either
    # be null or an ISO-8601 string in JSON.
    'last_logged_in_at': user['last_logged_in_at'].isoformat() if user['last_login'] is not None else None,

    # Remaining fields omitted for brevity
  }
  return json.dumps(user_dict)

Python makes a lot of the desired behavior concise to write. For example, the DBAPI (PEP-249) states that null and None are interchanged when reading and writing from the database. The stdlib json module also converts between None and null. This makes a lot of the mappings trivial to express in Python. But it gets tricky for cases like "last_logged_in_at", where a null is permitted by the business rules I've been given, but if it's non-null then I need to call a method on it. As written above, it is over 100 characters long. With safe-navigation operators, it could be made to fit the line length without loss of clarity:

    'last_logged_in_at': user['last_logged_in_at'] ?. isoformat(),

Many people in this thread prefer to write it as a block:

  if user['last_logged_in_at'] is None:
    user_dict['last_logged_in_at'] = None
  else:
    user_dict['last_logged_in_at'] = user['last_logged_in_at'].isoformat() 

I agree that the block is readable, but only because my made-up function is very short. In real-world glue code, you may have dozens of mappings, and multiplying by 4 leads to functions that have so many lines of code that readability is significantly worse, and that highly repetitive code inevitably leads to typos.

It's not just databases and web services. An early draft of PEP-505 contains additional examples of where None arises in glue code.[4] And it's not even just glue code: the standard library itself contains around 500 examples of none-coalescing or safe-navigation![5] I certainly don't think anybody here will claim that stdlib authors have cut hundreds of corners.



[1] https://www.python.org/dev/peps/pep-0465/
[2] https://www.python.org/doc/essays/omg-darpa-mcc-position/
[3] https://www.python.org/dev/peps/pep-0249/
[4] https://github.com/python/peps/blob/c5270848fe4481947ee951c2a415824b4dcd8a4f/pep-0505.txt#L63
[5] https://www.python.org/dev/peps/pep-0505/#examples