On Tue, Oct 23, 2018 at 8:04 PM Vladimir Filipović <hemflit@gmail.com> wrote:
Chris, I'm happy to work with you to hammer out comparisons of various solutions.
But I can't take on the role of an advocate for "multi-statement lambdas". I don't even understand what precisely that covers, since we don't have uni-statement lambdas. _If that role would be needed for this discussion, the rest of what I'm about to write may be a waste of your time!_
If you were confused by my use of the generic "you", then I apologize. This wasn't aimed personally at you, but more generally at "this is what we need". And you're right that we don't have uni-statement lambdas, but I suspect people would be more confused by "suite lambdas" as an alternative to "expression lambdas", unless they happen to be familiar with the internal details of CPython's grammar :) I suppose in theory you could design a syntax for lambda functions that permits a simple_stmt, or maybe a compound_stmt, but both would be as messy as supporting a full suite (or multiple statements, in vernacular) and would be unnecessarily restrictive.
The central point of Python's lambdas, for me, is that when one needs to refer to a _very simple_, _once-off_ function, lambdas _make the code better._ The ways they make it better go beyond saving vertical space and can be subtle: they improve the locality of code by allowing the function's definition to be exactly in the place where it's used; a lot like the difference between (in pseudocode) `output("Hello, world")` and having to say `String hw = new String("Hello, world"); output(hw)`.
My view, which I suspect is the same as yours but written in different words, is that a lambda function should be treated as a block of code inside another function, and not as its own entity. You don't give a name to the block of code between "if" and "else", because it's just code as part of the implementation of its containing function. You don't give a name to the implicit function created by a list comprehension, because it's just ... you get the idea.
Okay now, what does the decorator solution do in that WebSocket situation that just defining named functions doesn't already?
I suppose it helps somewhat with property #2 above (finding a place for the definition). Outside of that list, it lets us shorten the constructor call.
Am I missing something? Or am I moving the goalposts - were you explicitly trying to solve something else?
Part of getting a proposal accepted is showing that there is no adequate solution using existing tools. The best way to show that is to come up with the best possible solution that we currently have, and demonstrate (if it isn't self-evident) that it's sub-par. The decorator solution is one of two that came to my mind as existing options (the other being an abuse of class syntax). Criticizing it is exactly correct, and is a way to support the assertion "we need better lambda functions".
Let me try to anticipate one more existing solution (the best I came up with):
Where the statement is about updating counters or similar state variables, we can make them items in a dict (or collections.Counter) instead, and update them in a lambda via dict.update().
It's not terrible, but it means all other references to the state variable must change in a way that makes them less clear. ("Why are they using this dict at all? Ohh, because lambda.")
And having to replace `c += 1` with `d.update(c = d['c'] + 1)` or `counter.update(c=1)` is still ugly.
Also a point worth raising. Currently, lambda functions are permitted to mutate objects in surrounding scopes, but not to rebind them. Well, actually, PEP 572 might well be the solution there, but then you reopen all that lovely controversy... I mentioned abusing class syntax as an alternative solution. This would require a new API, but it wouldn't be hard to write a wrapper. It'd end up something like this: @args(websocket.WebSocketApp, url) class ws: def on_open(...): def on_message(...): The decorator could look something like this (untested): def args(func, *a, **kw): def wrapper(cls): for name, val in cls.__dict__.items(): if not name.startswith("_"): assert name not in kw kw[name] = val return func(*a, **kw) return wrapper In some contexts, that would be every bit as good as a lambda-based solution. To compete, the proposed lambda syntax would have to be better than all these - by no means impossible, but it's a target to aim at. ChrisA