2023-09-16

The case for closures (in Lua)

I have recently been criticized for my use of “nested” functions in Lua; the criticism revolved mostly about presumed performance problems (due to the cost of closure creation) and the claim that the usage of nested functions was “unnecessary” from a code quality standpoint. Here is my attempt at a thorough analysis, making a case for closures and more specifically the usage of “nested” functions in Lua.

Functional Programming

(Lua) closures alone suffice as Lambda Calculus - that is, closures paired with function application are Turing-complete. With higher-order functions, functional programming - fundamentally based on Lambda Calculus - is fully possible in Lua. Even just some very basic higher-order functions like map, filter or fold can already be very useful abstractions for working with iterators - most often implemented as closures, unless it is possible to efficiently wrap up their state in a single control variable + invariant state (as with ipairs and pairs) - but of course aren’t inexpensive in Lua; in much less dynamic languages like Rust, closures can often even be used as zero-cost abstractions.

Still, performance is usually not a top concern - especially if Lua is used rather than one of the classic fast compiled languages - and functional code can always be optimized to more imperative code if necessary.

“Nested” Functions

Creating closures inside other functions, even if not strictly necessary, offers various advantages:

However, these advantages also come at a cost:

Conclusion

As is usually the case in programming, tradeoffs with regards to both code quality and performance are involved when it comes to the decision of whether to use closures. Closures are essential to unlock the power of functional programming. Code quality trumps until optimization is done on the basis of profiling; closures - even if “nested functions” that could rather easily be refucktored away - should not be dismissed as “bad practice” on the basis of premature optimization (but of course - for the sake of completeness - neither are closures universally “good practice” for code quality).