PEP: 531 Title: Existence checking operators Version: $Revision$
Last-Modified: $Date$ Author: Nick Coghlan <ncoghlan@gmail.com> Status:
Withdrawn Type: Standards Track Content-Type: text/x-rst Created:
25-Oct-2016 Python-Version: 3.7 Post-History: 28-Oct-2016

Abstract
========

Inspired by PEP 505 and the related discussions, this PEP proposes the
addition of two new control flow operators to Python:

-   Existence-checking precondition ("exists-then"): `expr1 ?then expr2`
-   Existence-checking fallback ("exists-else"): `expr1 ?else expr2`

as well as the following abbreviations for common existence checking
expressions and statements:

-   Existence-checking attribute access: `obj?.attr` (for
    `obj ?then obj.attr`)
-   Existence-checking subscripting: `obj?[expr]` (for
    `obj ?then obj[expr]`)
-   Existence-checking assignment: `value ?= expr` (for
    `value = value ?else expr`)

The common `?` symbol in these new operator definitions indicates that
they use a new "existence checking" protocol rather than the established
truth-checking protocol used by if statements, while loops,
comprehensions, generator expressions, conditional expressions, logical
conjunction, and logical disjunction.

This new protocol would be made available as `operator.exists`, with the
following characteristics:

-   types can define a new `__exists__` magic method (Python) or
    `tp_exists` slot (C) to override the default behaviour. This
    optional method has the same signature and possible return values as
    `__bool__`.
-   `operator.exists(None)` returns `False`
-   `operator.exists(NotImplemented)` returns `False`
-   `operator.exists(Ellipsis)` returns `False`
-   `float`, `complex` and `decimal.Decimal` will override the existence
    check such that `NaN` values return `False` and other values
    (including zero values) return `True`
-   for any other type, `operator.exists(obj)` returns True by default.
    Most importantly, values that evaluate to False in a truth checking
    context (zeroes, empty containers) will still evaluate to True in an
    existence checking context

PEP Withdrawal
==============

When posting this PEP for discussion on python-ideas \[4\_\], I asked
reviewers to consider 3 high level design questions before moving on to
considering the specifics of this particular syntactic proposal:

1.  Do we collectively agree that "existence checking" is a useful
    general concept that exists in software development and is distinct
    from the concept of "truth checking"?
2.  Do we collectively agree that the Python ecosystem would benefit
    from an existence checking protocol that permits generalisation of
    algorithms (especially short circuiting ones) across different "data
    missing" indicators, including those defined in the language
    definition, the standard library, and custom user code?
3.  Do we collectively agree that it would be easier to use such a
    protocol effectively if existence-checking equivalents to the
    truth-checking "and" and "or" control flow operators were available?

While the answers to the first question were generally positive, it
quickly became clear that the answer to the second question is "No".

Steven D'Aprano articulated the counter-argument well in \[5\_\], but
the general idea is that when checking for "missing data" sentinels,
we're almost always looking for a *specific* sentinel value, rather than
*any* sentinel value.

`NotImplemented` exists, for example, due to `None` being a potentially
legitimate result from overloaded arithmetic operators and exception
handling imposing too much runtime overhead to be useful for operand
coercion.

Similarly, `Ellipsis` exists for multi-dimensional slicing support due
to `None` already have another meaning in a slicing context (indicating
the use of the default start or stop indices, or the default step size).

In mathematics, the value of `NaN` is that *programmatically* it behaves
like a normal value of its type (e.g. exposing all the usual attributes
and methods), while arithmetically it behaves according to the
mathematical rules for handling `NaN` values.

With that core design concept invalidated, the proposal as a whole
doesn't make sense, and it is accordingly withdrawn.

However, the discussion of the proposal did prompt consideration of a
potential protocol based approach to make the existing `and`, `or` and
`if-else` operators more flexible \[6\_\] without introducing any new
syntax, so I'll be writing that up as another possible alternative to
PEP 505.

Relationship with other PEPs
============================

While this PEP was inspired by and builds on Mark Haase's excellent work
in putting together PEP 505, it ultimately competes with that PEP due to
significant differences in the specifics of the proposed syntax and
semantics for the feature.

It also presents a different perspective on the rationale for the change
by focusing on the benefits to existing Python users as the typical
demands of application and service development activities are genuinely
changing. It isn't an accident that similar features are now appearing
in multiple programming languages, and while it's a good idea for us to
learn from how other language designers are handling the problem,
precedents being set elsewhere are more relevant to *how* we would go
about tackling this problem than they are to whether or not we think
it's a problem we should address in the first place.

Rationale
=========

Existence checking expressions
------------------------------

