Chapter 8

Lambda Expressions

Because every function deserves to be a first-class citizen!

Subsections of Lambda Expressions

Introduction

Once of the more interesting features that has been added to most object-oriented languages over time is lambda expressions. Lambda expressions are a unique way to handle functions in our code - basically, we can create a function on the fly, and then pass that function around as a parameter or store it in a variable, just like any other object. In true Von Neumann fashion, we are effectively treating the executable code of our program just like data.

In this chapter, we’ll briefly explore lambda expressions and where they came from. We’ll see some examples of how they are used in both Java and Python, and then we’ll discuss some best practices for when we should, or should not, consider using them in our code.

In this course, we generally won’t need to use lambda expressions in our programs except in a few cases, such as specific types of unit tests in Java. This chapter is meant to simply be informative and let you explore one interesting aspect of programming you may not have worked with up to this point.

Lambda Calculus

YouTube Video

Video Materials

The basis of lambda expressions comes from a special branch of mathematics known as Lambda Calculus. It was first introduced by Alonzo Church, who is often connected with Alan Turing in the early days of theoretical computer science. (You may have heard of the Church-Turing thesis that relates to the computability of functions on a Turing machine.)

Lambda Calculus

Lambda calculus is a formal notation used to describe computation. Recall that most mathematics uses expressions or equations, which express values, but don’t necessarily include the information needed to express the process of computation itself. By having a formal notation for computation, we can study the fundamental aspects of computer science and mathematics in a more rigorous way.

In programming, lambda calculus leads to a particular programming paradigm known as Functional Programming. The programming paradigm we’ve been studying, object-oriented programming is usually combined with the procedural programming paradigm, itself a subset of imperative programming. In imperative programming, we write code that consists of commands that modify the programs state. So, to compute the square of a number, we would create a variable in our state to store the result, and then modify that state by computing the correct value and storing it in that variable. The commands to do this are typically written as procedures (or functions) in procedural programming, so we can reuse those pieces of code throughout our program. Procedural programming typically follows the structured programming paradigm as well, where programs are constructed of smaller structures such as sequences, conditional statements, and iterative statements. Object-oriented programming, as we’ve learned, further refines this process by grouping related state and behaviors (methods which represent functions or procedures in other paradigms) into objects that can be seen as independent pieces of a larger program.

Functional programming is quite different. Instead of creating an imperative list of steps to be taken to modify the state of the program and achieve a result, functional programming involves constructing and applying mathematical functions, which simply translate values from inputs to outputs. Functional programming is a form of declarative programming, where computer programs are built simply by expressing the logic of the computation but not the individual steps or control flow necessary to achieve the desired result. In effect, a declarative programming language is used to state what a program does, but not necessarily how to do it.

Functional Programming Example

Here is an example of the imperative and functional programming paradigms being used to compute the same value. In this case, the program will multiply all even numbers in an array by 10, and then add them up and store the final result in a variable called result. These examples use the JavaScript programming language, which should be somewhat readable to us even though we’ve only studied Java or Python. This example is taken directly from the functional programming article on Wikipedia:

Imperative
const numList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result = 0;
for (let i = 0; i < numList.length; i++) {
  if (numList[i] % 2 === 0) {
    result += numList[i] * 10;
  }
}
Functional
const result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
               .filter(n => n % 2 === 0)
               .map(a => a * 10)
               .reduce((a, b) => a + b);

The imperative programming code is very similar to what we would write in Java or Python. We start with our array of numbers, then use a for loop to iterate through the entire array. Inside of the for loop, we determine if the individual number is an even number using the modulo operator. If so, we multiply that number by 10 and add that value to the result variable.

The functional programming code achieves the same result through the use of three higher-order functions. A higher-order function is a function that can accept a function as input - in this case a lambda expression in the form of an anonymous function that converts one or more input parameters into an output value. We’ll dig deeper into lambda expressions later in this chapter, but for now we’ll just observe what they do.

