Functional Programming

Functional programming is a paradigm that has been around since the early days of computer science. It’s found new popularity thanks to the rise of data science and machine learning.

In this article we’ll cover the core concepts behind functional programming along with a code example of how to use it:

What is functional programming?

Functional programming is the art of composing programs with functions.This programming paradigm treats functions as “first-class citizens,” which in computer science means you can bind them to names, pass them as arguments, and return them from other functions just like any other data type.

Traditionally, functional programming was about leveraging the purity of mathematical functions to write clean, modular, and concise code that avoids unintended side effects by design.

In practice, modern functional programming involves using a mix of pure functions and first-class functions that, while not technically pure, still encapsulate most of the characteristics of that functional programming ideal. We’ll cover these characteristics in greater detail in the following sections.

Functional programming characteristics

In functional programming, functions serve as the basic building blocks with which you compose computer programs. Let’s take a closer look at the programming features that make this a viable programming paradigm.

Immutable Data

In functional programming all data is immutable. In practice this means that when a function receives an input argument, it does not change that argument. Instead the output will return a new value that reflects the work done.

Pure functions

Pure functions are the computational analog to a mathematical function. To be considered pure, a function must:

  • Be deterministic—have no dependencies on out-of-scope variables
  • Always return the same output for a given identical input argument
  • Produce zero side effects

As true mathematical analogs, you can apply all the rules of mathematics to pure functions (e.g., the associative property). The mathematical predictability, independence, and modularity of pure functions makes them sturdy building blocks for computer algorithms. And it’s this natural affinity for algorithms that makes functional programming popular for machine learning and AI.

First-class functions

In the world of programming languages, designating a programming feature with first-class citizenship means that you can perform on that feature all the operations generally available to other entities in that language. First-class functions can be passed as arguments and returned from functions like any other data type. This opens the door for higher-order functions, which receive functions as inputs and produce functions as outputs.

Higher-order functions

Higher-order functions (sometimes called functionals) take in functions as arguments and may  return functions as results. Common examples of higher-order functions include filter, map, and reduce.

You Might Also Read  WordPress Visual Editor Not Working? Here’s How to Fix It

There’s a hidden power in composing pure functions into higher-order functions—you create safe, modular code by default. And when you can be sure your functions won’t have any unintended side effects, it opens the door to more powerful techniques.

Take lazy evaluation, which is the ability to delay the evaluation of an expression until its value is needed. This call-by-need evaluation strategy saves memory because functions are called only when necessary, and they run only long enough to deliver the required output. Lazy evaluation even makes it possible to work with infinite data structures.

Declarative programming

Functional programming is declarative. To truly grasp what that means it’s important to cover the two major camps to which programming paradigms belong:

  • Imperative programming: Concerned with the control flow of a program, the programmer must specify the “how” and the sequence in which things get executed.
  • Declarative programming: Concerned with results, the programmer must specify the “what” but leaves it up to the system to determine how to execute.

The imperative approach is like telling your driver how to get to your house with directions like “take a left at Maple Street” and “go straight down Church Lane.” The declarative approach is like telling your driver your exact address. Your driver might use GPS or just know the area well enough to navigate to your street, but you aren’t concerned with how he or she gets you there.

Stateless programming

In information technology, state refers to the current status or condition of a system. A system is stateful if it remembers preceding events or user interactions. In other words:

  • State is data that persists over time
  • State is mutable if it can mutate (be changed), immutable if it cannot
  • State is shared if it can be accessed concurrently by multiple parties

A lot of bugs arise because of shared mutable state. Two of the most common categories of errors include:

  • Unintentional locking: More than one party attempts to modify the same variable, database entry, or object at the same time, resulting in one party being blocked. To deal with this, a number of complex workarounds may need to be implemented to assist with concurrency.
  • Mutations – i.e., unpredictable behavior: A function encounters an error because a dependent variable is altered by a preceding function call somewhere else in the system. It’ll take some sleuthing to trace the bug and patch it.

Because purely functional programs are stateless, they avoid these pitfalls by design. In practice, functional programs will probably still have to deal with some mutable shared state, namely the outside world (e.g., network calls, database queries, APIs). But a stateless architecture can still save you a lot of trouble in the long term.

