Why not real anonymous functions?

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.

3 Likes