Why not real anonymous functions?

Did you search this forum for previous discussions? If not, why do you assume that other people will? Or would you have found a page on the Python wiki about rejected ideas?

A wiki has at least the virtue that it can be a quick and easy response to a future thread of this nature. Asking people to please read the previous discussions never works.

Yes, a good bit. But for full details on the PEP process, have a read here: PEP 1 – PEP Purpose and Guidelines | peps.python.org

The purpose is to raise questions and propose ideas for new features in Python, yes. But repeating old discussions is not the purpose, and “doing your homework and looking for past discussions” is generally considered part of the price of entry (informally - it’s not a forum rule or anything). Many people either don’t do that, or at best say they looked but couldn’t find anything (which is fine up to a point, but someone needs to do the research at the end of the day).

The point of a wiki page is to make “doing that research” a little easier. Personally, I’m not sure it will help enough, or give enough detail, and many of the people who post don’t seem to do even minimal research, so a wiki page could end up being just another place they didn’t look :slightly_frowning_face:

… and yet, when you were told that there were plenty of previous discussions on the various forums and mailing lists on this topic, you didn’t find them. I think that’s the fundamental problem here - finding previous discussions takes genuine research, and many posters don’t have the time or inclination to do that research, instead relying on the “old hands” to repeat the arguments. And those “old hands” are frankly getting burned out from being expected to do people’s research for them.

(To be clear, this thread is just an example of this phenomenon, and by no means the worst. It’s a general problem with the “Python ideas” group as a whole.)

7 Likes

Yes, exactly :smile:

Would you mind linking this on the main wiki page so that it’s findable?

The discussion for this thread is here. Please, everyone, feel free to edit it. I did not go through this whole thread; I was more concerned with the structure.

4 Likes

I personally don’t think it needs to be on the front page. It’s not like people are going to check the front page of the wiki before posting ideas, so having the link there won’t really help anyone. As long as the pages exist and are findable, that should suffice.

I do some programming in the functional paradigm, and I’ve previously spent a fair bit of time searching and reading about multi-line lambda possibilities on Python, and I found myself annoyed that nothing like that existed.

Your post has me starting to lean in the direction of thinking that multi-line lambdas or similar are not a good idea. (I might have thought about those ideas in the past, but I’d forgotten by now.)

The main reason I wanted them was to be able to always initialize my variables to a non-None value at the time of their first appearance in order to avoid the bug of using an uninitialized variable. Of course, that can be accomplished with a function. The secondary reason I wanted them was that it is nicer IMO to have the definition of the function close to the site where it is used in two case: 1) for ease of reading; 2) for ease of writing, a) either temporarily (to later be moved) or because b) it is a short context-specific function that doesn’t need to be reused elsewhere.

In C++, I can accomplish the purpose 2a easily with a lambda because it doesn’t capture any of the the surrounding context by default (i.e., it is not a closure by default). To do that in Python, I have to put my function in a class. For example:

class DoInterestingThings:
    @staticmethod
    def do_it(*args, **kwargs):
    """Does useful and interesting things, I promise"""

this_one_takes_a_func_arg("foo", 42, DoInteresttingThiings.do_it)

Then at a later date, I can cut and paste that to an outside scope to be able to reuse it in other places without having to worry that it is dependent on some captured variables. Also, I can avoid the possible bug of unintentionally depending on captured variables.

I’ve used that in my Python code. The main problem with it is that it is not idiomatic. I expected that someone might, at some point, find something like that in my code and think “WHY??”. So I have a comment about it in the code as well, and so all together the approach takes a few extra lines of code (when compared with a C++ lambda). I’m somewhat of a novice at Python. There might be a way to do that with metaclasses or decorators that I don’t know about.

What I especially like Python for is rapid development, but I also want to avoid common sources of bugs. That’s why a workflow like I’ve just described appeals to me.

I think I agree (or am leaning toward agreement) that, in general, it would be nicer for readers of one’s code if long anonymous functions are avoided. Regarding getting the function definition close to the site where it is used for better readability (purposes 1 and 2b above): for something that is a single expression a lambda could work, but 2 or 3 lines in many cases wouldn’t be bad, and 1 line could be too constraining sometimes; for longer functions, I liked @kfdf 's tail def example in a later post of this topic: Why not real anonymous functions? - #64 by kfdf.

It might be nicer in most cases to avoid multi-line lambdas or their equivalents, but it seems too bad that there isn’t the flexibility that their existence could make available.

