On Sat, Sep 19, 2015 at 7:06 AM, Steven D'Aprano <steve@pearwood.info> wrote:
On Sat, Sep 19, 2015 at 03:17:07AM -0500, C Anthony Risinger wrote:

> I really liked this whole thread, and I largely still do -- I?think -- but
> I'm not sure I like how `?` suddenly prevents whole blocks of code from
> being evaluated. Anything within the (...) or [...] is now skipped (IIUC)
> just because a `?` was added, which seems like it could have side effects
> on the surrounding state, especially since I expect people will use it for
> squashing/silencing or as a convenient trick after the fact, possibly in
> code they did not originally write.

I don't think this is any different from other short-circuiting
operators, particularly `and` and the ternary `if` operator:

result = obj and obj.method(expression)

result = obj.method(expression) if obj else default

In both cases, `expression` is not evaluated if obj is falsey. That's
the whole point.

Sure, but those all have white space and I can read what's happening. The `?` could appear anywhere without break. I don't like that, but, opinion.
 
> If the original example included a `?` like so:
>
>     response = json.dumps?({
>         'created': created?.isoformat(),
>         'updated': updated?.isoformat(),
>         ...
>     })
>
> should "dumps" be None, the additional `?` (although though you can barely
> see it) prevents *everything else* from executing.

We're still discussing the syntax and semantics of this, so I could be
wrong, but my understanding of this is that the *first* question mark
prevents the expressions in the parens from being executed:

json.dumps?( ... )

evaluates as None if json.dumps is None, otherwise it evaluates the
arguments and calls the dumps object. In other words, rather like this:

_temp = json.dumps  # temporary value
if _temp is None:
    response = None
else:
    response = _temp({
        'created': None if created is None else created.isoformat(),
        'updated': None if updated is None else updated.isoformat(),
        ...
        })
del _temp


except the _temp name isn't actually used. The whole point is to avoid
evaluating an expression (attribute looking, index/key lookup, function
call) which will fail if the object is None, and if you're not going to
call the function, why evaluate the arguments to the function?
 
Yes that is how I understand it as well. I'm suggesting it's hard to see. I understand the concept as "None cancellation", because if the left is None, the right is cancelled. This lead me here:

* This is great, want to use all the time!
* First-level language support, shouldn't I use? Does feels useful/natural
* How can I make my APIs cancellation-friendly?
* I can write None-centric APIs, that often collapse to None
* Now maybe user code does stuff like `patient.visits?.september?.records` to get all records in September (if any, else None)
* Since both `?` points would *prefer* None, if the result is None, I now have to jump around looking for who done it
* If I don't have debugger ATM, I'm breaking it up a lot for good 'ol print(...), only way
* I don't think I like this any more :(

I especially don't like the idea of seeing it multiple times quickly, and the potential impact to debugging. The truth is I want to like this but I feel like it opens a can of worms (as seen by all the wild operators this proposal "naturally" suggests).
 
> Usually when I want to use this pattern, I find I just need to write things
> out more. The concept itself vaguely reminds me of PHP's use of `@` for
> squashing errors.

I had to look up PHP's @ and I must say I'm rather horrified. According
to the docs, all it does is suppress the error reporting, it does
nothing to prevent or recover from errors. There's not really an
equivalent in Python, but I suppose this is the closest:

# similar to PHP's $result = @(expression);
try:
    result = expression
except:
    result = None


This is nothing like this proposal. It doesn't suppress arbitrary
errors. It's more like a conditional:

# result = obj?(expression)
if obj is None:
    result = None
else:
    result = obj(expression)


If `expression` raises an exception, it will still be raised, but only
if it is actually evaluated, just like anything else protected by an
if...else or short-circuit operator.

I did say vaguely :) but it is extremely hideous I agree. The part that made me think of this is the would be desire for things to become None (so, or example, wanting to avoid throwing typed/informative exceptions if possible) so they'd then be more useful with `?`.

--

C Anthony