[Python-ideas] Syntax for passing lambdas to functions

Andrew Barnert abarnert at yahoo.com
Wed Feb 26 20:44:51 CET 2014


From: Greg Ewing <greg.ewing at canterbury.ac.nz>

Sent: Tuesday, February 25, 2014 4:17 PM


> I was reading a blog post here:
> 
> http://stupidpythonideas.blogspot.co.nz/2014/02/fixing-lambda.html
> 
> where the author points out that there are a number
> of different problems that the various enhanced-lambda
> proposals are trying to solve, which are best addressed
> by different solutions.
> 
> Here's a suggestion for one of them. Suppose we could
> write things like:
> 
>    sorted(things, key(x) = x.date)
> 
>    Button("Do it!", on_click() = fire_the_ducks())


Of course that particular case would be better written as:

    Button("Do it!", on_click = fire_the_ducks)


But it becomes more useful if you do anything else:

    Button("Do it!", on_click() = fire_the_ducks(42))


At first glance, I think this is nice, but there's a nagging feeling that it may be a bit magical. Maybe if I think through what the compiler will do with it, I can resolve that feeling. (Obviously real users aren't going to care how it gets parsed and compiled, but if it's simple and clear enough, that implies that it can also be simple and clear to a human reader. Not perfectly, but… anyway, let's try it.) I'll do that at the end.


> It only addresses the case of passing a function using

> a keyword argument

It also doesn't look quite as nice when the function comes first, but I think it's still pretty nice:

    itertools.takewhile(predicate(x)=x<5, iterable=spam)

And functions that take a function and then *args, like map, would be tricky, but then it's fine if this doesn't work nicely in every possible case for passing a function around.

Also, this _could_ be extended to work in all cases where call expressions raise a SyntaxError, although I don't know that it _should_ be. For example, people who for whatever reason prefer to write "f = lambda x: …" instead of "def f(x): return …" today would probably love being able to write "f(x) = …", but I don't really want to encourage those people…

> but I think it would make for very
> readable code in those cases. And it doesn't use any
> colons!


I don't understand the problem with the colons, but never mind that; I agree that it's very readable. And the real benefit to me is that it doesn't require any new and potentially weird syntax, like Nick's magic ? parameter, for the one-argument case.

On the other hand, could this add any confusion? Today, we have:

    Button("Do it!", on_click=fire_the_ducks) # good

    Button("Do it!", on_click=fire_the_ducks()) # bad, passes call result as function

    Button("Do it!", on_click=lambda: fire_the_ducks()) # good

    Button("Do it!", on_click=lambda: fire_the_ducks) # bad, passes function returning function


We'd be adding:

    Button("Do it!", on_click()=fire_the_ducks()) # good

    Button("Do it!", on_click()=fire_the_ducks) # bad, passes function returning function


Would that last case add to the novices' confusion?

And now, the parsing:

First, in the grammar (see 6.3.4 Calls), you have to expand the left side of keyword_item.

The simplest idea is:

    keyword_item = identifier [ "(" [parameter-list] ")" ] "=" expression

Then, the keyword AST node expands to take all the same args-related attributes of the Lambda node:

    keyword(arg="on_click", args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwargs=None, value=Call(…))

Then, at compile time, if args is None, you just compile arg and value as today and push them on the stack; if it's not (even an empty list), you instead compile args, vararg, …, value together as a function, just as you would a Lambda node except that "body" is called "value",then push the arg and that function on the stack.

That doesn't feel right. On the other hand, there's a simpler way to do it:


    keyword_item ::= simple_keyword_item | functional_keyword_item
    simple_keyword_item = identifier "=" expression
    functional_keyword_item = identifier "(" [parameter_list] ")" "=" expression

Now, simple_keyword_item parses to the same keyword AST node as today, and functional_keyword_item also parses into a normal keyword node, which has a normal Lambda node as a value, built from the parameter_list and expression the exact same way as in a lambda expression.

That seems pretty clear and simple, but now at the AST level there's no way to distinguish between "key(x)=x.date" and "key=lambda x: x.date". Is that acceptable?

Last, there's always a hybrid: create a new KeyLambda node that has the same attributes as Lambda and compiles the same but can be distinguished from it by type, and maybe even a funckeyword that's identical to keyword as well. Then, no magic, and no irreversible parse either.


More information about the Python-ideas mailing list