[Python-ideas] PEP 505 (None coalescing operators) thoughts

Andrew Barnert abarnert at yahoo.com
Fri Oct 2 21:56:47 CEST 2015


On Oct 2, 2015, at 10:47, Ron Adam <ron3200 at gmail.com> wrote:
> 
>> On 10/02/2015 07:54 AM, Random832 wrote:
>>> On Fri, Oct 2, 2015, at 06:01, Ron Adam wrote:
>>>> On 10/01/2015 04:12 PM, Random832 wrote:
>>>> On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
>>>>>> And to go out on a limb...;-)
>>>>>> 
>>>>>> Another possibility is to have a*special magic callable*  that when
>>>>>> called skips the argument evaluation and returns None.
>>> 
>>>> That's dangerous talk indeed. Special magic callables are Lisp
>>>> territory.;)
>>> 
>>> It's also lambda calculus territory.
>> 
>> Does lambda calculus really have a notion of two types of function that
>> are called with the same syntax but one receives the values as they have
>> been evaluated and the other receives some abstract representation of
>> how to get them if it needs them? That doesn't even make any sense. Does
>> it even have a notion of side effects and therefore when/whether things
>> are evaluated if they are not needed?
>> 
>> Not everything in Lisp is in lambda calculus.
> 
> I wasn't really comparing it to lisp, but I understood your reference to it.  The comparison to lambda calculus is very loose also.

The comparison to Lisp is very good. Lisp has the exact concept you're asking for: a way to define a function that receives the expressions used as its arguments rather than their evaluated values.

The comparison to lambda calculus doesn't even make sense. It does even have the concept of the string of lambda calculus symbols (or any other parallel) as a thing that can be passed around.

> BTW, in lisp (and scheme) there are a handful of special functions.  One of those is the if function which avoids early evaluation of the expressions till after the condition is evaluated.  It can be implemented as a normal lisp expression but not without quoting the arguments.

Lisp and scheme also let you define new special functions that automatically get the quoted form of their arguments. (Modern Lisps don't usually have fexprs, but they have macros and special-forms, which are close enough for this discussion, I think.) And that's exactly what you're asking for here. The fact that you can define an if special form that doesn't require the caller to quote the arguments means the language doesn't need flow control expressions; you can define them yourself. There are both pros and cons to that, but the cons make it highly unsuitable for Python.

>>> I realized this evening the parser doesn't need to know at parse time,
>>> and the object doesn't need to be special.  It's the syntax that gives
>>> it the specialness, not the object.  So what I was thinking is still
>>> possible, but it would work more like the other suggestions.
>>> 
>>> If you look at byte code generated for a function call...
>>> 
>>> You will notice the function is loaded on the stack *before* the
>>> argument expressions are evaluated.
>>> 
>>> It won't require generating lamba expressions, just a conditional jump
>>> where '?(' is used.  So no, it won't be required at every call site.
>> 
>> What does this have to do with the idea of a magic callable?
> 
> As I said above it doesn't need to magic after all.
> 
>> If this is
>> implemented with a magic callable then A) *any* callable might be a
>> magic callable and B) the idea original idea strongly implied a
>> generalized notion of magic callables, of which NoneCall would be only
>> one example. How do you pass the arguments to the *other* magic
>> callables that *do* [maybe] evaluate them?
>> 
>> You've basically just explained how the bytecode works for ?( if it is
>> *not* implemented with a magic callable. So how does making it NoneCall
>> instead of just None improve anything?
> 
> It's not quite the same.  The proposed ?( skips the arguments if the right hand side is None.
> 
>         None?(...)   ---> None
> 
> So you can't use that to supply a default directly.
> 
>        value = None?(default)    # returns None (not useful here)

Again, why would you want that to return default? 

Imagine you write spam = eggs?(default), hoping to get default if eggs is None. What are you expecting if spam is not None then?

> To make that work, think of applying None to the arguments.  Which in turn does nothing to them, they just get returned.
> 
>        value = None?(default)   --> default

Functions don't take one argument; they take zero or more positional arguments and zero or more keyword arguments. And there are probably more calls with 0 or 2 arguments than 1 in an average program (at least if you don't count the implicit self in method calls). So, what does it mean to return "the argument"? The most reasonable answer I can imagine is this:

    def ident(*args, **kw):
        return args, kw

But that means that to actually use it, you have to write something like this:

    eggs = spam?(default)
    try:
        args, kw = default
        eggs = args[0]
    except (IndexError, ValueError):
        pass

And even that isn't correct if the normal return value of non-None spam(default) happens to be an iterable of two elements whose first element is a non-empty sequence. And, even if it were correct, it seems a lot less readable than:

    eggs = spam(default) if spam is not None else default

By comparison, returning None no matter what the arguments are makes sense in a way that returning the arguments doesn't: because None is a potentially usable value (especially with further chaining), unlike the arguments pair of collections.

> But to get the inverse of that which is the original preposed ?( behavior, you need a different way to trigger that.  We could use False I suppose.
> 
>       value = False?(default)   --> None
> 
> And of course....
> 
>       value = other?(default)   -->  other(default)
> 
> 
> 
> This is just based on the thought that a conditional ?( calling syntax could be useful in a broader scope and not just used as a None coalescing operator.
> 
> So in the above, None, False, and other can be replaced by expressions.
> 
>     def when(x):
>         return None if x else False
> 
>     value = when(cond)?(expr)       # expr or None
> 
> 
> 
> With the original proposed ?( operator the set default example is still doable, but it needs a helper function.
> 
>       None?(expr) --> None
> 
>       other?(expr) --> other(expr)    # The True case
> 
> 
>     def is_none(cond):
>         return None if cond is None else lambda x:x
> 
>     value = is_none(value)?(default_value)
> 
> 
> Compared to
> 
>     value = default_value if expr is None else None
> 
> 
> 
> I was trying to shorten it to..
> 
>    value = expr?(default)
> 
> But maybe that just won't work in a nice enough way.
> 
> Well it's just a thought/suggestion.  (shrug)
> 
> 
> Cheers,
>   Ron


More information about the Python-ideas mailing list