An increasingly common requirement in modern software development is the
need to work with "semi-structured data": data where the structure of
the data is known in advance, but pieces of it may be missing at
runtime, and the software manipulating that data is expected to degrade
gracefully (e.g. by omitting results that depend on the missing data)
rather than failing outright.

Some particularly common cases where this issue arises are:

-   handling optional application configuration settings and function
    parameters
-   handling external service failures in distributed systems
-   handling data sets that include some partial records

It is the latter two cases that are the primary motivation for this PEP
- while needing to deal with optional configuration settings and
parameters is a design requirement at least as old as Python itself, the
rise of public cloud infrastructure, the development of software systems
as collaborative networks of distributed services, and the availability
of large public and private data sets for analysis means that the
ability to degrade operations gracefully in the face of partial service
failures or partial data availability is becoming an essential feature
of modern programming environments.

At the moment, writing such software in Python can be genuinely awkward,
as your code ends up littered with expressions like:

-   `value1 = expr1.field.of.interest if expr1 is not None else None`
-   `value2 = expr2["field"]["of"]["interest"] if expr2 is not None else None`
-   `value3 = expr3 if expr3 is not None else expr4 if expr4 is not None else expr5`

If these are only occasional, then expanding out to full statement forms
may help improve readability, but if you have 4 or 5 of them in a row
(which is a fairly common situation in data transformation pipelines),
then replacing them with 16 or 20 lines of conditional logic really
doesn't help matters.

Expanding the three examples above that way hopefully helps illustrate
that::

    if expr1 is not None:
        value1 = expr1.field.of.interest
    else:
        value1 = None
    if expr2 is not None:
        value2 = expr2["field"]["of"]["interest"]
    else:
        value2 = None
    if expr3 is not None:
        value3 = expr3
    else:
        if expr4 is not None:
            value3 = expr4
        else:
            value3 = expr5

The combined impact of the proposals in this PEP is to allow the above
sample expressions to instead be written as:

-   `value1 = expr1?.field.of.interest`
-   `value2 = expr2?["field"]["of"]["interest"]`
-   `value3 = expr3 ?else expr4 ?else expr5`

In these forms, almost all of the information presented to the reader is
immediately relevant to the question "What does this code do?", while
the boilerplate code to handle missing data by passing it through to the
output or falling back to an alternative input, has shrunk to two uses
of the `?` symbol and two uses of the `?else` keyword.

In the first two examples, the 31 character boilerplate clause
`if exprN is not None else None` (minimally 27 characters for a single
letter variable name) has been replaced by a single `?` character,
substantially improving the signal-to-pattern-noise ratio of the lines
(especially if it encourages the use of more meaningful variable and
field names rather than making them shorter purely for the sake of
expression brevity).

In the last example, two instances of the 21 character boilerplate,
`if exprN is not None` (minimally 17 characters) are replaced with
single characters, again substantially improving the
signal-to-pattern-noise ratio.

Furthermore, each of our 5 "subexpressions of potential interest" is
included exactly once, rather than 4 of them needing to be duplicated or
pulled out to a named variable in order to first check if they exist.

The existence checking precondition operator is mainly defined to
provide a clear conceptual basis for the existence checking attribute
access and subscripting operators:

-   `obj?.attr` is roughly equivalent to `obj ?then obj.attr`
-   `obj?[expr]` is roughly equivalent to `obj ?then obj[expr]`

The main semantic difference between the shorthand forms and their
expanded equivalents is that the common subexpression to the left of the
existence checking operator is evaluated only once in the shorthand form
(similar to the benefit offered by augmented assignment statements).

Existence checking assignment
-----------------------------

Existence-checking assignment is proposed as a relatively
straightforward expansion of the concepts in this PEP to also cover the
common configuration handling idiom:

-   `value = value if value is not None else expensive_default()`

by allowing that to instead be abbreviated as:

-   `value ?= expensive_default()`

This is mainly beneficial when the target is a subscript operation or
subattribute, as even without this specific change, the PEP would still
permit this idiom to be updated to:

-   `value = value ?else expensive_default()`

The main argument *against* adding this form is that it's arguably
ambiguous and could mean either:

-   `value = value ?else expensive_default()`; or
-   `value = value ?then value.subfield.of.interest`

The second form isn't at all useful, but if this concern was deemed
significant enough to address while still keeping the augmented
assignment feature, the full keyword could be included in the syntax:

-   `value ?else= expensive_default()`

Alternatively, augmented assignment could just be dropped from the
current proposal entirely and potentially reconsidered at a later date.

Existence checking protocol
---------------------------