So you want to trade a nice and clear NameError with a potentially confusing AttributeError in a potentially completely unrelated part of the code? Why? Python isn’t C/C++. You don’t have undefined behavior.

That does absolutely nothing to prevent the capturing of variables. It is quite literally completely equivalent to just have it outside the class in terms of how the function behaves if accessed from the class object.

2 Likes

Thanks. You are right.
What I actually want is a TypeError rather than a NameError in some cases. I’ll give that more thought. Maybe I am totally misguided about this.

Because multiline lambdas are the software equivalent of a run-on sentence. It’s one of the things I dislike the most about Typescript and Javascript.

BTW, in Python you’re almost always better off using a (list/set/dict) comprehension, generator expression, or something from the operator module - rather than using even a single-line lambda.

2 Likes

I haven’t read through the whole discussion, but I would say that the biggest thing that has changed in the last ~18 years is the introduction of JavaScript fat arrow function. I believe most JS developers are quite fond of them, and the cases in which they are used in JS would not be all too different from the ones we have in Python. Might be just me, but I find the code that uses fat arrows to be much more readable than the one without it. So why not have the same readability boost in Python?

2 Likes

Then please do that before making any further statement. One of the central points of this discussion is that people not reading the old discussions to check if the arguments are actually invalidated just annoys everyone.

1 Like

That’s fair. It’s worth noting that JS code uses arrow functions for a lot of things that Python does in other ways (you’ll see a lot more map() in JS than Python), so the use-case isn’t as strong. A quick search of some of my JS code shows that most of the arrow functions I have are:

  1. Passed to map() where a comprehension would probably be better
  2. Passed to sort() because the default JavaScript sort order is moronic
  3. Or, very very commonly, defining event handlers, which in Python can be WAY better written with a decorator (instead of on(....., e => { ... }); you could do @on(...) def handler(): to express the same concept).

So, which JS usage would also translate into Python, and can’t be done with lambda as it currently is?

5 Likes

Be reasonable :slight_smile: Not everyone can afford to spend their entire days reading all of python-ideas. That’s one of the advantages of a PEP - you can ask people to please read the PEP before contributing to the discussion, and it’s a lot less daunting than “please read this thread and all the ones linked to for previous discussions”.

4 Likes

True, I should have directed them to the wiki page, that is what it’s there for after all. But IMO this kind of attitude, i.e. a willingness to rehash discussions again and again is not going to result into a good experience for anyone. It is just going to lead to even longer discussions that even fewer people will read. JS and it’s inline function definitions already have been discussed here, which a quick search could have revealed.

1 Like

I would say that in addition to these three, it is also used for simple closures. So in the code I saw, I would occasionally see something like this:

function makeOuter(firstNumber) {
  return (secondNumber) => {
    const retval = someTransformation(secondNumber);
    return firstNumber + retval;
  };
}

const someFunc = makeOuter(5);

Forcing me to name an inner function here feels like a waste. If a function name acts like a “forced comment”, in cases of rather straightforward closures it adds nothing and just makes the code less clean, at least in my humble opinion.

1 Like

Hello folks, first comment here. I wanted to supply some motivating use cases for “anonymous functions” that I am using all the time coming from C++ and other languages with similar capability. Some of the use cases people describe somewhat dismissively are in my opinion huge benefits to readability and program maintenance.

First, constant values that require some minor local setup benefit from small initializing functions:

// Python doesn't really have const but still a vital convention

// imagine how much less readable the below would be if I had to define an out of line
// function for every one-off scoping of a few statements

const auto data = []{
  auto packet = get_thing();
  assert(packet.valid());
  return packet.payload;
}();

// now everyone can see data is assigned once and not intended to be changed

// I also don't have that temporary packet object hanging around to be accidentally touched again

Similar to the above, little scopes (enabled by immediately executed functions) can section off names, resources, etc. This is huge in combination with scope-based resource management, e.g. with/using etc:

const auto widget{};
// debug checks (can collapse the below section in your editor)
{
  lock_guard l(g_log_mutex); // unlocked at end of this scope
  assert(widget.online());
  log("the widget says: ");
  log(widget.print());
}
// in languages that lack braced scopes you can accomplish the above with
// immediately invoked functions or lambdas

// anything I didn't want to intrude on the surrounding scope is now contained, resources are
// freed automatically

I saw some comments above dismissively saying “it just saves you one line” vs having to define a function separately. This is actually a big savings in many contexts! First, everyone knows the hardest problem in coding is naming things, and forcing the user to give a name to a tiny snippet of code immediately halts your thinking as you give pointless names to functions every time you intend to supply behaviors as arguments. This also increases the potential distance between the point of definition of a callback and its point of use, which is a source of errors in code under maintenance.

