Saturday, September 21, 2024

The Most Powerful Concepts in Programming Languages: Functional Programming and Lambda Expressions



In this blog, I would like to introduce the concepts that I believe are the most powerful in programming languages, and mastering them will give you solid programming thinking. I will explain them one by one with examples in Java and Python. After introducing the concepts of object orientation, recursion, and concurrency in the first three articles, I will elucidate the concept of functional programming and lambda expressions in this article.



1. Object-oriented
2. Recursion
3. Concurrency

4. Functional Programming and Lambda Expressions

5. Data Structure
6. Software Design Patterns
7. Annotations/Decorators
8. Machine Learning



1. Functional Programming and Lambda Expressions

Functional programming is a paradigm that promotes the use of pure functions and immutable data. In functional programming, you can compose multiple functions and apply them to arguments to perform calculations. A functional program results from the functions’ composition. Pure functions provide the same output for the same inputs. They have no side effects like the functions in the imperative paradigm. Pure functions preserve the definition and logic of a function as defined in mathematics. A functional program is often more concise, intuitive, and more understandable than an imperative due to the sequence of functions.


However, the advantage of a pure function is also its limitation. Indeed, functional programming does not allow data to be saved like an assignment does in imperative languages. Using only a functional language to develop large, traditional applications like desktop or website applications is challenging.


The good news is that functional programming is often combined these days with imperative paradigms in many modern and powerful languages like Java, Python or JQuery. So you can continue to enjoy short, easy-to-understand, and more natural functional code with your working programming language. We now summarize the most important features of functional programming as follows:


Data Immutability: This is the basic feature of functional programming to avoid side effects. The immutability of data means that once it is created, we cannot change it. You can update the data by creating new data structures with the desired changes.


Recursion: It is widely used in functional programming to implement the iterative way to solve similar sub-problems. 


Function composition: Functional programming combines functions to create new ones. This chaining of functions allows you to construct complex code to perform multiple actions at once. The code nevertheless remains intuitive and simple to understand.


Higher-order functions: Functional programming allows functions to take other functions as arguments or return them as results. This feature reinforces code abstraction.


Data transformation: Functional programming promotes data transformation pipelines. It transforms data from one state to another.


Lambda expression: It is also called an anonymous function and is unnamed with a short form. It can take any number of arguments, but must only have one expression. Lambda expressions are useful in situations that allow for quick calculations where complete functions (such as functions defined with the ‘def’ keyword in Python) would not be necessary. Note that the lambda expression can be implemented in another complete function.


2. Example in Java

We provide some examples of the functional programming style in Java to show how the functional code is more concise and easier to understand. 


2.1. Example 1 in Java

In this first example, let’s assume that we have a ‘User’ class with some basic fields, constructors, and getters:


import java.sql.Date;

public class User {

    private String firstName;

    private String lastName;

    private String email;

    private Date birth;

    public User(String firstName, String lastName, String email, Date birth) {

        this.firstName = firstName;

        this.lastName = lastName;

        this.email = email;

        this.birth = birth;

    }

    public String getFirstName() {

        return firstName;

    }

    public String getLastName() {

        return lastName;

    }

    public String getEmail() {

        return email;

    }

    public Date getBirth() {

        return birth;

    }

}


To create data, we provide the following code to populate a list of users:


public static List<User> getList() {

        List<User> users = new ArrayList<>();

        users.add(new User("Jack", "Sparrow", "Jack@gmail.com", 

Date.valueOf("1980-12-25")));

        users.add(new User("Anakin", "Skywalker", 

"Anakin@gmail.com", Date.valueOf("1985-05-15")));

        users.add(new User("Indiana", "Jones", "Indiana@gmail.com", 

Date.valueOf("1970-01-03")));

        users.add(new User("Sarah", "Connor", "Sarah@gmail.com", 

Date.valueOf("1983-08-14")));

        users.add(new User("Ellen", "Ripley", "Ellen@gmail.com", 

Date.valueOf("1990-07-04")));

        users.add(new User("James", "Bond", "James@gmail.com", 

Date.valueOf("1975-02-17")));

        users.add(new User("Forrest", "Gump", "Forrest@gmail.com", 

Date.valueOf("1995-09-21")));

        users.add(new User("Harry", "Potter", "Harry@gmail.com", 

Date.valueOf("1984-11-07")));

        return users;

}


Now we want to sort this list. It can’t be better than using functional programming in Java to do it. You can imagine any sorting scenario and achieve it in very few lines of code. We describe this by creating a method that sorts the list of users by ‘lastname’, then by ‘firstname’, then by date of birth in descending order! The method contains a few lines of code:


public static List<User> UsersSorting(List<User> users) {

        Comparator<User> compareByLastName =

 Comparator.comparing(User::getLastName);

        Comparator<User> compareByFirstName =

 Comparator.comparing(User::getFirstName);

        Comparator<User> compareByBirth = Comparator.comparing(User::getBirth);

        Comparator<User> compareByNameBirth =

compareByLastName.thenComparing(

compareByFirstName.thenComparing(compareByBirth.reversed()));

        Collections.sort(users, compareByNameBirth);

        return users;

}


In this method, we create three comparators. The first compares users’ last names, the second compares their first names, and the last compares their dates of birth.


Subsequently, we combine these three comparators into one to ensure sorting our list in the desired order: last name in ascending order, first name in ascending order, and date of birth in descending order.


Finally, we apply this comparator to the list of users with the help of the static ‘sort’ method of the ‘Collections’ class to get the sorted list which we return in the last line of the ‘UsersSorting’ method. We can use this method inside a main method like this:


