Understanding Lambda Expressions

Lambda expressions (or lambda functions) are essentially blocks of code that can be assigned to variables, passed as an argument, or returned from a function call, in languages that support high-order functions. They have been part of programming languages for quite some time. Some examples include Smalltalk, Lisp, Ruby, Scala, Python, and, more recently, Java and JavaScript. Here are some examples:

Python:

square = lambda x: x**2
square(6) //-> 36

Ruby:

square = lambda { |x| x * x }
square.(6) #-> 36

JavaScript (fat-arrow functions):

var square = x => x * x;
square(6) //-> 36

Lambda expressions are also known as closures or blocks. If you look closely at the Ruby code, you can see the declaration of a “callable” block of code (encapsulated in { } braces) that is assigned to a variable and run. Lambda expressions are also called closures because they access state from its surrounding lexical scope. This is unlike function pointers in C, which can also be passed around but do not carry (or close over) their lexical scope.

Most people people perceive lambdas simply as anonymous functions, but when you study them closer (perhaps from a functional programming point of view), lambda expressions carry a lot more meaning and are a lot more powerful than you think. To speak about this more concretely, I’ll refer to JavaScript’s “arrow” syntax shown above.

Syntax

The most notorious difference is in syntax. Lambda expressions offer a syntactical advantage of over a traditional anonymous function declaration

function (x) { return x * x };

by removing a few artifacts like the function and return keywords, which makes these expressions feel more like just callable blocks of code. This syntax also makes your code a lot more succinct when used in conjunction with high-order functions like map, reduce, and filter:

range(1, 5).map(x => x * x); //-> [1, 4, 9, 16, 25]

Singularity

The benefits of using lambda expressions extend way beyond just syntax. Lambdas encourage you to practice the first of the famous SOLID principles of software design: Single Responsibility. Applied to functions, single responsibility just means that functions should have a single purpose. While you can have lambda expressions contain multiple statements

(x, y) => {
  // do something with x and y  
// statement 1
// statement 2
return result;
}

most of its usage derives from being inlined as a function argument into larger expressions or within a composition (more on this later). This means that you can create several small lambda functions that do exactly one thing, and combine them together to form whole programs:

range(1, 5)
.filter(x => (x % 2) === 0)
.map(x => x * x); //-> [4, 16]

But to really understand why they are so important, we need to examine lambda expressions from a functional programming point of view.

Pure Functions

Functional programming treats functions as pure mathematical relations or mappings between sets of types; in this case the set of inputs (domain) and output (range or codomain). Hence, you can view any functions as a mapping of types. For instance, the squared function is a mapping from Number to Number:

You can also map to user-defined types, like a Long to an Account object:

As you can see from these diagrams, mathematically speaking, a function’s output must be solely derived from its input and not from any other state outside of the function’s scope. This absence of side effects is what determines that a function is pure, as all functions in FP should be. Lambda expressions encode this pretty well into its syntax by using the logical implies symbol “=>” to mean that a set of inputs implies a certain output.

Referential Transparency

We can extend this concept of purity to a mathematical principle called referential transparency, which means that a pure function can always be substituted for its computable value without altering the meaning of the expression within which it’s used. For a concrete example, consider a pure function like sum:

var size = (arr) => !arr ? 0 : arr.length;

And a function average:

var average = (arr) => Math.round(sum(arr) / size(arr))

For an input such as [80, 90, 100], size will consistently return the value 3. Because this function always returns the same output on the same input, it’s said to be referentially transparent — actually the entire program above is. That means that I can use size(arr) interchangeably with the value 3 in the expression above, without altering its meaning. This mathematical quality makes our programs much easier to reason about and easier to trace on any given input. Indeed, these are very simple functions yet the concepts apply equally as well to the most complex algorithms. Referential transparency means that I can treat functions truly as black-boxes because I can predict a function’s outcome based solely on the input provided.

Consequently, it’s easy to see that under referential transparency there’s no room for either no-argument functions or void functions; mathematically speaking, either of these cases implies the empty set “∅” in the domain or codomain, respectively. Take the following example that uses the empty set notation in place of the arguments list:

var hello = () => console.log('Hello!');

Zero-arity functions or void functions always imply that side effects are taking place, since no other real work could be done without accessing resources outside of its own scope. Lambda expressions reveal this mapping more closely.

Composition

All of these principles serve an important purpose: to facilitate function composition — the epitome of functional programming. Composition is used to orchestrate the execution of functions in a loosely coupled manner where the output of one is used as the input to the next.

f o g (x) = f(g(x))

At a fine-grained level, function composition resembles the “coding to interfaces” principle because it treats functions as a black boxes with clear inputs and outputs. For example:

var str = 'We can only see a short distance
ahead but we can see plenty there
that needs to be done';
var explode = str => str.split(/\s+/);
var size = arr => !arr ? 0 : arr.length;
var countWords = compose(size, explode);
countWords(str); //-> 19

I can use composition to join independent functions and create entire programs based on these simple concepts. Lambda expressions encourage us to create programs that are declarative, modular, and reusable at a very fine-grained level!


Now a little about me… My name is Luis Atencio and I’m a software engineer @ Citrix Systems. If you want to learn about these topics and many others in much more detail (and would like use JavaScript to do so), I recommend you check out: Functional Programming in JavaScript (Manning 2016). Chapter 1 is free!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.