2

Author: Fernando Doglio
Translator: Front-end Xiaozhi Source: medium

If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.

Readers often ask me, Brother Zhi, what is your VSCode theme, take a good look at it, can you share it? Exactly, I saw 10 good-looking VSCdoe themes today (Sunday), the first of which is what I use, and I will share it with you here.

Object-oriented programming and functional programming are two very different programming paradigms with their own rules and advantages and disadvantages.

However, instead of following one rule all the time, JavaScript is right in the middle of these two, providing some aspects of ordinary OOP languages, such as classes, objects, inheritance, and so on. But at the same time, it also gives you some concepts of functional programming, such as higher-order functions and the ability to compose them.

Higher order functions

We begin with the most important of the three concepts for pedestrians: higher-order functions.

Higher-order functions mean that functions are not just one that can be defined and called from code, in fact, you can use them as assignable entities. If you've used some JavaScript, this shouldn't be surprising. It's very common to assign anonymous functions to constants.

 const adder = (a, b) => {
  return a + b
}

The above logic is invalid in many other languages, being able to assign a function like an integer is a very useful tool, in fact, most of the topics covered in this article are by-products of that function.

Benefits of Higher-Order Functions: Encapsulating Behavior

With higher-order functions, we can not only assign functions as above, but also pass them as arguments when the function is called. This opens the door to creating a very dynamic code base from which complex behavior can be passed directly as a parameter to reuse it.

Imagine working in a pure object-oriented environment and you want to extend the functionality of a class to accomplish a task. In this case, you might use inheritance by encapsulating that implementation logic in an abstract class, which is then extended into a set of implementation classes. This is perfect OOP behavior, and it works, we:

  • Created an abstract structure to encapsulate our reusable logic
  • Secondary construct created
  • We reuse the original class and extend it

Now, what we want is to reuse logic, we can simply extract the reusable logic into a function and pass that function as a parameter to any other function, this way, we can save some of the creation of "boilerplate", Because, we are just creating functions.

The following code shows how to reuse program logic in OOP.

 //Encapsulated behavior封装行为stract class LogFormatter {
  
  format(msg) {
    return Date.now() + "::" + msg
  } 
}

//重用行为
class ConsoleLogger extends LogFormatter {
  
  log(msg) {
    console.log(this.format(msg))
  }  
}

class FileLogger extends LogFormatter {

  log(msg) {
    writeToFileSync(this.logFile, this.format(msg))
  }
}

The second example is to extract the logic into functions, which we can mix and match to easily create what we want. You can keep adding more formatting and writing features, then just mix them up with one line of code:

 // 泛型行为抽象
function format(msg) {
  return Date.now() + "::" + msg
}

function consoleWriter(msg) {
  console.log(msg)
}

function fileWriter(msg) {
  let logFile = "logfile.log"
  writeToFileSync(logFile, msg)
}

function logger(output, format) {
  return msg => {
    output(format(msg))
  }
}
// 通过组合函数来使用它
const consoleLogger = logger(consoleWriter, format)
const fileLogger = logger(fileWriter, format)

Both methods have advantages, and both are very effective, no one is optimal. Just to show the flexibility of this approach here, we have the ability to pass behaviors (i.e. functions) as parameters as if they were primitive types (like integers or strings).

The benefit of higher-order functions: clean code

A good example of this benefit is the Array method, such as forEach , map , reduce and so on. In non-functional programming languages such as C, iterating over the elements of an array and transforming them requires the use of a for loop or some other looping construct. This requires us to write code in a specific way, which is the process of the requirements description cycle happening.

 let myArray = [1,2,3,4]
let transformedArray = []

for(let i = 0; i < myArray.length; i++) {
  transformedArray.push(myArray[i] * 2) 
}

The above code mainly does:

  • i ,该myArray的索引, 0myArray的长度
  • For each value of i myArray ---, multiply the value of i at the position of ---759be37d5e86b5964aeb6a57046043c9--- and add it to the transformedArray array .

