[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