The existence checking protocol is including in this proposal primarily
to allow for proxy objects (e.g. local representations of remote
resources) and mock objects used in testing to correctly indicate
non-existence of target resources, even though the proxy or mock object
itself is not None.

However, with that protocol defined, it then seems natural to expand it
to provide a type independent way of checking for `NaN` values in
numeric types - at the moment you need to be aware of the exact data
type you're working with (e.g. builtin floats, builtin complex numbers,
the decimal module) and use the appropriate operation
(e.g. `math.isnan`, `cmath.isnan`, `decimal.getcontext().is_nan()`,
respectively)

Similarly, it seems reasonable to declare that the other placeholder
builtin singletons, `Ellipsis` and `NotImplemented`, also qualify as
objects that represent the absence of data moreso than they represent
data.

Proposed symbolic notation
--------------------------

Python has historically only had one kind of implied boolean context:
truth checking, which can be invoked directly via the `bool()` builtin.
As this PEP proposes a new kind of control flow operation based on
existence checking rather than truth checking, it is considered valuable
to have a reminder directly in the code when existence checking is being
used rather than truth checking.

The mathematical symbol for existence assertions is U+2203 'THERE
EXISTS': `∃`

Accordingly, one possible approach to the syntactic additions proposed
in this PEP would be to use that already defined mathematical notation:

-   `expr1 ∃then expr2`
-   `expr1 ∃else expr2`
-   `obj∃.attr`
-   `obj∃[expr]`
-   `target ∃= expr`

However, there are two major problems with that approach, one practical,
and one pedagogical.

The practical problem is the usual one that most keyboards don't offer
any easy way of entering mathematical symbols other than those used in
basic arithmetic (even the symbols appearing in this PEP were ultimately
copied & pasted from \[3\]\_ rather than being entered directly).

The pedagogical problem is that the symbols for existence assertions
(`∃`) and universal assertions (`∀`) aren't going to be familiar to most
people the way basic arithmetic operators are, so we wouldn't actually
be making the proposed syntax easier to understand by adopting `∃`.

By contrast, `?` is one of the few remaining unused ASCII punctuation
characters in Python's syntax, making it available as a candidate
syntactic marker for "this control flow operation is based on an
existence check, not a truth check".

Taking that path would also have the advantage of aligning Python's
syntax with corresponding syntax in other languages that offer similar
features.

Drawing from the existing summary in PEP 505 and the Wikipedia articles
on the "safe navigation operator \[1\]\_ and the"null coalescing
operator\" \[2\]\_, we see:

-   The `?.` existence checking attribute access syntax precisely aligns
    with:

    -   the "safe navigation" attribute access operator in C\# (`?.`)
    -   the "optional chaining" operator in Swift (`?.`)
    -   the "safe navigation" attribute access operator in Groovy (`?.`)
    -   the "conditional member access" operator in Dart (`?.`)

-   The `?[]` existence checking attribute access syntax precisely
    aligns with:

    -   the "safe navigation" subscript operator in C\# (`?[]`)
    -   the "optional subscript" operator in Swift (`?[].`)

-   The `?else` existence checking fallback syntax semantically aligns
    with:

    -   the "null-coalescing" operator in C\# (`??`)
    -   the "null-coalescing" operator in PHP (`??`)
    -   the "nil-coalescing" operator in Swift (`??`)

To be clear, these aren't the only spelling of these operators used in
other languages, but they're the most common ones, and the `?` symbol is
the most common syntactic marker by far (presumably prompted by the use
of `?` to introduce the "then" clause in C-style conditional
expressions, which many of these languages also offer).

Proposed keywords
-----------------

Given the symbolic marker `?`, it would be syntactically unambiguous to
spell the existence checking precondition and fallback operations using
the same keywords as their truth checking counterparts:

-   `expr1 ?and expr2` (instead of `expr1 ?then expr2`)
-   `expr1 ?or expr2` (instead of `expr1 ?else expr2`)

However, while syntactically unambiguous when written, this approach
makes the code incredibly hard to *pronounce* (What's the pronunciation
of "?"?) and also hard to *describe* (given reused keywords, there's no
obvious shorthand terms for "existence checking precondition (?and)" and
"existence checking fallback (?or)" that would distinguish them from
"logical conjunction (and)" and "logical disjunction (or)").

We could try to encourage folks to pronounce the `?` symbol as "exists",
making the shorthand names the "exists-and expression" and the
"exists-or expression", but there'd be no way of guessing those names
purely from seeing them written in a piece of code.

Instead, this PEP takes advantage of the proposed symbolic syntax to
introduce a new keyword (`?then`) and borrow an existing one (`?else`)
in a way that allows people to refer to "then expressions" and "else
expressions" without ambiguity.

