javascript functional programming ---- function combination

Function combination

Concept of function combination: if a function needs to be processed by multiple functions to get the final value, we can combine these intermediate process functions into a new function at this time.

Functions are like pipelines of data. Function combination is to connect these pipelines and let the data pass through multiple pipelines to form the final result

Function combinations are executed from right to left by default

When combining functions, try to use pure functions with only one parameter

Basic function combination

When we use pure functions and Coriolis, it is easy to write onion code, H (g (f (x)), that is, layer by layer code. For example, we need to get the last element of the array and convert it into uppercase letters.

First call the array object's reverse method to invert the array, then call the first method to get the first element of the array, then call the toUpper method to convert the first element to uppercase.

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const array = ['a', 'b', 'c', 'd'];
let end = toupper(first(reverse(array)));
console.log(end)

It can be found that the above methods are called layer by layer. This is the onion code. This is the most basic function combination. Let's encapsulate it.

For example, the above example needs to call three functions: reverse, first and toUpper. We can combine these three functions into one to get the test function. When calling, it is still passed into the array array, and the processing result is unchanged. Function combination is actually equivalent to hiding the intermediate results of multiple function calls. For example, reverse is passed to first, and first is passed to toUpper.

const array = ['a', 'b', 'c', 'd'];
function compose(fn1,fn2,fn3){
    return function(...args){
        return fn1(fn2(fn3(...args)))
    }
}
const test = compose(toupper,first,reverse);
console.log(test(array ))

Functions are like data pipelines. Function combination is to connect these pipelines and let the data pass through multiple pipelines to form the final result.

Combination of multiple functions

Next, realize the combination of multiple functions, simulate the principle of combined functions, the customary writing method of general function combination, and the combined functions are executed from right to left. We need to reverse it. Here, we need to reverse args. After reversing, we need to call the functions inside in turn, and the return value of the previous function needs to be the parameter of the next function.

function compose (...args) {
  return function (value) {
    return args.reverse().reduce(function (acc, fn) {
      return fn(acc)
    }, value)
  }
}
const test = compose(toupper,first,reverse);
console.log(test(array))

Characteristics of function combination

The combination of functions should meet the association law: we can combine g and h, or f and G, and the results are the same

const test = compose(toUpper, compose(first, reverse))
console.log(test(['one', 'two', 'three']))

It can be found that whether we combine the first two or the second two first, we get the same result, which is the combination law, which is the same as the combination law in mathematics.

Debugging of function combination

When we use function combination, if the result of our execution is inconsistent with our expectation, how should we debug at this time?

For example, in the following code, when we want to know the result of reverse execution. We can print a function called reverse in front of it

Now let's implement a function to change "NEVER SAY DIE" into "NEVER SAY DIE". The idea is

      1. First, use the split function to cut the string into an array with spaces -- > [never, say, die],

      2. Then use the map function to change each item of the array into lowercase, [never, say, die],

      3. Finally, use the join function to merge the array into the string "never say die".

In function combination, what we need is a pure function with only one parameter. The following split and join functions are pure functions with two parameters, so we use the Coriolis of functions to convert them into pure functions with only one parameter

function curry (func) {
  return function curriedFn(...args) {
    // Determine the number of arguments and formal parameters
    if (args.length < func.length) {
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    return func(...args)
  }
};

const split = curry(function(step,arr){
  return arr.split(step)
})
const map = function(arr){
  return arr.map(function(item){
    return item.toLowerCase()
  })
}
const join = curry(function(step,str){
  return str.join(step)
})

Then write a debug print function trace, which is transformed into a pure function that only needs one parameter by using the coritization of the function. Its first parameter tag describes the print result after the execution of which function is the current result (the print information after the end of which pipeline)

const trace = curry((tag, v) => {
  console.log(tag, v)
  return v
})

Next, start the function combination

const f = compose(join('-'), trace('map after'), map, trace('split after'), split(' '));
console.log(f('NEVER SAY DIE'))

Well, the debugging method is completed. Click the complete code

function compose (...args) {
  return function (value) {
    return args.reverse().reduce(function (acc, fn) {
      return fn(acc)
    }, value)
  }
};

function curry (func) {
  return function curriedFn(...args) {
    // Determine the number of arguments and formal parameters
    if (args.length < func.length) {
      return function () {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    return func(...args)
  }
};

const trace = curry((tag, v) => {
  console.log(tag, v)
  return v
})
const split = curry(function(step,arr){
  return arr.split(step)
})
const map = function(arr){
  return arr.map(function(item){
    return item.toLowerCase()
  })
}
const join = curry(function(step,str){
  return str.join(step)
})

const f = compose(join('-'), trace('map after'), map, trace('split after'), split(' '))

console.log(f('NEVER SAY DIE'))

pointerFree programming style

Its specific implementation is the function combination mentioned above. We can define the process of data processing as a synthesis operation independent of parameters. There is no need to use the parameter representing the data, just combine some simple operation steps together.

Do not use the value to be processed, only synthesize the operation process. Chinese can be translated into "worthless" style.

1. It is not necessary to specify the data to be processed;

2. Only the synthesis operation process is required;

3. Some auxiliary basic operation functions need to be defined

 

When using function combination to deal with problems, it is actually a PointFree mode. For example, in the following case, we first synthesize some basic operations into a function, and there is no data to be processed in this process. This is PointFree mode.

Let's take an example: we still need to convert Hello World into Hello_ The form of world;

According to our traditional way of thinking, we will first define a function to receive a data we want to process, and then we process our data in this function to get the result we want. This is a non PointFree mode.

function f (word) {
    return word.toLowerCase().replace(/\s+/, '_');
}
f('Hello World')

If we use PointFree mode to solve this problem, we will first define some basic operation functions, and then turn them into a new function. In the process of synthesis, we do not need to specify the data we need to process.

const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
console.log(f('Hello World'))

Another example: extract the first letter of a word and convert it to uppercase

const fp = require('lodash/fp')
const firstLetterToUpper = fp.flowRight(join('.'),fp.map(fp.flowRight(fp.first,fp.toUpper)), split(' '))
console.log(firstLetterToUpper('world wild web'))
// => W. W. W

Let's review the core of functional programming, which is to abstract the operation process into functions. PointFree pattern is to synthesize our abstract function into a new function, and this synthesis process is actually an abstract process. In this abstract process, we still don't need to care about data.

Above, we use PointFree mode to implement the above case. Lodash function library is used for function combination here; Lodash # is a very useful library. The curry method in lodash used earlier is curry. The compose combination method we simulated above is also replaced by the flowRight method of lodash library;

There is also fp module in lodash library, which provides a practical and friendly method for functional programming, which has been cored!, If the parameter of a method is a function, it will require function priority and data lag.

 

Keywords: Javascript

Added by yarub on Tue, 08 Feb 2022 03:51:24 +0200