def widget1_onhover_action(): ...
def widget1_onclick_action(): ...
# this sure is tedious
library.add_widget(widget1_onhover_action, widget1_onclick_action, ...)

# now I have a bunch of functions in my scope that I have to undefine (even more visual noise)
# or they're stuck cluttering up my scope. (Imagine how easy it would be to mistype or tab-complete
# widget1_action instead of widget2_action in the middle of setting up multiple things)

# this is even more tiresome if you want each of these callbacks to be a stateful thing because now 
# that's a class

After all, nobody would say string literals shouldn’t be immediately usable in expressions. If you had to assign every string literal to a named definition or variable first before you could pass it to something else your code would be littered with terribly named little strings. It’s good that string literals function as expressions in every language, and functions should too.

And even in situations where you do want to define some things separate from the point of use, the use of immediately executed functions can limit their visual and lexical scope without a separate definition, to resemble the following C++ pattern:

// setup buttons
{
  auto Click = []{ ... };
  auto Hover = []{ ... };
  library.addWidget("foo", Click, Hover);
} // all that stuff is out of scope and doesn't need unique names

In languages that lack scoped blocks I’m always using anonymous functions to emulate this behavior, which is useful for organizing and containing complexity within scopes. For example, in PowerShell you can just use an immediately-invoked code block:

# the below code immediately executes but variables declared within it don't leak out
&{
  @("foo", "bar") | Out-Host
}

I was completely floored when trying to do non-trivial Python code for the first time and learning that there’s still no corresponding idioms to enable this sort of cleanliness. It’s function definitions and classes all over the place to do trivial one-off callback tasks. It’s like the situation before “modern C++”, when in order to use most algorithms you had to define a class or function out of line somewhere first. Consequently almost nobody actually did this because it stinks.

Python at least has single-expression lambdas but these are extremely limiting unless you use some dense language constructs to do multi-step transformations on data in one giant expression with five levels of parens and brackets to avoid ever assigning any intermediate variables. A multi-line lambda or anonymous function is in comparison much more readable.

4 Likes

Can you provide pseudo-Python code for the above C++ snippets?

7 Likes

If you are ok with using a not well known idiom, you can already do this:

T = TypeVar('T')

def init(func: Callable[[], T]) -> T:
    return func()
    

@init
def data() -> SomeDataType:
    package = get_thing()
    assert packet.valid()
    return package.payload

data is now such a constant. Type checkers should already understand it, although PyCharm doesn’t like it, but that is something that the IDE devs should fix.

You can then also name the function _ to signal that it shouldn’t be used to get a quick local scope:

def scope(func: Callable[[], None]) -> None:
    return func()

@scope
def _():
    ...

I guess the pattern isn’t all that common because the need for it isn’t that high. Most of the time just executing the code and optionally del the temporary variables is enough.

Also, IMO you are failing to understand the primary opposition. It isn’t that there aren’t good usecases. It’s that we don’t know of an acceptable syntax (C++ is bracket based, so it doesn’t translate well) or of usecase so overwhelming strong that a complete break in python’s syntactic structure is acceptable. (neither of the two you mention clear that bar IMO)

3 Likes

That’s fine, I just saw a couple comments dismissing the value of the use case. I think it’s extremely valuable!

This is a clever trick! But just contorted and strange enough to probably not be permitted on any team.

My Python is weak, but I think every time I use it I’m surprised that multi-statement lambdas don’t just work:

(lambda:
    do stuff
    do more stuff
)()

Sorry if I’m missing some obvious language implementation issue that would make those really hard to bring to reality. Certainly experts have debated how this should look for many years; I’m just a late-coming user who doesn’t like the way things are.

In python, there is currently the very simple rule that within () (or [], {}) newline and indentation is ignored (outside of strings ofcourse). Whatever syntax for multiline lambdas gets added has to break this to be actually useful. While the new PEG parser can for sure deal with this, this is major shift in design and it will require retraining of all human parsers as well, and it will (in the opinion of many, including me) make the language harder to explain.

5 Likes

I definitely agree with you that this is the only meaningful objection, and it is a real one. However I also agree with @voidstar that there has been a decent amount of commentary on this thread that questions the justification for such a construct at all. I’m not sure how many developers realize that Python is the odd one out in most mainstream languages for not supporting fully expressive anonymous function literals.