MEMEPh. ideas that are worth sharing...

What is JavaScript functional programming

Foreword


Functional programming has become a very hot topic on the front end. In the last few years, we've seen a lot of application codebases using functional programming ideas heavily.

This article will omit the introduction of those obscure concepts, and focus on what is functional code in JavaScript, the difference between declarative and imperative code, and what are the common functional models? 

 

1. What is functional programming


Functional programming is a programming paradigm, which mainly uses functions to encapsulate the operation process, and calculates the results by combining various functions. Functional programming means you can write less buggy code in less time. As a simple example, suppose we want to capitalize the first letter of each word in functional programming is great, we can do it like this:

var string = 'functional programming is great';
var result = string
  .split(' ')
  .map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
  .join(' ');

The above example first uses split to convert the string to an array, then converts the first letter of each element to uppercase by map, and finally converts the array to a string by join. The whole process is join(map(split(str)))that it embodies the core idea of ​​functional programming: transforming data through functions .

From this we can see that functional programming has two basic characteristics:

 

2. Contrasting Declarative and Imperative


// imperative
var CEOs = [];
for(var i = 0; i < companies.length; i++){
CEOs.push(companies[i].CEO)
}


//Declarative
var CEOs = companies.map(c => c.CEO);

From the above example, we can see that the declarative writing is an expression, and we don't need to care about how to iterate the counter and how to collect the returned array. It specifies what to do, not how to do it. An obvious benefit of functional programming is this declarative code . For pure functions without side effects, we can focus on writing business code without considering how the function is implemented internally.

 

3. Common Features


No side effects

It means that the external state will not be modified when calling a function, that is, a function still returns the same result after n times of calling.

var a = 1;
// has a side effect, it modifies the external variable a
// Multiple calls have different results
function test1() {
a++
return a;
}



// no side effects, no modification of external state
// Same result if called multiple times
function test2(a) {
return a + 1;
}

 

Transparent referencing

It means that a function will only use the variables passed to it and the variables created by itself, and will not use other variables.

var a = 1;
var b = 2;
// variables used inside the function do not belong to its scope
function test1() {
return a + b;
}

// variables used inside the function are explicitly passed in
function test2(a, b) {
return a + b;
}

 

Immutable variable

It means that once a variable is created, it cannot be modified, and any modification will generate a new variable. The biggest benefit of using immutable variables is thread safety. Multiple threads can access the same immutable variable at the same time, making parallelism easier to achieve. Since JavaScript does not natively support immutable variables, it needs to be implemented through a third-party library. (like Immutable.js, Mori, etc.)

var obj = Immutable({ a: 1 });
var obj2 = obj.set('a', 2);
console.log(obj);  // Immutable({ a: 1 })
console.log(obj2); // Immutable({ a: 2 })

 

Functions are first-class citizens

We often say that functions are "first-class citizens" in JavaScript, which means that functions are on an equal footing with other data types, and can be assigned to other variables, passed as parameters, passed into another function, or used as other functions. The return value. Closures, higher-order functions, function currying, and function composition, which are described below, are all applications around this feature.

 

4. Common functional programming models


1. Closure

A function is a closure if it references free variables. What are free variables? Free variables refer to variables that do not belong to the scope of the function (all global variables are free variables, strictly speaking, functions that reference global variables are closures, but such closures are useless, usually we say A closure refers to a function inside a function).

The conditions for the formation of the closure:

The purpose of closures:

You can define some scope-limited persistent variables, which can be used for caching or intermediate calculations .

// Simple caching tool
// anonymous function creates a closure
const cache = (function() {
const store = {};
return {
get(key) {
return store[key];
},

set(key, val) {
store[key] = val;
} 
} 
}());

console.log(cache) //{get: ƒ, set: ƒ}
cache.set('a', 1);
cache.get('a'); // 1

The above example is the implementation of a simple caching tool. The anonymous function creates a closure, so that the store object can always be referenced and will not be recycled.

Disadvantages of closures: Persistent variables will not be released normally, and will continue to occupy memory space, which can easily cause memory waste , so some additional manual cleanup mechanisms are generally required.

 

2. Higher order functions