public static void main(String[] args) {

        List<User> users = getList();

        List<User> sortedUsers = UsersSorting(users);

        sortedUsers.forEach(x->{

            System.out.println(x.getFirstName() + " " + x.getLastName() + " " + x.getBirth());

        });

}


2.2 Example 2 in Java

Lambda expressions are supported in Java. They are linked to the concept of functional interface. In Java, a functional interface is an interface that defines only one abstract method. A functional interface is used to represent a function and is the context in which lambda expressions can be defined. A lambda expression in Java can have the following declaration:


(parameter1, .., parametern) -> expression


Now let’s show how to use lambda expressions to define anonymous functions in Java. In the code below, we define a class we named ‘Main’. Within the main method of the ‘Main’ class, we define ‘binaryOperator’ as a functional interface. The latter is an instance of the ‘IntBinaryOperator’ class that we import at the beginning of the code. 


The code of the ‘binaryOperator’ interface will be defined using lambda expression which takes two parameters ‘x’ and ‘y’ of type ‘int’ and returns their sum. We call the ‘applyAsInt’ method of ‘binaryOperator’ with some actual integer parameters (25 and 105) and display the result.


We can also define a custom function interface, which we call ‘OpInterface’ here. It contains a single abstract ‘opMethod’ method with three string parameters. Any implementation of this method should return a result of type String. 


In the main method code, we declare ‘opLam’ as a functional interface of the ‘OpInterface’ class. It takes three string parameters and returns their concatenation. We call the ‘opMethod’ method of the ‘opLam’ interface with some actual string parameters ("Hello", " ", "World!") and display the result.


import java.util.function.IntBinaryOperator;


interface OpInterface {

    String opMethod(String x, String y, String z);

}

public class Main {

    public static void main(String[] args) {

        IntBinaryOperator binaryOperator = (int x, int y) -> x + y;

        System.out.println("Result of addition: " + binaryOperator.applyAsInt(25, 105));


        OpInterface opLam = (String x, String y, String z) -> x + y + z;

        String result = opLam.opMethod("Hello", " ", "World!");

        System.out.println("Result of concatenation: " + result);

    }

}


Running the code above gives us the following result:


Result of addition: 130

Result of concatenation: Hello World!


3. Example in Python

Python is a language that provides many tools to effectively apply functional programming principles. In this section, let’s take some examples to illustrate the use of the functional programming paradigm in this language.


We import the ‘reduce’ function from the ‘functools’ module as a helpful function that we will explain how to use later. We define a high-order function ‘apply_op’ that accepts two parameters: ‘func’ which should be a function and ‘nums’ as a list of numbers. This method iterates over the ‘nums’ list to apply the ‘func’ function to each element of this list and saves the obtained result in the ‘result’ list.


from functools import reduce


def apply_op(func, nums):

    result = []

    for num in nums:

        result.append(func(num))

    return result


def square(x):

    return x ** 2


def double(x):

    return x * 2


list_nums = [10, 15, -25, -44, 50]


squared_nums = apply_op(square, list_nums)

print("List of squared numbers:", squared_nums)


double_nums = apply_op(double, list_nums)

print("List of doubled numbers:", double_nums)


To execute this high-order function,  we define some functions such as ‘square’ and ‘double’ to pass them as parameters to the ‘apply_op’ function. As their names suggest, the ‘square’ method squares a number, and the ‘double’ method is defined to double a number. 


Before applying the higher-order function ‘apply_op’ to ‘square’ and ‘double’, we need a list of numbers which we define and call ‘list_nums’. We first apply ‘apply_op’ to ‘square’ and ‘list_nums’. We then store the result in a ‘squared_nums’ list which we print out in the next statement. 


In the last two statements, we use the higher-order function ‘apply_op’ with the function ‘double’ and list ‘list_nums’ to get a new list of doubled numbers ‘double_nums’ which we print in the last statement.


We continue this example and we define a lambda function to compute the addition of two numbers. As expected, the lambda function has a short form. It takes two parameters and returns their sum. Next, we use the ‘reduce’ function, which is a high-order function, to calculate the sum of the numbers in the ‘list_nums’ list.


add = lambda x, y: x + y

summed_nums = reduce(add, list_nums)

print("Sum of numbers:", summed_nums)


We can define another function ‘is_even_num’ to check if a number is even. So, the ‘is_even_num’ function returns true if the number is true and false otherwise. We can use the predefined ‘filter’ function in Python to filter out the even numbers from the list ‘list_nums’ and print them in the third statement of the code.


is_even_num = lambda x: x % 2 == 0

even_nums = list(filter(is_even_num, list_nums))

print("Even numbers:", even_nums)


Another functional programming code in Python is to use the ‘map’ function. The latter applies a function to all the elements of a list to obtain a new list containing the results of the applied function. 


Here we apply ‘map’ to a lambda function which returns the cube of a number and the list ‘list_nums’. We use ‘list’ to get the application result as a list stored in the ‘cubed_nums’ list which we print in the last statement.


cubed_nums = list(map(lambda x: x**3, list_nums))

print("Cubed numbers:", cubed_nums)


The execution of the code above gives us the following output:


List of squared numbers: [100, 225, 625, 1936, 2500]

List of doubled numbers: [20, 30, -50, -88, 100]

Sum of numbers: 6

Even numbers: [10, -44, 50]

Cubed numbers: [1000, 3375, -15625, -85184, 125000]

No comments:

Post a Comment

Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...