So, our functional program can be broken down into four parts:

  1. We start with our array of numbers from 1 through 10. That is the input we provide to the first function.
  2. On that array, we apply the filter function. This function accepts a lambda expression as an argument. That lambda should take a value from the array, and convert it to a boolean value, which is used to filter the values in the array. In this case, that boolean value will be true if the value n from the array is an even number. The filter function then uses that lambda to return a new array that just contains those values in the original array that return true in the lambda function provided to filter. So, our new array will contain [2, 4, 6, 8, 10].
  3. Then, we apply the map function to that new array returned from filter. The map function also takes a lambda as an argument, and that lambda is used to transform, or map, the values from the array to new values. In this case, it will convert the existing value a to the value a * 10. So, once the map function is complete, the array would contain [20, 40, 60, 80, 100]. Remember that this value isn’t stored as state in the program, per se, but is representing the values that would result from applying these functions to the input array itself.
  4. Finally, we use the reduce function to reduce all of the values in the array to a single resulting value. The result function uses a lambda expression as an argument. That lambda is used to describe how to combine two values from the array, a and b, to a single resulting value. In this case, we want to sum the values in the array, so the lambda will return in a + b as the result. The reduce function will repeatedly use that lambda to reduce two values in the array to a single value until only one value remains. That value will be the result of that function, which will be then represented by the result variable. Notice that it isn’t stored in that variable, since again we don’t have the concept of state. Instead, we are just stating that the variable result now represents the value that is the result of applying these functions to the given input value.

Functional programming can be challenging to understand at first, especially for programmers that come from an imperative programming paradigm. However, it is very powerful, and has some interesting uses. Once of the more common uses of functional programming is the creation of programs that can be proven to work correctly. This is because there is no actual computation performed, so there can be no side effects from those computations. Therefore, as long as the functional statements yield the correct results via a mathematical proof, we know that the program works correctly.

Functional Programming Today

Many programming languages today either support some form of functional programming, or at least support the use of lambda expressions within their code. Some languages, such as Python, JavaScript and Go, support the functional programming paradigm directly. Other languages, such as Java and C#, have introduced the ability to do some functional programming over time.

Other languages, such as Haskell, F#, Erlang, and Lisp are built almost exclusively for functional programming. While they are most used in academia, functional programming is also very commonly used in web back-end development, statistics, data science, and more.

Subsections of Lambda Calculus

Functions as Objects

One of the major concepts from functional programming is that functions are now treated as first-class citizens within a programming language. A first-class citizen is an element of a programming language that can be treated like any other element - it can be stored in a variable, provided as an argument to another function, returned from a function, and even modified by other code.

This can be a very strange concept to reason about - we are used to thinking of state and behavior as two separate parts of an object-oriented program. However, functional programming allows us to store a behavior as state, and then use that behavior as input to other parts of the code.

Lambda Expressions

In both Java and Python, one of the most common ways to create a behavior that can be stored as state is to use a lambda expression. Lambda expressions are sometimes known as anonymous functions since they are effectively functions that are not given a name, though some languages like Python allow us to assign names to lambda expressions as well.

As we saw in the example on the previous page, JavaScript allows us to quickly create lambda expressions that perform a particular task, such as determining if a value meets a given criteria that was used with filter, converting a value to a new value as used with map, or taking two values and reducing them to a single value as used in the reduce function.

In that example, filter, map, and reduce are examples of higher-order functions that accept other functions as input. Those higher-order functions can then use the function provided as input to perform their work. In the case of filter, it uses the provided function to determine if each value in the array should be included in the result or not.

We’ve already seen a couple of examples of lambda expressions, or at least something similar, in our programs:

  • Java - in our unit tests, we saw a lambda expression () -> new GameLogic() used as part of a unit test. That lambda is used to create a new object, and is used by the assertThrows assertion, itself a higher-order function, to determine if the code in the lambda expression results in an exception. In effect, that function executes the lambda and observes the result to determine if the exception is thrown.
  • Python - one major feature of Python is list comprehension, such as square_list = [x**2 for x in range(0, 10)]. While list comprehension isn’t exactly the same as a lambda expression, it is very similar in concept. We are effectively creating a small anonymous function that is used to populate a list. In fact, we could do the same thing with a lambda expression: square_list = list(map(lambda x: x**2, range(0, 10)))

On the next pages, we’ll discuss the specifics of creating and using lambda expressions in both Java and Python. Feel free to read the page for the language you are studying, but it may be very informative to review how both languages handle the same concept.

Java Lambdas

YouTube Video

Video Materials

Java introduced lambda expressions in Java version 8. As we would expect based on the previous pages, it allows us to create anonymous functions that can then be passed as arguments to other functions. Java includes several new types, such as Predicate and Consumer, all contained in the java.util.function package.

Lambda Expression Syntax

In general, a lambda expression in Java consists of the following syntax:

  1. A list of formal parameters in parentheses, separated by commas. You do not have to specify the data type of the parameters. Likewise, if there is a single parameter, the parentheses may also be omitted.
  2. An arrow ->
  3. A body, which may be either a single expression or a block of statements surrounded by curly braces {}

