Learn Functional Programming by refactoring a React app

Making order by decomposing the program into functions

Functional programming has been around since the 60s, and is rapidly rising back into popularity nowadays. Whether you are a total beginner to it, or you already know its fundamental concepts, I’d like to demonstrate how it could be applied to bring order in a React application’s code.

In this tutorial, I will refactor the code of my previous tutorial about React function components to suit the functional programming paradigm. Here are the topics that I will cover by doing so:

  • How to rewrite our code to suit the declarative nature of functional programming
  • The importance of using pure functions when programming functionally
  • How functional programming treats functions as first-class citizens
  • A technique to compose functions using partial application and higher-order functions
  • A discussion about how functional programming can help us to write code that is more understandable through referential transparency

If any of this has raised your interest, then I won’t let you wait no further!

A declarative paradigm

As applications grow in complexity, it is useful to organize their code in fashions that are meaningful to the programmers working on them.

Functional Programming is a paradigm that drives such an organization in a declarative fashion. The declarative approach could be summed up as: “Tell the program what to do, not how to do it”, which opposes this approach to the imperative paradigms.

Tell the program what to do, not how to do it.

Declarative Programming mantra

I understand this might be very abstract to you at that point, so let’s immediately dive into an example to explain what this all mean. If we look at our App component, we can see that is is actually written in an imperative style:

function App() {
  const data = [
    [null, BlackMan, null, BlackMan, null, BlackMan, null, BlackMan],
    [BlackMan, null, BlackMan, null, BlackMan, null, BlackMan, null],
    [null, BlackMan, null, BlackMan, null, BlackMan, null, BlackMan],
    [null, null, null, null, null, null, null, null],
    [null, null, null, null, null, null, null, null],
    [WhiteMan, null, WhiteMan, null, WhiteMan, null, WhiteMan, null],
    [null, WhiteMan, null, WhiteMan, null, WhiteMan, null, WhiteMan],
    [WhiteMan, null, WhiteMan, null, WhiteMan, null, WhiteMan, null]
  ]

  return (
    <table className='no-border'>
      <thead>
        <tr><th></th><th>a</th><th>b</th><th>c</th><th>d</th><th>e</th><th>f</th><th>g</th><th>h</th><th></th></tr>
      </thead>
      <tbody>
        {data.map((rowData, index) => {
          const number = data.length - index
          
          return <Row key={number.toString()} number={number} data={rowData} />
        })}
      </tbody>
      <tfoot>
        <tr><th></th><th>a</th><th>b</th><th>c</th><th>d</th><th>e</th><th>f</th><th>g</th><th>h</th><th></th></tr>
      </tfoot>
    </table>
  );
}

What I am refering to, is the anonymous function that is passed to the map() method: we are actually explaining that each entry of the data array will be transformed in a Row component, and that this component will receive the value of the entry as its data property, as well as a number computed from the array length and the entry’s index as its number property.

All this code is verbose explaining how to make a row. What if we could just tell the program to make a row, and assume it already knows how to make one? Such a declarative code would look like this:

      <tbody>
        {data.map(makeRow)}
      </tbody>

Of course, we would still have to declare the makeRow() function, which will then contain the exact same code that we moved from here. But writing the code this way helps to enforce the separation of concerns: we can reason independently about the display of the App component and about the mapping of a data entry into Row components.

We can do the exact same operation inside the Row component:

function Row(props) {
    return (
        <tr>
            <th>{props.number}</th>
            {props.data.map(makeSquare)}
            <th>{props.number}</th>
        </tr>
    )

    function makeSquare(Piece, index) {
        const column = String.fromCharCode(97 + index)

        return (
            <Square
                key={column + props.number}
                row={props.number}
                column={column}
            >
                {Piece && <Piece />}
            </Square>
        )
    }
}

And I can do the same for the Sqaure component (almost):

function Square(props) {
    return ( 
        <td className={'square ' + squareStyle(props.row, props.column)}>
            {props.children}
        </td>
    )
}

function squareStyle(row, column) {
    const isEvenRow = (row % 2 === 0)
    const isEvenColumn = (column.charCodeAt() % 2 !== 0)
    const isLight = (isEvenRow && isEvenColumn) || (!isEvenRow && !isEvenColumn)

    return isLight ? 'light' : 'dark'
}

Source code

Favoring pure functions

There is a slight difference between this code and the Row / App refactored code, but as you care for the details, you may have catched it already: the squareStyle() function is totally independent from the Square component, while the makeRow() and makeSquare() functions are nested inside their respective components.

If we look inside the makeSquare() function’s code, we’ll see that it uses the props variable that is passed to the Square component. This is why the makeSquare() function can not be extracted from the Row context as it is now. From the makeSquare() perspective, this props variable is as much restrictive as a global variable.

