[Python-ideas] Infix functions

Steven D'Aprano steve at pearwood.info
Tue Feb 25 02:47:44 CET 2014


On Mon, Feb 24, 2014 at 04:11:19PM -0800, Andrew Barnert wrote:
> On Feb 24, 2014, at 7:28, Steven D'Aprano <steve at pearwood.info> wrote:
> 
> > but really, these are just jokes. I'm still no closer to actual 
> > use-cases.
> 
> I will repeat one that I already posted, but try to put it 
> differently: a using function that wraps a with statement.
> 
> Every kind of statement in Python would sometimes be useful as an 
> expression. You can always wrap the statement in a function, but it 
> looks ugly--even with Nick's concise lambda proposal. For example, 
> lets say we wrapped with, try/except, and if/else in functions named 
> using, catch, and cond:
> 
>     data = using(:?.read(), open(path))
>     buf = catch(:read_file(path), (FileNotFoundError, :'')
>     b = cond(a, :1/a, :NaN)
> 
> All of these are possible today, 

You must have a different understanding of "possible" than I do, because 
they all give me syntax errors.


> but were not sufficient to curb the desire for with, except, and if 
> expressions. One was already added, one is under consideration, and 
> the last gets suggested at least once/year.
> 
> 
> But if you could infix those functions:
> 
>     data = :?.read() `using` open(path)
>     buf = :read_file(path) `catch` FileNotFoundError, :''
>     b = a `cond` :1/a, :NaN
> 
> Would they then be good enough to obviate the need for new syntax?

All of those are completely unreadable messes to me. If you're trying to 
sell the idea of one new controversial feature, you really shouldn't 
demonstrate it with another controversial new syntax.

Let me try to make your argument for you, I hope fairly. You're hoping 
that by introducing custom infix binary operators, you can replace 
syntax for (say)

    result = 23 if condition else 42

with 

    result = 23 `if_operator` condition, 42

or something similar. The fact that ternary if already exists isn't 
important here -- it is your argument that if we had custom infix 
operators, we wouldn't have needed to add ternary if.

I don't think this suggestion comes even close to working well. Without 
proper support interpreter support for ternary operators, forcing three 
arguments around a binary operator is never going to look right, and 
may have precedence issues. And it lacks support for lazy evaluation, 
which means you're stuck with an ugly work-around of wrapping operands 
in functions and delaying calling the function when you want laziness:

    # define the two getter functions elsewhere:
    result = (get_true_value `if_operator` condition, get_false_value)()

    # in-place, using lambda:
    result = ((lambda: true_expression) `if_operator` condition, 
              (lambda: false_expression))()

    # using proposed lambda alternative
    result = (:true_expression `if_operator` condition, 
              :false_expression)()


None of which are as good as the syntax we have for ternary if. This 
suggests that dedicated syntax beats custom syntax.

[Aside: one can remove the outer pair of parentheses by embedding the 
final call inside the custom operator, but that would mean you *always* 
have to use functions as arguments, even when lazy evaluation is 
unnecessary. That's a bad idea.]


Not to put too fine a point to it, mapping ternary statements like 
if...else and try...except to an infix binary operator sucks. 
Fortunately there are only two of those, one is already syntax and the 
other is subject to a PEP. What's left? There aren't that many 
expressions left...

    for-loops can be written as list comps, and besides they 
    require a name-binding so don't fit well to this model;

    del doesn't need to be an expression, because it doesn't 
    return anything;

    likewise for pass;

    assignment-as-expression goes against the grain of the 
    language;

    import can be written as __import__ if you really must;

    it might be handy to raise an exception from inside an 
    expression, but I don't see that this maps to your proposal;

    classes can be created with type();

    and functions with lambda.


Have I missed anything?

This leaves perhaps while loops and with statement, and I don't think 
that they're going to map any better to this proposal than did if.

    result = expression `while_operator` condition

doesn't work for me. It's unclear that the result is a list (or a tuple? 
set? lazy generator?), but even if we decided that you can map while and 
with statements to infix binary operators, I don't think that having an 
over-generalised system like this is an improvement over dedicated 
while-comprehensions and with-expressions.

Besides, there is strong value in having (say) a single way to spell a 
for expression, namely [blah for name in seq], rather than a plethora of 
custom infix operators:

    # assume there is magic for name-binding somewhere
    result = blah `for_operator` name, seq
    result = blah `for_loop` seq, name
    result = name, blah `do` seq
    result = seq `repeat` name, blah


etc., depending on the personal preferences of the coder writing it.

I've given this idea a good shake, and I think it's a limp rag. I don't 
think that mapping statements to infix binary operators in this way is a 
workable idea.

I'll be honest, it seems to be *so grossly and fundamentally unworkable* 
to me that I'm kind of astonished that you've raised it not once but 
twice, instead of discarding it as ridiculous, like the idea of a paper 
submarine or a hot iron balloon. Perhaps there are other requirements 
needed to make this work that you haven't mentioned and I haven't 
thought of? Sorry to be so dismissive, but that's the truth of it.


[...]
> > Any infix binary operator a `op b can be written as a function of two 
> > arguments, op(a, b).
> 
> Of course. The whole point of the idea--in the very first paragraph of 
> the initial post--is that a `op` b would be compile to the exact same 
> code as op(a, b). (Others have suggested that maybe a method would be 
> better, but that doesn't change the point.) This is pure syntactic 
> sugar for those functions, nothing else.
> 
> > It's not that there are no use-cases for infix 
> > binary operators, but that all the obvious ones (such as string 
> > concatenation, comparisons, list repetition, etc.) already exist and the 
> > rest can nearly always be easily written as functions.
> 
> The question is not whether they can be written as functions, but 
> whether those functions' calls can be read more easily with infix 
> syntax.

That's not the only question. A very important question is whether it is 
worth adding all this generalized infix operator machinary just so that 
people can write:

    x `becomes` y

rather than

    becomes(x, y)


If this is *pure syntactic sugar* with absolutely no extra features, as 
you suggest, then I think we're just wasting our time discussing it. 
It's not that I'm entirely against sugar, I like the fact that I can do 
string repetition with "spam"*23 rather than "spam".repeat(23), but 
(re-iterating what I said earlier) all the important and obvious 
flavours of sugar have already been done. Adding more will give you 
source code diabetes.

If we've missed one or two use-cases, perhaps we can add operator 
support for those, e.g. "spam" - "eggs" (whatever that might mean!), 
without needing to over-generalise the concept to support arbitrary 
operators everywhere.


-- 
Steven


More information about the Python-ideas mailing list