current position:Home>Functional programming of front-end development

Functional programming of front-end development

2022-04-29 09:18:06Programming meow

General functional programming language , yes Haskell, Considered a pure functional language by functional fundamentalists . The idea of functional programming is also constantly affecting the traditional programming language , such as Java 8 Start supporting lambda expression , The building of functional programming was originally based on lambda Calculated and built . For front-end programming, it must be an option , The back end does not have to .

React The components of the framework have not only supported class components from a very early time , It also supports functional components . Then React Hooks Appearance , It makes the idea of functional programming more and more indispensable .

No side effect

This is the essence of functional programming .

Functions without side effects should meet the following characteristics :

  1. There should be input parameters . If there are no input parameters , This function can't get any external information , You don't have to run .

  2. There must be a return value . If there is an input, there is no return value , No side effects , So this function is white .

  3. For certain inputs , There are definite outputs

Mathematical functions are like this :

const sqr3 = function(x){
    return x * x * x; 
}
console.log(sqr3(2));

Side effect free functions have three great benefits :

  1. It can be cached . We can use the dynamic programming method to save the intermediate value , Used to replace the execution result of the actual function , Greatly improve efficiency .

  2. High concurrency . Because it doesn't depend on the environment , Can be scheduled to another thread 、worker Even on other machines , There is no environmental dependence anyway .

  3. Easy to test , Easy to prove correct . It is not easy to cause occasional problems , It has nothing to do with the environment , Very conducive to testing .

Combinatorial function

After functions that have no side effects , You need a good combination of these functions .

Aforementioned sqr3 There's a problem with the function , If not number type , The calculation will go wrong . According to the imperative idea , We may just go straight to modify sqr2 Code for , Like this :

/**  imperative  */
const sqr3 = function(x){
    if (typeof x === 'number'){
        return x * x * x;
    }
    return 0;
}

/**  Functional expression  */
const isNum = x => typeof x === 'number';
console.log(sqr3(isNum("20")));

Or we're designing sqr3 The location of a preprocessing function is reserved first , If you want to upgrade in the future, change this preprocessing function , The subject logic remains unchanged :

/** fn  As   Preprocessing functions  */
const sqr3 = function(fn, x){
    const y = fn(x);
    return y * y; 
}

const sqr3New = function(x){
    return sqr3(isNum,x);
}
console.log((sqr3New(2.2)));

​​​​ Ability of container to encapsulate functions

If we want to reuse this for other functions isNum The ability of , You can encapsulate a container object to provide this capability :


class MayBeNumber{
    constructor(x){
        this.x = x;
    }

    map(fn){
        if (isNum(this.x)){
            return MayBeNumber.of(fn(this.x));
        }
        return MayBeNumber.of(0);
        
    }

    getValue(){
        return this.x;
    }
}

such , No matter what object we get , Use it to construct a MayBeNumber The object comes out , Call this object again map Method to call a mathematical function , I brought it myself. isNum The ability of :

const notnum = new MayBeNumber(undefined).map(Math.sin).getValue();
console.log(notnum);

You can find , The output value is from NaN Turned into 0. And another benefit of encapsulation into objects is It can be used "." Called many times .

also , Another benefit of using object encapsulation is , Nested function calls are in the reverse order of imperative expressions , While using map Is consistent with the imperative , Execute first and write first :

const num = new MayBeNumber(1).map(Math.sin).map(sqr2).getValue();
console.log(num);

of encapsulation new

The above is encapsulated in an object , But functional programming hasn't come out yet new Object re map, The best thing is that the construction object is also a function . Define it as of Method :

MayBeNumber.of = function(x){
    return new MayBeNumber(x);
}
const num = MayBeNumber.of(2).map(Math.tan).map(Math.exp).getValue();
console.log(num);

Let's look at another situation , When we deal with the return value , If there is Error, Don't deal with it Ok The return value of , It can be written like this :

class Result{
    constructor(Ok, Err){
        this.Ok = Ok;
        this.Err = Err;
    }

    isOk(){
        return this.Err === null || this.Err === undefined;
    }

    map(fn){
        return this.isOk() 
                 ? Result.of(fn(this.Ok),this.Err) 
                 : Result.of(this.Ok, fn(this.Err));
    }
}