This approach works and is relatively easy to understand, however, the complexity of this logic goes up as the complexity of the project goes up, and so does the cognitive load. However, it's easier to read like this:

 const double = x => x * 2;

let myArray = [1,2,3,4];
let transformedArray = myArray.map(double);

This way is easier to read than the first way, and since the logic is hidden in two functions ( map and double ), you don't have to worry about understanding their work principle. You could also hide the multiplication logic inside the function in the first example, but the traversal logic has to be there, which adds some unnecessary hindrance to reading.

Currying

Function currying is the technique of transforming a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returns a new function that accepts the remaining parameters and returns the result. Let's look at an example:

 function adder(a, b) {
  return a + b
}

// 变成
const add10 = x => adder(a, 10)

Now, if all you had to do was add 10 to a series of values, you could call add10 instead of calling adder 5f9c43dc0abd9b0804d915432b5147c1 each time with the same second parameter adder . This example may seem silly, but it embodies the ideal of 柯里化 .

You can think of currying as the inheritance of functional programming, and then go back to the logger example along the same line, and get the following:

 function log(msg, msgPrefix, output) {
  output(msgPrefix + msg)
} 

function consoleOutput(msg) {
  console.log(msg)
}

function fileOutput(msg) {
  let filename = "mylogs.log"
  writeFileSync(msg, filename)
}

const logger = msg => log(msg, ">>", consoleOutput);
const fileLogger = msg => log(msg, "::", fileOutput);

log 's function requires three arguments, and we introduced it into a dedicated version that requires only one argument, since the other two arguments have been chosen by us.

Note that the log function is treated as an abstract class here, just because in my example, I don't want to use it directly, but there is no limit to doing so because it's just a normal function. If we were using a class, we wouldn't be able to instantiate it directly.

Combinatorial functions

Function composition is the process of combining two or more functions to generate a new function. Composing functions together is like snapping a chain of pipes together to let data flow through.

In computer science, function composition is the act or mechanism of combining simple functions into more complex functions. Just like normal function composition in mathematics, the result of each function is passed as the argument of the next function, and the result of the last function is the result of the whole function .

This is the definition of function composition from Wikipedia, the bolded part is the more critical part. When using currying, there is no such restriction, we can easily use preset function parameters.

Code reuse sounds great, but it's hard to achieve. If the code is too business specific, it will be difficult to reuse it. Such code is too general and simple, and rarely used. So we need to balance the two, a way to make smaller, reusable parts that we can use as building blocks to build more complex functionality.

In functional programming, functions are our building blocks. Each function has its own function, and then we combine the required functions (functions) to complete our needs. This way is a bit like Lego blocks. In programming, we call it a combined function.

Take a look at the following two functions:

 var add10 = function(value) {
    return value + 10;
};
var mult5 = function(value) {
    return value * 5;
};

The above is a bit verbose, let's rewrite it with arrow functions:

 var add10 = value => value + 10;
var mult5 = value => value * 5;

Now we need a function that adds 10 to the incoming parameter, and then multiplies it by 5, as follows:

Now we need a function that adds 10 to the incoming parameter, and then multiplies it by 5 , as follows:

 var mult5AfterAdd10 = value => 5 * (value + 10)

Even though this is a very simple example, I still don't want to write this function from scratch. First, there may be a mistake here, like forgetting parentheses. Second, we already have a function add10 that adds 10 and a function mult5 that multiplies by 5, so here we are writing the code we have already repeated.

Refactor mult5AfterAdd10 using functions add10 , mult5 :

 var mult5AfterAdd10 = value => mult5(add10(value));

We just used the existing function to create mult5AfterAdd10 , but there is a better way.

In mathematics, f ∘ g is function composition, called "f composed by g", or more commonly " f after g ". So (f ∘ g)(x) is equivalent to f(g(x)) which means calling f after calling g .

In our case, we have mult5 ∘ add10 or " add10 after mult5 ", so the name of our function is called mult5AfterAdd10 . Since Javascript itself doesn't do function composition, let's see how Elm wrote it:

 add10 value =
    value + 10