In functional programming, the squareStyle() function is what can be called a pure function: its output depends only on the values that are passed as inputs. While the makeSquare() function will return a result that depends not only on its inputs, but on the state of the Row component as well…

In computer programming, a pure function is a function that has the following properties:

1. The function return values are identical for identical arguments

2. The function application has no side effects

Pure function – Wikipedia, The free encyclopedia

But why does this purity of functions matter, would you ask? The answer might not surprise you too much: to make the life of the programmer that reads the code a little easier.

How? Because now the squareStyle() function can be tested independently: I know that if I call squareStyle(8, 'a') (an even row and even column), the result will always be 'light'. It doesn’t matter what the program has done before, this result will always be the same.

We can benefit from this purity to write unit tests using Jest (and you can use my tutorial on Test Driven Development if you don’t know how to):

describe('The squareStyle function', () => {
    it('Sets a light style for square a8', () => {
        const result = squareStyle(8, 'a')

        expect(result).toMatch('light')
    })
}

As you can see, the test is concise, easy to write, and doesn’t need any specific of set up.

Source code

Functions as first-class citizens

In order to refactor the makeSquare() function into a pure version, we would need to use the most fundamental concept of functional programming: functions as first-class citizens. This implies that functions can be manipulated as values in the language we are using, which is the case in javascript.

What does ‘manipulating functions as values’ means? Let’s look at what we can do with values in javascript:

  • We can assign a value to a variable
  • We can pass a value as an argument to a function
  • We can return a value from a function

Well, we can do just that with functions as well!

[…] functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.

First-class function – Wikipedia, the free encyclopedia

So, instead of relying on the props variable from the Row component in our makeSquare() function’s code, we could dynamically generate our makeSquare() function with a fixed value for the props.number (which is the only member of the props object we really need).

First, let’s return our makeSquare() function from another function that takes the row number as argument:

function makeSquareForRow(rowNumber) {
    return function (Piece, index) {
        const column = String.fromCharCode(97 + index)

        return (
            <Square
                key={column + rowNumber}
                row={rowNumber}
                column={column}
            >
                {Piece && <Piece />}
            </Square>
        )
    }
}

As you can see, the makeSquareForRow() function only takes the rowNumber as a parameter. It then returns an anonymous function, that will be used to make several squares with the same rowNumber.

The reason that the returned function can access the rowNumber variable is because it pertains to the makeSquareForRow() scope, so it can access every variables in this scope, hence, the rowNumber (passed as argument to makeSquareForRow, so available in the whole function body). This is a common trick of functional programming, and the returned function is called a closure.

The outer function contains a local variable and a closure that references this variable.
A closure can access local variables in its outer function’s scope.

Next we have to assign this returned function to a variable, so we can access it each time we want to output a square:

function Row(props) {
    const makeSquare = makeSquareForRow(props.number)

    return (
        <tr>
            <th>{props.number}</th>
            {props.data.map(makeSquare)}
            <th>{props.number}</th>
        </tr>
    )
}

This way, the makeSquare() function is generated once for each row, with the corresponding row number, and then called once for each square.

Note: The purity of this function can be discussed. As we are outputting some HTML in the end, we are modifying the state of the program. So the overall HTML will be different whether this function was called before or after other functions modifying the HTML… Take this refactoring a step further if you like, and make the function only returns the properties of the Square component. If you do, you might as well write some tests about it.

Source code

Composing functions

We could use the same trick we used with makeSquareForRow() function to refactor the makeRow() function from the App component into a ‘pure’ function as well. However, because we are doing Functional Programming doesn’t mean we should forget the simplest programming mantras like Don’t Repeat Yourself!

Partial application

Just think back of what we did with the makeSquareForRow(): we fixed the rowNumber argument, and returned a function that only takes the Piece and index arguments. You may think that such an operation could be a common one in a paradigm that uses functions as first-class citizens, how right you are! This principle is called partial application.

In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.

Partial Application – Wikipedia, the Free Encyclopedia

To make our makeSquareForRow() function compatible with partial application, we can write it this way:

function makeSquareForRow(rowNumber, Piece, index) {
    const column = String.fromCharCode(97 + index)

    return (
        <Square
            key={column + rowNumber}
            row={rowNumber}
            column={column}
        >
            {Piece && <Piece />}
        </Square>
    )
}

Now our function takes three arguments, but we can fix the first one in an anonymous function (using the arrow syntax):

function Row(props) {
    const makeSquare = (Piece, index) => makeSquareForRow(props.number, Piece, index)

    return (
        <tr>
            <th>{props.number}</th>
            {props.data.map(makeSquare)}
            <th>{props.number}</th>
        </tr>
    )
}

As you can see, the makeSquare() function is a function that takes the Piece and index arguments, and calls the makeSquareForRow() function.

Source code

Higher-Order Functions

Now, the common operation of fixing one argument of a function is done in an anonymous function. What about giving it a name, so we can reuse it in another part of our code?

const fixFirst(fn, arg) = (...args) => fn(arg, ...args)

You know what? We just created a Higher-Order Function here!

In mathematics and computer science, a higher-order function is a function that does at least one of the following:

– takes one or more functions as arguments,

– returns a function as its result.

Higher-Order Functions – Wikipedia, The free encyclopedia

As you can see, our fixFirst() function takes any function as first argument, and any argument as second… argument.

It then returns a new function, that takes any number of arguments and computes the result of the first-order function (the one we previously passed as parameter to the higher-order function), with the first arguments fixed from the higher-order function’s scope and the remaining arguments passed to the composed function.

That’s quite a lot for a one-liner, ain’t it? In fact, functional programming tends to make the code of applications more terse. So it is important that you understand the underlying principles and techniques to not be lost when reading such code…

Wait a minute, didn’t I introduce functional programming as a way to bring clearness into complex applications? And now I just tell you that you have to understand the principles to not be lost into their code, ain’t it a little paradoxical here? Well, maybe… let’s discuss it in the next section, shall we?

Source code

Toward referential transparency

So, all of this was quite some work, just to have a program that still behave the same from the previous, non functional version. What benefits do we get for such an endeavor?

Let’s recapitulate what we’ve done:

  • We hid the implementation details behind abstractions (functions) to make our code more declarative.
  • We made sure that the functions were pure so their results wouldn’t be affected by any global value.
  • We wrote an unit test to verify that the squareStyle() function behave as we expect.
  • We used functions as first-class citizens, so we can create new functions dynamically, as they are needed.
  • We composed functions by using partial application, so we could use functions with fixed arguments.
  • We created a higher-order function so we could reuse a common behavior while composing functions.

In the end, our program has several new named functions that are called throughout the code. But are these functions troublesome for understanding what the program does?

Let’s use the makeSquare() as an example:

function Row(props) {
    const makeSquare = fixFirst(makeSquareForRow, props.number)

    return (
        <tr>
            <th>{props.number}</th>
            {props.data.map(makeSquare)}
            <th>{props.number}</th>
        </tr>
    )
}

While looking at the returned JSX markup, I can see that each entry of the props.data array will be mapped through the makeSquare() function. As I have assigned this function into a constant, I know that its value (the function) can’t be re-assigned, so it will always be the same function that will be called.

This means I could replace the variable by its value if I wanted:

function Row(props) {
    return (
        <tr>
            <th>{props.number}</th>
            {props.data.map(fixFirst(makeSquareForRow, props.number))}
            <th>{props.number}</th>
        </tr>
    )
}

And I could replace the fixFirst() function by its corresponding code just the same way, and it wouldn’t change how the program behaves, because the function is pure.

function Row(props) {
    return (
        <tr>
            <th>{props.number}</th>
            {props.data.map((...args) => makeSquareForRow(props.number, ...args))}
            <th>{props.number}</th>
        </tr>
    )
}

The function expression can again be replaced by its computed value, which depends only on its arguments: the expression and the returned value are equivalents. But does writing directly the values brings clarity?

Not really, because it means we have more code to understand at the same time. While by programming declaratively, we can reason about each part of the program independently. And we can test them independently as well!

Being able to replace an expression with a corresponding value is called referential transparency. And I hope you now can see how it improves the readability of our code. So I’ll close here with a more formal definition.

An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program’s behavior. This requires that the expression be pure, that is to say the expression value must be the same for the same inputs and its evaluation must have no side effects.

Referential Transparency – Wikipedia, the free encyclopedia

Quizz time!

Did you follow my rambling all along? High five! You are definitely as passionate about the details as I am. Take the quizz and show me that you master the basics of functional programming:

Resources

A good guide is helpful when you try to learn a new paradigm. I surely didn’t come up with all of this on my own! So I followed Federico Kereki’s book, Mastering Functional Javascript Programming – Second Edition (Packt Publishing).

The book start from a classic javascript problem (no React involved) and then, chapter after chapter, dives way further into the intricacies of functional programming than I did here. Enhancing each chapter with questions for you to practice.

This is quite a dense source of information, and you’ll need several sessions to digest all of it. Although, I believe it is a perfect way to understand a paradigm that is being more and more used in applications and frameworks nowadays.

If you wonder if you should abandon Object Oriented Programming in favor of Functional Programming, I invite you to read Uncle Bob’s take about it: FP vs OO – Clean Coder Blog.

Whichever way you choose to learn about functional programming, enjoy your learning!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.