In this post we’ll explore the meaning of functional programming with a focus on closures, the one feature crucial to all the modern frameworks mentioned above.
First, there’s an initial misconception about functional programming we should clear up. Many people believe functional programming is about organizing code into functions with distinct purposes. That paradigm is best known as procedural programming.
Opinions about the definition of functional programming—and even vigorous debates—vary widely, but there’s clear consensus on two language features it requires: it must treat functions as data (allowing assigning them to variables, passing them as parameters to other functions, etc.) and it must support closures.
Functions acting as data are a well-known trait familiar to anyone who’s worked with the
setTimeout function or jQuery event and AJAX APIs. Closures, more formally known as lexical scope, are a way to achieve data encapsulation. Given nested function definitions, they permit data access to variables defined within a containing function and these variables continue to exist (in the inner functions) even after their outer, defining function has terminated. The variable thus scoped is said to be closed over by the inner functions that can still access it.
To better understand this, let’s look at a basic example of variable closure. Here’s a screenshot that links to a jsFiddle you can experiment with:
In this code snippet, we’ve created an outer function,
enclosure. The outer function declares a variable n, then returns an anonymous function that increments n and reports its value. When we call
enclosure, it returns the anonymous function, which we assign to a variable for future use.
There’s nothing immediately useful for typical application development in this example. But, notice we’ve avoided making n a global variable. The variable will change every time
increment is called, but it’s not in scope anywhere else in the program. Since encapsulation is a key element of robust software architecture, we are evidently onto something here.
Let’s look at a slightly more interesting example to see what closures and higher-order functions can accomplish together:
In the above code, our earlier example has been extended to more realistic scenarios. One situation I’ve often come across “in the wild” is the need to run a function, such as an initializer, only once. Or, perhaps I want to ensure an event can only fire a maximum number of times, in an alternating way, or on every third call. The usual solution for these cases, again, is a global counter. But why pollute the global namespace with this kind of eclectic clockwork?
In general, this technique achieves arbitrarily sophisticated criteria for manipulating both function execution and behavior; in short, closures offer us new and powerful options for complex or unusual control flow in a nicely encapsulated fashion.
Now, without further ado, let’s look at a more fun example since we’ve seen a couple of basic educational illustrations.
Here we implement animation without using
We can largely ignore the ancillary code to see what’s going on in the body of
As usual with recursion, we need to provide a base case that halts and unwinds the call stack. Our method of choice is the variable end, enclosed by our inner function.
Now, you might be asking: what’s the point of basically reimplementing the native
This is a fair question to ask.
I can’t imagine how often someone would require the above version, but there are certain differences to note. With
setInterval you’re required to keep track of an event ID if you ever want to stop it, whereas our version has its termination condition fully encapsulated. More importantly,
setInterval runs on a fixed schedule, whereas the above code can be designed to dynamically adapt its executing interval.
Last, (for this post anyway) but certainly not least, closures enable partial application, one of the most elegant and flexible idioms of functional coding.
Partial application reduces the number of parameters a function requires by freezing, (or fixing, rather) one of its arguments into place. Here is a bare-bones example:
Like the animation program above, it’s worth clicking the code so you can see it in action on jsFiddle. What’s going on here is that we define a function
fix, which takes two arguments: some other function, and some argument to freeze to it. The
fix function will then return a new function, which still asks for the parameter you didn’t freeze, but always remembers the one you did.
So in this example, we freeze the first argument of
Math.pow to two; then, by using
Array.map (a common motivation for using partial application) we get a lovely one-liner listing powers of two.
To conclude this post, I’d like to answer anyone who might be thinking: it’s interesting to see the nuts and bolts behind these hip new libraries, but will it improve my code quality? Even when I already have developers experienced with their own ways of working, some with well-earned expertise in other programming paradigms?
There could be endless debates on this exact question; instead, I just wanted to point out a few aspects that might attract you to a functional style, as they did for me.
For starters, passing and returning functions among each other makes it easier to generalize code, as we demonstrated with the general
fix function above. And depending on your definition of de-coupling, functional programming can encourage that. Rather than arrange a linear series of operations, you architect programs by interfacing and dynamically adapting more distinct and independent units of broad functionality. It’s almost like getting the modularity benefits of good object-oriented design, but more cleanly and simply. And, as suggested in the example of
runOnce, the functional way of thinking often inspires more creative or simplified mechanisms for program control flow.
The best virtue of this coding style, by far, is how concise it is. The one-liner we showed earlier was just one of many such graceful possibilities. But since we can’t presume dense code is good (or maintainable) code, many will ask: do elegance and conciseness matter to us so much?
To this, I would only like to share a pithy, but undeniable, truth I learned from a former engineering mentor of mine:
“Code that doesn’t exist can’t contain bugs.”
Michael Serra is a quality automation engineer in LYONSCG’s Chicago office. He is a software QA analyst and coder who specializes in browser automation and robust web application architecture.