JS functional programming in plain English

Foreword: I believe that all of you in mo (Mo) yu (Hua) commune (shui) have seen many words such as Currie, functional programming and pure function. When I just entered the front end, I often see, for I have no experience are basically in the clouds and fog, do not understand these flashy operations have what use, my husband has always been a direct Jquery shuttle (escape). Now work a few years to look back, gradually realize the secret place among them. This article is in the learning process incidentally record to share, as far as possible simple and popular arrangement and expression, understanding of functional programming.

1. Functional and imperative programming

Functional Programming (REFERRED to as FP), for many front-end beginners will feel this word is very lofty, daunting, thinking that I usually write enough to meet the needs, why to learn what Functional Programming, increase the burden. In fact, I also have this idea, but in the actual coding process, many students should encounter such problems:

The function logic of page B is similar to page A, but the parameters are slightly different. So just sacrifice CV method, copy a large section of the function, fix it, and call it a day. The next day C page also has similar, or directly CV copy, continue to modify. All of a sudden, one day, there is a requirement, and the judgment condition in this logic needs to be changed… (Ah!! Don’t speak code de!! After I believe that we are clear, only Ctrl + F search, replace one by one.

Functional programming is a good way to solve this problem. In fact, there are many shadows of functional programming in the front end, such as arrow functions in ES6, react.Memo (), and so on. Having said that, it’s time to get to the point. Before we dive into functional programming, let’s look at imperative programming

Imperative programming

Now we have a requirement, a list of products:

let shoppingCart = [
    { productTitle: "Functional Programming".type: "books".amount: 10 },
    { productTitle: "Kindle".type: "eletronics".amount: 30 },
    { productTitle: "Shoes".type: "fashion".amount: 20 },
    { productTitle: "Clean Code".type: "books".amount: 60}]Copy the code

Now calculate the total amount of all the books in your shopping cart. (: This simple I will, take up the pen to write a lengthy code as follows:

let bookAmount = 0;

for (let i = 0; i < shoppingCart.length; i++) {
    if (shoppingCart[i].type === 'books') { bookAmount += shoppingCart[i].amount; }}console.info(bookAmount);
Copy the code

There’s nothing wrong with it. It’s simple and intuitive. Write whatever data you want. But code reuse is not as efficient, for example, if you want to calculate the total cost of the categories of electronic products, or maybe you have to multiply the quantity later. There is no doubt much that needs to be changed. How do you write it in terms of functions

Functional programming

Using the same example, using functional programming would look like this :(ignore compose for a moment, we’ll get to that later.)

// Note: the following map and other methods are different from the native ones. They are all from the Ramda library

const byBooks = (order) = > order.type == "books";
const getAmount = (order) = > order.amount;
const sumAmount = (acc, amount) = > acc + amount;

const getTotalAmount = compose(
    reduce(sumAmount, 0),
    map(getAmount),
    filter(byBooks)
)

getTotalAmount(shoppingCart); / / 70
Copy the code

When we want to change the calculation category, we simply replace the byBooks function. Or if you add a quantity, you have to multiply the quantity. That just needs to add one more operation to compose. This will automatically apply wherever this function is used.

Compose, as its name suggests, is a combination. Combine the functions passed in with the output of the previous function as the input of the next function, somewhat similar to a pipe. Each function acts as a workbench, processing the incoming data and sending it out to the next, pipelined process. The order of compose calls is right-to-left (bottom-up). Here’s a simple illustration:

2. A few concepts

I’ve talked about a lot of functional programming concepts, but I think I know enough about them by now. Some excellent libraries such as Ramda already provide off-the-shelf API calls for functional programming. The rest of this article focuses on internal implementations of functional programming, such as Curry and Compose.

Let’s go back and think about why javascript is such a functional programming language. I believe many students will always see a sentence when reading relevant technical books:

The function is first class citizen.

Why? Because functions have the following powers:

  • You can use variable names
  • Can be used as arguments to other functions
  • It can be returned as a result of a function

Taking a look at the example above, functions like map can take a function directly and process it.

Pure function (no side effects)

Those of you who have used React know the concept of pure functions. The simple definition is: For the same input, there will always be the same output, completely independent of external changes, and will not cause any observable side effects. Here’s an example:

Implement a function that computes the area of a circle as follows:

const PI = 3.14
const calcualteArea = (radius) = > radius * radius * PI;
calcualteArea(10) / / 314
Copy the code

That’s fine, but we would say it’s an impure function. Why? In fact, in the calcualteArea function, PI is a global variable, and the function depends on that global variable. Imagine changing the value of the global variable when PI is actually 42. So 10 times 10 times 42 is 4,200. For the same input value 10 you get different output values. So what is a pure function? The modified code is as follows:

const PI = 3.14
const calcualteArea = (radius, pi) = > radius * radius * pi;
calcualteArea(10, PI) / / 314
Copy the code

Now, we pass the value of PI directly to the function as an argument. In this way, the function does not depend directly on the outside, but only on the parameters passed to the function. For the same input, you always get the same output.

Side effects that interact with the external environment of a function are side effects.

let counter = 1;

function increaseCounter(value) {
    counter = value + 1
}

increaseCounter(counter)
console.log(counter) / / 2
Copy the code

The above function, which takes an integer value and returns an incremented value of 1, modifies the global object and could cause a devastating BUG if other functions use this counter. We make the following modifications:

let counter = 1;

function increaseCounter(value) {
    return value + 1
}

increaseCounter(counter) / / 2
console.log(counter) / / 1
Copy the code

So the value of counter is still the same.

Following the principle of a pure function and no side effects makes our program more controllable. Each function is independent and will not affect our system. This is very important for functional programming. The use of currization, described later, prevents us from being trapped in the vortex of passing parameters when we write pure functions.

Higher-order functions

In general, higher-order functions refer to:

  • Takes one or more functions as arguments or
  • Return a function as the result

The commonly used map, filter, and reduce functions are typical higher-order functions. The parameter is a function.

const arr = [2.3.4]
const mapfun = (val) = > val * 2
arr.map(mapfun)
Copy the code

Consider an example that returns a function as a result. Suppose you want to sort an array by any key passed in. We can write this:

const compareByKeys = (key) = > {
    return (a, b) = > {
        return a[key] - b[key]
    }
}

arr = [{ name: 'John'.age: 22 }, { name: 'Tom'.age: 18 }, { name: 'Ada'.age: 25 }, { name: 'Tim'.age: 12 }]

arr.sort(compareByKeys(age)) // Sort output by age
Copy the code

A function is returned from the compareByKeys function above and passed to sort to be called. This is possible because functions are first-class citizens. And with that, we can implement continuous calls to functions like fn(a)(b)(c).

React higher-order components do the same thing, receiving one component and returning another. In higher-order components, it is possible to do some processing on props, inject some logic, etc

3. Essential operations

I’ve explained why functional programming is possible in javascript and some related concepts. The following sections focus on several essential operations for functional programming.

Currie,

The name corrification sounds fancy, but in plain English it is simple: call a function with only a few arguments and let it return a function that handles the rest. For example, a function can receive three arguments. After corrification, it can pass the first argument and return a function, and then call the function to pass in a second argument or multiple arguments, and so on until the parameters required by the function are satisfied. Without further ado, let’s take an example:

var add = function(x) {
    return function(y) {
        return x + y
    }
}
var increment = add(1)
increment(2); / / 3

var addTen = add(10)
addTen(2); / / 12
Copy the code

In the example above, we define an add function that takes one argument x and returns another function that takes another argument y and returns the value of x + y. When add is called, x should normally be reclaimed, but the function returned contains the parameter x, so it is not reclaimed. Yes, this is the familiar closure that returns a function that remembers the first argument x in the closure so it can be accessed in subsequent functions. This is a simple example of currization.

In plain English, cremation is the process by which your logic is executed in steps. Let’s say you’re writing a simple input field data processing that strips whitespace from strings and separates them with ‘-‘ characters. Perhaps one day another requirement for input field handling would be to remove Spaces from strings and separate them with ‘/’ characters. In this way, we can “pre-load” the function to remove whitespace from the string, and then return the corresponding content based on the different delimiters passed in.

Obviously, the above simple example is only a simple two-parameter Cremation process. In practice, we need to support multi-parameter and more general cremation.

First, let’s explain the general idea. There are two main points:

  • Determine the number of parameters, when the passed parameters have met the required parameters of the function, you can directly run the function, return the result
  • When arguments are not satisfied, a function must be returned to continue collecting arguments.

The general flow chart is as follows:

Specific code implementation:

function curry(fn){
    return function f(){
        const args = [].slice.call(arguments)
        if (args.length < fn.length) {
            return function() {
                return f.apply(this, args.concat([].slice.call(arguments)))}}else {
            return fn.apply(this, args)
        }
    }
}
Copy the code

Of course, this is a simple implementation that is not perfect, as is the absence of parameter placeholders. For those interested, read ramda’s implementation code for Currization. Now let’s use currie to implement the string whitespace substitution problem described above.

const replace = curry((a, b, str) = > str.replace(a, b)) // Create a Currified function that takes three arguments, the replaced, to be replaced, and the target string

const replaceSpaceWith = replace(/\s/g) // Preloads a function to replace whitespace

const replaceSpaceWithDash = replaceSpaceWith(The '-') // Construct the function substituted by -

const replaceSpaceWithSlash = replaceSpaceWith('/') // Construct the function replaced by /

replaceSpaceWithDash('a b c') // a-b-c
replaceSpaceWithSlash('a b c') // a/b/c
Copy the code

As shown in the code above, we can store the operation of replacing Spaces and then pass in different replacement characters according to the actual situation. Usually, we use Currization to make a function univalued, increasing the applicability and diversity of the function.

But more importantly, single-valued functions have more meaning for the following combination of functions.

Function composition

The combination of functions, as I mentioned briefly earlier, is the combination of functions. Suppose we have a requirement to invert an array, capitalize the first element and print it out. We would normally write:

const reverse = (arr) = > arr.reverse()
const upper = (arr) = > arr[0].toUpperCase()
const log = (arr) = > console.log(arr)

const arr = ['a'.'b'.'c']
log(upper(reverse(arr)))
Copy the code

For example, “compose” is something that many of you have written about in some ways. It’s not very elegant and it looks uncomfortable. How about “compose”?

const change = compose(log, upper, reverse)
const arr = ['a'.'b'.'c']
change(arr)
Copy the code

Isn’t it much clearer? This combinatorial writing is a bit like a pipe, except that it runs from right to left, calling one another, passing in the results of the previous function as arguments. So the function, in our hands, becomes like a Lego piece, and we can put it together, and we can combine all kinds of functions, and that’s the charm.

Now, we said that currie is a way of making functions singlevalued, and that’s exactly what we’re saying that the function in the middle of a combination of functions must take only one parameter. This is the combination of a multi-parameter function that is first processed (Coriolization) and then assembled according to actual needs (function combination).

Now let’s look at a simple implementation of the compose method, which is a good way to think about it:

  • composeAccept the need to combine functions andReturns a wrapped function
  • You need to use closures to record the list of functions and, in turn, fromExecute from right to leftThe result of each execution is used as an argument to the next function to be executedreduceRightimplementation

The implementation code is as follows:

const compose = (. fns) = > {
    return (. args) = > {
        return fns.reduceRight((val, fn) = > { return fn.apply(null, [].concat(val)) }, args)
    }
}

// The simplified version is as follows

const compose = (. fns) = > (. args) = > fns.reduceRight((val, fn) = > fn.apply(null, [].concat(val)), args)
Copy the code

4, practice

This example comes from a functional Flickr

So much has been said on paper, but here is a direct example to feel:

Write a simple function that takes an image from Flickr and displays it on the page. The way we write it, we make an Ajax request, pull the corresponding data from the returned data, and add it to the page in a loop. Here we use functional writing to achieve:

// Start with jquery and Ramda
requirejs.config({
    paths: {
        ramda:'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min'.jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min'}})/ / application
reuire([
    'ramda'.'jquery'].function (_, $) {
        // Define two impure functions, the request API and render to HTML, to make it controllable
        // Use the Currization to make it a single-valued function
        var Impure = {
            getJSON: _.curry((callback, url) = > {
                $.getJSON(url, callback)
            }),

            setHtml: _.curry((sel, html) = > {
                $(sel).html(html)
            })
        }
        
        // Construct the URL passed to the URL parameter in the Impure. GetJSON function
        var url = (term) = >'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + term + '&format=json&jsoncallback=? '
        
        ///////////////////////////////////////////
        // The following is a request to obtain the data after the integration of extraction
        // Note: prop is used to obtain the value of the object similar to a[prop]
        var mediaUrl = _.compose(_.prop('m'), _.prop('media'))
       
        var srcs = _.compose(_.map(mediaUrl), _.prop('items'))
        
        // Items is the fetched array. You can use the address request API above to see the returned data structure
        // The next step is to set the display to the page
        
        var img = (url) = > { return $('<img />', { src: url }) }
        
        var images = _.compose(_.map(img), srcs)
        var renderImages = _.compose(Impure.setHtml("body"), images)
        var app = _.compose(Impure.getJSON(renderImages), url)
    }
)
Copy the code

Done. First we use Kerrization to turn a multi-parameter function into a single value, which can be used in subsequent combinations of functions. As you can see, the basic idea is to build small pieces of functionality and then slowly put them together. Although it may not be as intuitive as direct imperative programming, it is better than clarity, with every step of the operation traceable, making it easier to trace problems. You don’t get bogged down in a bunch of logic. Of course, there are places to optimize, see the example link above for details.

5, summary

This article briefly introduces some concepts related to functional programming, and two important tools: currization and function composition. In fact, in our business oriented, we can say that functional programming is rarely used, and it is all one of the three frameworks. Learning to understand functional programming is not to say that you must use it, but to use it, but to broaden your programming ideas, there is a suddenly enlightened feeling: Oh!! You could write it this way! Maybe that knowledge will come in handy when you hit a brick wall.

Finally, if this article helps you, don’t be stingy with your likes! ❤ ❤ ❤

The resources

Redux/React: How to use functional programming in Redux/React Functional programming, as well as simple applications in JavaScript, React Curritization -Ramda source code implementation