mult5 value =
    value * 5
mult5AfterAdd10 value =
    (mult5 << add10) value

In Elm , << means to use a combined function, in the above example the value is passed to the function add10 and its result is passed to mult5 . You can also combine any number of functions like this:

 f x =
   (g << h << s << r << t) x

Here x is passed to function t , the result of function t is passed to r , the result of function t is passed to s , and so on. Do something similar in Javascript and it would look like g(h(s(r(t(x))))) , a bracket nightmare.

Everyone said that there is no project to write on the resume, so I helped you find a project, and also included a [Building Tutorial] .

Common functional functions (Functional Function)

3 common functions in functional languages: Map , Filter , Reduce .

The following JavaScript code:

 for (var i = 0; i < something.length; ++i) {
    // do stuff
 }

There is a big problem with this code, but not a bug. The problem is that it has a lot of boilerplate code. If you program in imperative languages like Java, C#, JavaScript, PHP, Python, etc., you will find that you write the most code. That's the problem .

Now let's solve the problem step by step, and finally encapsulate it into an invisible for syntax function:

First modify the above code with an array named things :

 var things = [1, 2, 3, 4];
for (var i = 0; i < things.length; ++i) {
    things[i] = things[i] * 10; // 警告:值被改变!
}
console.log(things); // [10, 20, 30, 40]

This is very wrong, the value has been changed!

After re-editing:

 var things = [1, 2, 3, 4];
var newThings = [];
for (var i = 0; i < things.length; ++i) {
    newThings[i] = things[i] * 10;
}
console.log(newThings); // [10, 20, 30, 40]

The things value is not modified here, but newThings is modified. Leave that alone for now, after all we're using JavaScript. Once a functional language is used, everything is immutable.

Now wrap the code into a function, which we name map because the function of this function is to map each value of an array to a new value of the new array.

 var map = (f, array) => {
    var newArray = [];
    for (var i = 0; i < array.length; ++i) {
        newArray[i] = f(array[i]);
    }
    return newArray;
};

The function f is passed in as a parameter, then the function map can perform arbitrary operations on each item of the array array.

Now rewrite the previous code using map :

 var things = [1, 2, 3, 4];
var newThings = map(v => v * 10, things);

No for loop here! And the code is more readable and easier to analyze.

Now let's write another common function to filter elements in an array:

 var filter = (pred, array) => {
    var newArray = [];
for (var i = 0; i < array.length; ++i) {
        if (pred(array[i]))
            newArray[newArray.length] = array[i];
    }
    return newArray;
};

The predicate function pred returns TRUE when certain items need to be preserved, and FALSE otherwise.

Use a filter to filter out odd numbers:

 var isOdd = x => x % 2 !== 0;
var numbers = [1, 2, 3, 4, 5];
var oddNumbers = filter(isOdd, numbers);
console.log(oddNumbers); // [1, 3, 5]

The filter function is much simpler than manual programming with a for loop. The last common function is called reduce. Usually this function is used to reduce a sequence of numbers to a number, but in fact it can do a lot of things.

In functional languages, this function is called fold .

 var reduce = (f, start, array) => {
    var acc = start;
    for (var i = 0; i < array.length; ++i)
        acc = f(array[i], acc); // f() 有2个参数
    return acc;
});

The reduce function accepts a reduction function f , an initial value start , and an array array .

These three functions, map, filter, and reduce allow us to bypass the repetitive way of for loops and do some common operations on arrays. But in functional languages, there is only recursion and no loops, and these three functions are even more useful. Incidentally, in functional languages, recursive functions are not only very useful, but essential.


The bugs that may exist after the code is deployed cannot be known in real time. In order to solve these bugs afterwards, a lot of time is spent on log debugging. By the way, I recommend a useful bug monitoring tool , Fundebug .

Original: https://blog.bitsrc.io/functional-programming-in-functions-composition-and-currying-3c765a50152e

comminicate

If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.


王大冶
68.1k 声望105k 粉丝