Functional programming tends to reuse a common set of functional capabilities to process data by using higher-order functions. A higher-order function refers to a function that takes a function as an argument, takes a function as a return value, or takes both a function as an argument and a function as a return value .

Higher-order functions are often used to:

The JavaScript language natively supports higher-order functions. For example, Array.prototype.map, Array.prototype.filter and Array.prototype.reduce are built-in higher-order functions in JavaScript. Using higher-order functions will make our code clearer and more concise. .

 

map

The map() method creates a new array whose result is the result of calling a provided function for each element in the array. map does not change the original array.

Suppose we have an array of objects containing name and kind properties, and we want all the name properties in this array to be placed in a new array, how to achieve this?

// do not use higher order functions

var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];

var names = [];
for (let i = 0; i < animals. length; i++) {
names.push(animals[i].name);
}

console.log(names); //["Fluffykins", "Caro", "Hamilton", "Harold", "Ursula", "Jimmy"]
// use higher order functions

var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];

var names = animals.map(x=>x.name);
console.log(names); //["Fluffykins", "Caro", "Hamilton", "Harold", "Ursula", "Jimmy"]

 

filter

The filter() method creates a new array containing all the elements tested by the callback function. filter calls the callback function once for each element in the array. The callback function returns true if the element passes the test and retains the element, and false if it does not. filter does not change the original array, it returns the new filtered array.

Suppose we have an array of objects containing name and kind properties. We want to create an array containing only dogs (species: "dog"). How to achieve it?

// do not use higher order functions
var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];

var dogs = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].species === "dog") dogs.push(animals[i]);
}
console.log(dogs);
// use higher order functions
var animals = [
  { name: "Fluffykins", species: "rabbit" },
  { name: "Caro", species: "dog" },
  { name: "Hamilton", species: "dog" },
  { name: "Harold", species: "fish" },
  { name: "Ursula", species: "cat" },
  { name: "Jimmy", species: "fish" }
];
var dogs = animals.filter(x => x.species === "dog");
console.log(dogs); // {name: "Caro", species: "dog"}
// { name: "Hamilton", species: "dog" }

 

reduce

The reduce method executes the callback function on each element of the calling array, finally producing a single value and returning it. The reduce method accepts two parameters: 1) the reducer function (callback), 2) an optional initialValue.

Suppose we want to sum an array:

// do not use higher order functions
const arr = [5, 7, 1, 8, 4];
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
console.log(sum);//25
// use higher order functions
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue,0);
console.log(sum)//25

We can vividly show the difference between the three through the following figure:
 

3. Function currying

Currying is also known as partial evaluation. The currying function will receive some parameters, and then will not be evaluated immediately, but will continue to return a new function, save the incoming parameters in the form of a closure, and wait until it is actually evaluated At the same time, all incoming parameters are evaluated at one time.

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

Here we define an add function that takes one argument and returns a new function. After calling add, the returned function remembers the first parameter of add by means of a closure. So, how do we implement a simple curried function?

function curryIt(fn) {
// The number of parameters of the parameter fn function
var n = fn.length;
var args = [];
return function(arg) {
args.push(arg);
if (args.length < n) {
return arguments.callee; // return a reference to this function
} else {
return fn.apply(this, args);
} 
};
}
function add(a, b, c) {
return [a, b, c];
}
var c = curryIt(add);
var c1 = c(1);
var c2 = c1(2);
var c3 = c2(3);
console.log(c3); //[1, 2, 3]

From this we can see that currying is a method of "preloading" functions by passing fewer parameters to get a new function that already remembers these parameters. In a sense, this is a kind of The "cache" of parameters is a very efficient way to write functions!

 

4. Function composition (Composition)

As mentioned earlier, one of the hallmarks of functional programming is evaluating functions by concatenating them. However, as the number of concatenated functions increases, the readability of the code decreases. Function composition is the method used to solve this problem.

Suppose there is a compose function that takes multiple functions as arguments and returns a new function. When we pass an argument to this new function, the argument "flows" through the function within it and returns the result.

//combination of two functions
var compose = function(f, g) {
return function(x) {
return f(g(x));
};
};
//or
var compose = (f, g) => (x => f(g(x)));
var add1 = x => x + 1;
var mul5 = x => x * 5;
compose(mul5, add1)(2);// =>15