These keywords also align well with the conditional expressions that are
semantically equivalent to the proposed expressions.

For `?else` expressions, `expr1 ?else expr2` is equivalent to::

    _lhs_result = expr1
    _lhs_result if operator.exists(_lhs_result) else expr2

Here the parallel is clear, since the `else expr2` appears at the end of
both the abbreviated and expanded forms.

For `?then` expressions, `expr1 ?then expr2` is equivalent to::

    _lhs_result = expr1
    expr2 if operator.exists(_lhs_result) else _lhs_result

Here the parallel isn't as immediately obvious due to Python's
traditionally anonymous "then" clauses (introduced by `:` in `if`
statements and suffixed by `if` in conditional expressions), but it's
still reasonably clear as long as you're already familiar with the
"if-then-else" explanation of conditional control flow.

Risks and concerns
==================

Readability
-----------

Learning to read and write the new syntax effectively mainly requires
internalising two concepts:

-   expressions containing `?` include an existence check and may short
    circuit
-   if `None` or another "non-existent" value is an expected input, and
    the correct handling is to propagate that to the result, then the
    existence checking operators are likely what you want

Currently, these concepts aren't explicitly represented at the language
level, so it's a matter of learning to recognise and use the various
idiomatic patterns based on conditional expressions and statements.

Magic syntax
------------

There's nothing about `?` as a syntactic element that inherently
suggests `is not None` or `operator.exists`. The main current use of `?`
as a symbol in Python code is as a trailing suffix in IPython
environments to request help information for the result of the preceding
expression.

However, the notion of existence checking really does benefit from a
pervasive visual marker that distinguishes it from truth checking, and
that calls for a single-character symbolic syntax if we're going to do
it at all.

Conceptual complexity
---------------------

This proposal takes the currently ad hoc and informal concept of
"existence checking" and elevates it to the status of being a syntactic
language feature with a clearly defined operator protocol.

In many ways, this should actually *reduce* the overall conceptual
complexity of the language, as many more expectations will map correctly
between truth checking with `bool(expr)` and existence checking with
`operator.exists(expr)` than currently map between truth checking and
existence checking with `expr is not None` (or
`expr is not NotImplemented` in the context of operand coercion, or the
various NaN-checking operations in mathematical libraries).

As a simple example of the new parallels introduced by this PEP,
compare::

    all_are_true = all(map(bool, iterable))
    at_least_one_is_true = any(map(bool, iterable))
    all_exist = all(map(operator.exists, iterable))
    at_least_one_exists = any(map(operator.exists, iterable))

Design Discussion
=================

Subtleties in chaining existence checking expressions
-----------------------------------------------------

Similar subtleties arise in chaining existence checking expressions as
already exist in chaining logical operators: the behaviour can be
surprising if the right hand side of one of the expressions in the chain
itself returns a value that doesn't exist.

As a result, `value = arg1 ?then f(arg1) ?else default()` would be
dubious for essentially the same reason that
`value = cond and expr1 or expr2` is dubious: the former will evaluate
`default()` if `f(arg1)` returns `None`, just as the latter will
evaluate `expr2` if `expr1` evaluates to `False` in a boolean context.

Ambiguous interaction with conditional expressions
--------------------------------------------------

In the proposal as currently written, the following is a syntax error:

-   `value = f(arg) if arg ?else default`

While the following is a valid operation that checks a second condition
if the first doesn't exist rather than merely being false:

-   `value = expr1 if cond1 ?else cond2 else expr2`

The expression chaining problem described above means that the argument
can be made that the first operation should instead be equivalent to:

-   `value = f(arg) if operator.exists(arg) else default`

requiring the second to be written in the arguably clearer form:

-   `value = expr1 if (cond1 ?else cond2) else expr2`

Alternatively, the first form could remain a syntax error, and the
existence checking symbol could instead be attached to the `if` keyword:

-   `value = expr1 if? cond else expr2`

Existence checking in other truth-checking contexts
---------------------------------------------------

The truth-checking protocol is currently used in the following syntactic
constructs:

-   logical conjunction (and-expressions)
-   logical disjunction (or-expressions)
-   conditional expressions (if-else expressions)
-   if statements
-   while loops
-   filter clauses in comprehensions and generator expressions

In the current PEP, switching from truth-checking with `and` and `or` to
existence-checking is a matter of substituting in the new keywords,
`?then` and `?else` in the appropriate places.

For other truth-checking contexts, it proposes either importing and
using the `operator.exists` API, or else continuing with the current
idiom of checking specifically for `expr is not None` (or the context
appropriate equivalent).