Result.of = function(Ok, Err){
    return new Result(Ok, Err);
}

console.log(Result.of(2, undefined).map(sqr3));

//  The output is :Result { Ok: 8, Err: undefined }

This is a container design pattern :

  1. There is a container for storing values

  2. This container provides a map function , Role is map Function so that the function it calls can be calculated with the value in the container , The final return is the object of the container

We can call this design pattern Functor Functor . If this container also provides a of Function converts a value into a container , Then it's called Pointed Functor. such as JavaScript Medium Array type .

Simplify the object level

With the help of Result structure , Yes sqr3 Format the return value of . If it's a numerical value ,Ok Numerical value ,Err yes undefined. If it's not numerical ,Ok yes undefined,Err yes 0:

const sqr3Res = function(x){
    if (isNum(x)){
        return Result.of(x * x * x, undefined);
    }
    return Result.of(undefined, 0);
}

console.log(Result.of(4.3, undefined).map(sqr3Res));
//  Output results :Result { Ok: Result { Ok: 18.49, Err: undefined }, Err: undefined }

What is returned is a nested result , But what we need is a son Result Value . I need one Result Add one more join function :

class Result{
    constructor(Ok, Err){
        this.Ok = Ok;
        this.Err = Err;
    }

    isOk(){
        return this.Err === null || this.Err === undefined;
    }

    map(fn){
        return this.isOk() 
                ? Result.of(fn(this.Ok),this.Err) 
                : Result.of(this.Ok, fn(this.Err));
    }

    join(){
        if (this.isOk()) {
            return this.Ok;
        }
        return this.Err;
    }

    flatMap(fn){
        return this.map(fn).join();
    }
}
Result.of = function(Ok, Err){
    return new Result(Ok, Err);
}

console.log(Result.of(3, undefined).flatMap(sqr3Res));

//  Output results :Result { Ok: 27, Err: undefined }

Loosely speaking , image Result This implementation flatMap Functional Pointed Functor, It's legendary Monad.

Partial functions and higher-order functions

The biggest difference between functional programming and command-line programming :

  1. A function is a first-order formula , We should be familiar with saving functions in variables and then calling them

  2. Functions can appear in the return value , The most important usage is to put the input as n(n>2) A function with two parameters is converted to n individual 1 A concatenated call of two parameters , This is the legendary Coriolis . This new function with reduced parameters , We call it a partial function

  3. Functions can be used as arguments to functions , Such functions are called higher-order functions .

How to use the functional method to implement a valid function that only executes once ?

once It's a function of higher order , The return value is a function , If done yes false, Will done Set to true, And then execute fn.done Is at the same level as the return function , So it will be acquired by the closure memory :

const once = (fn) => {
    let done = false;
    return function() {
        return done ? undefined : ((done=true), fn.apply(this,arguments));
    }
}

const initData = once(
    () => {
        console.log("Initialize data");
    }
);

initData();
initData();

You can find , Second call init_data() Nothing happened .

Recursion and memory

Recursion is a complex in functional programming , The simplest recursion is factorial :

const factorial = (n) => {
    if (n === 0){
        return 1;
    }
    return n * factorial(n - 1);
}

console.log(factorial(10));

This will repeat the calculation many times , Low efficiency , You should use dynamic programming or cache memory . you 're right , We can encapsulate one called memo To achieve this function :

const memo = (fn) => {
    const cache = {};
    return (arg) => cache[arg] || (cache[arg] = fn(arg));
}

  Use memo Post factorial of :

 const fastFact = memo(
    (n) => {
        if (n <= 0){
            return 1;
        }
        return n * fastFact(n-1);
    }
);

Speech return front end ,React Hooks Medium useMemo This is the memory mechanism used :

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

summary

In everyday use , You need to remember these points :

  1. The core of functional programming is to store functions in variables , Used in parameters , Used in the return value ;

  2. When programming, always remember to No side effect and side effect codes are separated ;

  3. There is a mathematical basis behind functional programming , It's not just a design pattern .

copyright notice
author[Programming meow],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/119/202204290630304441.html

Random recommended