This is a generic question about using anonymous functions as arguments to other functions. I give an example in Python, but the question is not about Python (and I'm particularly interested in whether answers might be different for functional languages than for other paradigms).
In Python it is common to pass an anonymous function as an argument to another function, for example:
sorted(my_data_structure, key=lambda x: x[0].foo)
where lambda x: x[0].foo
is an anonymous function to return the foo
attribute of the 0th element of each thing in my_data_structure
(under the assumption that whatever lives in my_data_structure
will have a 0th item and that 0th item will have a foo
attribute).
To my mind, in all but the simplest cases, this presents a problem for encapsulation and extensibility: what if my_data_structure
changes? What if the attribute to sort by becomes bar
in a later code refactoring? Now you've got to descend into the actual lambda
to deal with it.
Alternatively, this could have been written as:
def my_helper(some_data_thing):
"""My intentions are ____ !
"""
return some_data_thing[0].foo
#Elsewhere in the code:
sorted(my_data_structure, key=my_helper)
While this adds 4 lines of code, it is more readable. You don't have to parse whatever the lambda
is trying to do at the moment you read about the call to sorted
. It gives the opportunity for type annotation, documentation, or even unit-testing to occur for the helper function.
If something changes, but you still want the local code around the use of sorted
to remain the same, this nicely encapsulates that for you away into the helper function. And if more than one place needed to sort, or group, or whatever, by the same key, you get code reuse better than reproducing the lambda
is many locations.
However, others might also say that you are bending over backwards to give a name, my_helper
, to a little function that is actually more clear to just see it in code directly.
I can agree with this to an extent, especially when the lambda
involved is very small, but what if it needs to be a multi-line lambda
that runs into a lot of indentation issues, or involves a somewhat obfuscated list comprehension or some other device?
My question is: what approaches exist for determining where to draw the line between a useful in-line anonymous function that is "self-contained" enough that it is more effective to write it directly versus an in-line function that is too complex and should be wrapped in a helper function. Are there other considerations I'm not taking into account?
For example: this post on this issue in JavaScript states and affirms many of the concerns I've listed. Is this codified for programming in general? Is it language or paradigm-specific? And what about the other side of the debate?
1 Answer 1
You draw the line based on length and repetition. A lambda is okay if it's short and not repeated. Once you get into multiple lines, or are repeating it multiple times, you should really give it a name. If your data structure changes, it's no big deal to change one or two short lambdas, especially in a strong statically typed language. For some reason, though, lambdas give people a blind spot to repetition.
JavaScript is really the only language I've seen where multi-line anonymous functions are commonly accepted. I'm not sure what it is about the language that makes it that way. It may be more a cultural thing. That doesn't make it a best practice, however, just a common one. Also, the same rules apply about repetition. More so, since repetition of longer functions is worse.
-
JavaScript is by far not the only language. Most language with good support for functional programming are used in that way. Scala is a good example. The problems mentioned by the OP do not even occur here, because it has functions and is statically typed so the compiler will complain immediatly if the data structures api changes.valenterry– valenterry2014年12月15日 20:22:43 +00:00Commented Dec 15, 2014 at 20:22
-
Actually, @valenterry the actual use case in my team's project is a series of poorly written multi-line anonymous functions in an in-house LiveScript-like functional language that is in some ways very similar to scala (and it has static typing). Even in Haskell you can get really bad multi-line lambdas and though the compiler can easily deal with their type signature, humans reading the code sometimes have no hope.user103181– user1031812014年12月15日 23:12:42 +00:00Commented Dec 15, 2014 at 23:12
-
Well, sure. Anonymous functions are used because we are too lazy to write an own stragety-pattern-like class for each single anonymous function. But if there are more of them or the same one is used multiple times, one should write it once and use it when needed. I would say even two times writing the same anonymous function already shows that it need to be refactored. The approach to decide is always "what cost less in the long term and when does it have to be finished". Unless its extreme time pressure, refactor instead of doing copy&paste even once. That is how I do it.valenterry– valenterry2014年12月15日 23:20:57 +00:00Commented Dec 15, 2014 at 23:20
what if my_data_structure changes? What if the attribute to sort by becomes bar in a later code refactoring?
The data structure changing shouldn't stop you from sorting it. If the data structure's elements change that's a breaking API change that breaks other parts of the code too, so you have more to worry about than just updating the lambda.