You Might Also Read  Top Website Application Performance Monitoring Tools

Recursion over iteration

Functional programming languages rely on recursion rather than iteration. Instead of iterating over a loop, a function in a functional programming language will call itself. The following Python code example illustrates the differences between recursive and iterative approaches:

# Solving for a factorial using recursion

def recursiveFactorial(n):
   if (n == 0):
        return 1;

   # recursion call
   return n *recursiveFactorial(n - 1);

#Solving for a factorial using iteration
def iterativeFactorial(n):
   factorial = 1;
     # using iteration
   for i in range(2, n + 1):
        factorial *= i;

     return factorial;

Functional programming advantages

Doing things the functional way has its perks. In this section we’ll look at the advantages of functional programming.

Bug-free code

While bug-free code is a myth, it is true that the functional approach avoids many of the unintended side effects associated with changing variables and data. Modular functional code is also easier to test, allowing you to catch and patch any bugs that do appear.

Efficient parallel programming

The immutability of functional programming makes it easier to reason about parallel programming problems because you don’t have to worry about side effects generated by mutable data.

Enforces better programming

Encapsulation, modularity, testability, and portability are all coding best practices developers need to follow regardless of which programming paradigm they choose to use. Functional programming, especially when working with pure functions, will naturally make your code modular, highly testable, and less prone to bugs.

Great for concurrency

One of the advantages of functional programming relying on stateless functions is that it’s great for reasoning about concurrency. Because each function is stateless, you can distribute application workloads across multiple compute resources with ease to improve performance at scale.

Easier to test and execute

Because we know that the outputs are the same for any given inputs in a pure function, it is significantly easier to write and maintain test suites for them. The more pure functions you use in your code, the easier it is to test and catch potential bugs before they go into production.

Functional programming limitations

Steep learning curve

Functional programming can trace its roots to Alonzo Church and his lambda calculus, which handles computation as a mathematician would. As a result, its concepts and terminology require a bit of academic learning to truly grasp its potential.

Extensive environmental setup

Functional programming relies on passing state through stateless functions. And while that confers many advantages, it also means you have to explicitly pass state into a function as input parameters and return new state out as a function result. The more complex that state needs to be, the more coding overhead you will have to invest upfront. This has the tendency for teams to rely on extensive mock objects (called mocks) to mimic the convenience of shared mutable state in object-oriented programming. This is frowned upon by the functional programming community, as a better solution is to improve the modularity of your codebase through pure functions and testing.

You Might Also Read  NoFollow And DoFollow Links

Recursion uses more memory

Recursion is memory intensive. Each recursive call adds to the memory stack to keep values there until it is finished. This means you will generally require a greater allocation of memory for a recursive function than an equivalent iterative function.

The collective wisdom is that iteration is faster than recursion, with an exception for certain use-cases and multicore designs where recursive performance can outcompete iteration thanks to concurrency and load splitting.

Functional programming example

Pure functions have no dependencies on out-of-scope variables. That means no variation with nonlocal variables, local static variables, mutable reference arguments, or input streams from I/O devices. A pure function always returns the same result for the same set of parameters.

This is an impure function in JavaScript:

var myObj = {foo: 1};
var bar = 1;

function impureFunction (foo) {
  foo = foo * bar + 1;

  return foo;
}

var foobar = impureFunction(myObj.foo);

Notice how the variable foobar doesn’t depend just on its input parameter myObj.foo? It has another external dependency “bar” that is not immediately apparent in the impureFunction call. In a larger codebase, this could get confusing. Confusion can lead to unexpected consequences.

Here’s how we purify it:

var myObj = {foo: 1};
var bar = 1;

function pureFunction (foo, foobar) {
foo = foo * foobar + 1;

   return foo;
}

var foobar = pureFunction(myObj.foo, bar);

Now it’s clear that the value of foobar depends on the values of both foo and bar. All our variables are accounted for and encapsulated into the logic of the code. It’s not only safer, but easier to test.

Pure functions are the building blocks of functional programming. Their purity makes it possible to compose them in the same way a mathematician would compose mathematical functions.

Functional programming is a paradigm that involves looking at programs as compositions of mathematical functions. Emphasis is placed on what you want rather than how you get it. Computations are viewed as a series of inputs and outputs.