Lambda Expression Example

Let’s look at an example of creating and using a lambda expression in our code. This example comes from Lambda Expressions from the Oracle Java Tutorials:

public class Calculator {
  
    interface IntegerMath {
        int operation(int a, int b);   
    }
  
    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }
 
    public static void main(String... args) {
    
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));    
    }
}

In this simple calculator class, we are defining an internal interface called IntegerMath, which defines one operation between two integers, which also returns an integer. Then, in our Calculator class, we have a function operateBinary that accepts an argument of type IntegerMath.

So, in our main method, we are creating two lambda expressions that use the type IntegerMath. One is a lambda that accepts two values and returns the sum, and the other accepts two values and returns the difference. Java will automatically recognize that those lambda expressions match the operation method defined in the IntegerMath interface. So, when we call the operateBinary method and provide either addition or subtraction as arguments, it will use those lambda expressions to compute the result.

As we can see, we were able to create two functions, via lambda expressions, as first-class citizens in our language by storing them in variables, and then passing those functions as arguments to another method, which can then call the function itself.

Lambda Expressions In Practice

In practice, Java tends to use lambda expressions for tasks such as sorting, filtering, or mapping data in a collection. Lambda Expressions from the Oracle Java Tutorials gives another example that can be used to quickly generate a filter that will print a list of email addresses for people in list who are males between the ages of 18 and 25:

The function that accomplishes this work is shown below:

public static void processPersonsWithFunction(
    List<Person> roster,
    Predicate<Person> tester,
    Function<Person, String> mapper,
    Consumer<String> block) {
    for (Person p : roster) {
        if (tester.test(p)) {
            String data = mapper.apply(p);
            block.accept(data);
        }
    }
}

We can use that function by passing three lambdas, as shown here:

processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

In this case, roster is a list of Person objects, and we have created three lambda expressions to filter the list to include only the people we want, them map those people to an email address, and finally print those emails to the terminal.

Referencing Java Methods

Finally, while methods in Java aren’t exactly first-class citizens, there is a shorthand that we can use to create lambda expressions that simply call a given method.

For example, the lambda expression:

a -> a.toLowerCase()

simply calls the toLowerCase() method of the String class. So, we could replace that with this method reference:

String::toLowerCase()

In effect, this allows us to reference a function as if it were a first-class citizen, even if we can’t truly store it in a variable like other objects in Java.

There are four different types of method references:

  • Static Method in a Class : ClassName::staticMethodName
  • Instance Method of Particular Object: objectInstance::instanceMethodName
  • Instance Method of Arbitrary Object of Given Type: ClassName::methodName
  • Constructor: ClassName::new

Many parts of the Java API accept method references along with lambda expressions, so this is yet another way we can make use of existing or anonymous functions in our code.

For more information on using lambda expressions and method references in Java, check out the references linked below.

References

Subsections of Java Lambdas

Python Lambdas

YouTube Video

Video Materials

Lambda expressions, typically called lambda functions in most Python documentation, are effectively a syntactic shortcut for defining a function within Python code. This is because normal Python functions are already first-class citizens in the language - we can already pass existing named functions as arguments to other functions! So, lambdas in Python are simply shortcuts we can use to create a new anonymous function where needed, but we can always use normal functions to perform the same task.

Python Functions vs. Lambdas

Python lambda functions are effectively the same as Python functions. For example, we can write an addition function in Python in the following way:

def addition(x, y):
    return x + y

The same concept can be expressed as a lambda function, and we can even store it in a variable:

addition_lambda = lambda x, y: x + y

Those two functions are effectively identical - they produce the same result, and can be treated as variables as well as callable functions.

The basic syntax of a lambda function in Python includes the following:

  1. The keyword lambda
  2. A list of parameters separated by commas, which can be named, positional, keyword, or variable parameters. Basically, any way you can define the parameters for a normal Python function can also be used for a lambda function.
  3. A colon after the parameters:
  4. A single expression that creates the result of the lambda function. Lambdas may not include multiple expressions, or any statements such as return or pass.

In addition, Python lambda functions are not compatible with type annotations. So, when working with object-oriented Python, we will almost always prefer to write our own functions using the normal syntax, which allows us to perform type checking using Mypy.

Python Lambda Example

Here’s a quick example of using both lambda functions and normal class functions as first-class citizens in Python. This example is adapted from a similar example given in Lambda Expressions from the Oracle Java Tutorials:

class Calculator:
    
    @staticmethod
    def addition(x, y):
        return x + y
    
    def operate_binary(self, a, b, operation):
        return operation(a, b)
    
    @staticmethod
    def main():
        calc = Calculator()
        subtraction = lambda x, y: x - y
        print("40 + 2 = {}".format(calc.operate_binary(40, 2, Calculator.addition)))
        print("20 - 10 = {}".format(calc.operate_binary(20, 10, subtraction)))
        print("7 * 6 = {}".format(calc.operate_binary(7, 6, lambda: x, y: x * y)))

if __name__ == "__main__":
    Calculator.main()

In this code, we are defining two different functions that we’ll use later as arguments:

  • addition is a static method within the Calculator class that adds two values together.
  • subtraction is a variable in the main function that is storing a lambda function that will subtract two values.

Then, we’ve created a higher-order function operate_binary in the Calculator class, which accepts two integers as parameters a and b, as well as a callable object in the operation parameter. In effect, the operation parameter is meant to be a function, either a traditional Python function or a lambda function.

In our main function, we call calc.operate_binary in two different ways. On the first line, we provide Calculator.addition as the third argument. Notice that we are not including the parentheses at the end of the function name. In that way, we aren’t calling the function Calculator.addition, but we are referencing it as an attribute within the Calculator class. We can do this because functions are first-class citizens in Python, so we can treat them just like any other variable. Inside the calc.operate_binary function, we see that it calls the function stored in the operation variable by putting parentheses after the name, pass in any arguments as needed.

In the second example, we are passing the subtraction variable, which is a lambda function we created earlier, to the calc.operate_binary higher order function. So, it will be stored in operation and executed there.

Finally, we can create an anonymous lambda function directly within the function call to calc.operate_binary. This is why, typically, most lambda functions in Python are thought of as anonymous functions - we don’t give them a name or store them in a variable, we simply create them as needed when we pass them to higher-order functions.

For more information on using lambda functions in Python, check out the references linked below.

References

Subsections of Python Lambdas

Best Practices

Lambda expressions are a very powerful tool that has been added to many different programming languages, including the ones we are studying in this course. However, there are some caveats that we should be aware of, and some best practices to follow.

Readability

For starters, lambda expressions can affect the readability of code. Even though lambda expressions are included in both Java and Python, and have been for quite a while at this point, many developers still have not learned how to use them. This is mainly due to the fact that lambda expressions are closely related to functional programming, which is a completely different programming paradigm than what most programmers are used to.

In addition, pretty much anything that can be done with a lambda expression can be achieved through strictly procedural code, so there is really nothing to be gained through the use of lambda expressions in terms of functionality or performance.

Instead, the use of lambda expressions in Java and Python really comes down to readability, and for that reason, many developers tend to avoid them. From a certain point of view, lambda expressions don’t really do anything except make the code harder to read for some developers, but possibly easier to read for others.

Scale

If we do choose to use a lambda expression, it is best to keep them as short and concise as possible. In effect, a lambda expression should be thought of as a single operation or expression. In Python, this is required, but Java allows lambda expressions to include multiple statements.

If we need to write more complex code, it is probably best to do so using procedural code and traditional functions instead of lambda expressions.

Summary

In general, while lambda expressions are very powerful and can be used in many different places in our code, in this course we’ll generally avoid their use in places where they are not required. However, as a developer, you are welcome to use your better judgment - if you feel that a piece of code is better expressed as a lambda expression instead of procedural code, you are welcome to do so. When you do, keep in mind that this may make your code more difficult to understand for novice programmers who are not experienced with lambdas, so you may wish to thoroughly document your code to explain how it works.

Summary

In this chapter, we introduced lambda calculus as the basis for the functional programming paradigm. In functional programming, programs are written in a declarative language, expressing the desired result as a composition of functions instead of a procedural set of steps to execute.

In Java and Python, this appears as lambda expressions or lambda functions - small pieces of code that can be used to create anonymous functions. In addition, those functions can be treated as first-class citizens in our language, so we can store them in variables, pass them as arguments, and more.

However, due to the fact that lambda expressions are not well understood by a large number of programmers who do not have experience with functional programming, we’ll generally avoid their use in our code. In most cases, anything that can be done in a lambda expression can also be done using procedural code and functions, and that is much more readable to the average programmer.

Review Quiz

Check your understanding of the new content introduced in this chapter below - this quiz is not graded and you can retake it as many times as you want.

Quizdown quiz omitted from print view.