The simplest possible enhancement in that regard would be to elevate the
proposed `exists()` API from an operator module function to a new
builtin function.

Alternatively, the `?` existence checking symbol could be supported as a
modifier on the `if` and `while` keywords to indicate the use of an
existence check rather than a truth check.

However, it isn't at all clear that the potential consistency benefits
gained for either suggestion would justify the additional disruption, so
they've currently been omitted from the proposal.

Defining expected invariant relations between `__bool__` and `__exists__`
-------------------------------------------------------------------------

The PEP currently leaves the definition of `__bool__` on all existing
types unmodified, which ensures the entire proposal remains backwards
compatible, but results in the following cases where `bool(obj)` returns
`True`, but the proposed `operator.exists(obj)` would return `False`:

-   `NaN` values for `float`, `complex`, and `decimal.Decimal`
-   `Ellipsis`
-   `NotImplemented`

The main argument for potentially changing these is that it becomes
easier to reason about potential code behaviour if we have a recommended
invariant in place saying that values which indicate they don't exist in
an existence checking context should also report themselves as being
`False` in a truth checking context.

Failing to define such an invariant would lead to arguably odd outcomes
like `float("NaN") ?else 0.0` returning `0.0` while
`float("NaN") or 0.0` returns `NaN`.

Limitations
===========

Arbitrary sentinel objects
--------------------------

This proposal doesn't attempt to provide syntactic support for the
"sentinel object" idiom, where `None` is a permitted explicit value, so
a separate sentinel object is defined to indicate missing values::

    _SENTINEL = object()
    def f(obj=_SENTINEL):
        return obj if obj is not _SENTINEL else default_value()

This could potentially be supported at the expense of making the
existence protocol definition significantly more complex, both to define
and to use:

-   at the Python layer, `operator.exists` and `__exists__`
    implementations would return the empty tuple to indicate
    non-existence, and otherwise return a singleton tuple containing a
    reference to the object to be used as the result of the existence
    check
-   at the C layer, `tp_exists` implementations would return NULL to
    indicate non-existence, and otherwise return a `PyObject *` pointer
    as the result of the existence check

Given that change, the sentinel object idiom could be rewritten as::

    class Maybe:
      SENTINEL = object()
      def __init__(self, value):
          self._result = (value,) is value is not self.SENTINEL else ()
      def __exists__(self):
          return self._result

    def f(obj=Maybe.SENTINEL):
        return Maybe(obj) ?else default_value()

However, I don't think cases where the 3 proposed standard sentinel
values (i.e. `None`, `Ellipsis` and `NotImplemented`) can't be used are
going to be anywhere near common enough for the additional protocol
complexity and the loss of symmetry between `__bool__` and `__exists__`
to be worth it.

Specification
=============

The Abstract already gives the gist of the proposal and the Rationale
gives some specific examples. If there's enough interest in the basic
idea, then a full specification will need to provide a precise
correspondence between the proposed syntactic sugar and the underlying
conditional expressions that is sufficient to guide the creation of a
reference implementation.

...TBD...

Implementation
==============

As with PEP 505, actual implementation has been deferred pending
in-principle interest in the idea of adding these operators - the
implementation isn't the hard part of these proposals, the hard part is
deciding whether or not this is a change where the long term benefits
for new and existing Python users outweigh the short term costs involved
in the wider ecosystem (including developers of other implementations,
language curriculum developers, and authors of other Python related
educational material) adjusting to the change.

...TBD...

References
==========

.. \[1\] Wikipedia: Safe navigation operator
(https://en.wikipedia.org/wiki/Safe\_navigation\_operator)

.. \[2\] Wikipedia: Null coalescing operator
(https://en.wikipedia.org/wiki/Null\_coalescing\_operator)

.. \[3\] FileFormat.info: Unicode Character 'THERE EXISTS' (U+2203)
(http://www.fileformat.info/info/unicode/char/2203/index.htm)

.. \[4\] python-ideas discussion thread
(https://mail.python.org/pipermail/python-ideas/2016-October/043415.html)

.. \[5\] Steven D'Aprano's critique of the proposal
(https://mail.python.org/pipermail/python-ideas/2016-October/043453.html)

.. \[6\] Considering a link to the idea of overloadable Boolean
operators
(https://mail.python.org/pipermail/python-ideas/2016-October/043447.html)

Copyright
=========

This document has been placed in the public domain under the terms of
the CC0 1.0 license: https://creativecommons.org/publicdomain/zero/1.0/

 .. Local Variables: mode: indented-text indent-tabs-mode: nil
sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
