Functional programming

Keywords: Javascript Programming

Previous remarks

Unlike Lisp and Haskell, JavaScript is not a functional programming language, but in JavaScript you can manipulate functions as objects, that is, you can apply functional programming techniques in javascript.Array methods in ES5, such as map() and reduce (), are well suited for the functional programming style.Functional programming is described in detail in this paper.

 

Function Processing Array

Assume you have an array with elements that are numbers, and you want to calculate the mean and standard deviation of these elements.If you use a non-functional programming style, the following is true.

var data = [1,1,3,5,5];
var total = 0;
for(var i = 0 ; i < data.length; i++){
    total += data[i];
}
var mean = total/data.length;
total = 0;
for(var i = 0; i < data.length; i++){
    var deviation = data[i] - mean;
    total += deviation * deviation;
}
var stddev = Math.sqrt(total/(data.length-1));

You can use the array methods map() and reduce() to achieve the same calculation, which is extremely concise

var sum = function(x,y){
    return x+y;
}
var square = function(x){
    return x*x;
}
var data = [1,1,3,5,5];
var mean = data.reduce(sum)/data.length;
var deviations = data.map(function(x){
    return x - mean;
});
var stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));

In ES3, these array methods are not included and the map() and reduce() functions need to be customized

//Call function for each array element f(),And returns an array of results
//If Array.prototype.map If defined, use this method
var map = Array.prototype.map ? function(a,f){return a.map(f);}
          : function (a,f){
                var results = [];
                for(var i = 0,len=a.length; i < len; i++){
                    if(i in a){
                        results[i] = f.call(null,a[i],i,a);
                    }
                }
                return results;
            }
//Using functions f()And optional initial values will be an array a Reduce to a value
//If Array.prototype.reduce Use this method if it exists
var reduce = Array.prototype.reduce 
            ? function(a,f,initial){
                if(arguments.length > 2){
                    return a.reduce(f,initial);                    
                }else{
                    return a.reduce(f);
                }
            }
            : function(a,f,initial){
                var i = 0, len = a.length ,accumulator;
                if(argument.length > 2){
                    accumulator = initial;
                }else{
                    if(len == 0){
                        throw TypeError();
                    }
                    while(i < len){
                        if(i in a){
                            accumulator = a[i++];
                            break;
                        }else{
                            i++;
                        }
                    }
                    if(i == len){
                        throw TypeError();
                    }
                }
                while(i < len){
                    if(i in a){
                        accumulator = f.call(undefined,accumulator,a[i],i,a);
                    }
                    i++;
                }
                return accumulator;
            }

 

Higher order function

A higher-order function is a function of an operation function that takes one or more functions as parameters and returns a new function

//This higher-order function returns a new function that passes its arguments in f(),And back f Logical non of return value of
function not(f){
    return function(){
        var result = f.apply(this,arguments);
        return !result;
    };
}
var even = function(x){
    return x % 2 === 0;
}
var odd = not(even);
[1,1,3,5,5].every(odd);//true

The not() function above is a higher-order function because it takes a function as a parameter and returns a new function

The following mapper() function also takes a function as a parameter and returns a new function that maps an array onto another array using the function

//The parameters of the returned function should be a real parameter group and the function should be executed on each array element f(),And returns an array of all the calculated results
function mapper(f){
    return function(a){
        return map(a,f);
    }
}
var increment = function(x){
    return x+1;
}
var incrementer = mapper(increment);
increment([1,2,3]);//[2,3,4]

The following is a more common example, which takes two functions f() and g() and returns a new function to calculate f(g())

//Returns a new calculable f(g(...))Functions of
//Functions returned h()Pass in all its arguments g(),Then the g()Return value of f()
//call f()and g()Hourly this Value and call h()Hourly this Value is the same this
function compose(f,g){
    return function(){
        //Need to give f()Pass in a parameter, so use f()Of call()Method
        //Need to give g()Many parameters are passed in, so use g()Of apply()Method
        return f.call(this,g.apply(this,arguments));
    };
}
var square = function(x){
    return x*x;
}
var sum = function(x,y){
    return x + y;
}
var squareofsum = compose(square,sum);
squareofsum(2,3);//25

 

Incomplete function

Incomplete functions are a function transformation technique that splits a complete function call into multiple function calls, each incoming argument is part of the complete argument, each split function is called an incomplete function, and each function call is called an incomplete call.The feature of this function transformation is that each call returns a function until the final result is run.

The bind() method of function f() returns a new function, passes a specific context and a specified set of parameters to the new function, and then calls the function f().The bind() method simply places the arguments to the left of the full argument list, meaning that the arguments passed in bind() are placed at the beginning of the argument list passed in to the original function, but sometimes you want the arguments passed in bind() to be placed to the right of the full argument list.

//Implement a tool function to array class objects(Or object)Convert to Real Array
function array(a,n){
    return Array.prototype.slice.call(a,n||0);
}
//The arguments to this function are passed to the left
function partialLeft(f){
    var args = arguments;
    return function(){
        var a = array(args,1);
        a = a.concat(array(arguments));
        return f.apply(this,a);
    };
}
//The arguments to this function are passed to the right
function partialRight(f){
    var args = arguments;
    return function(){
        var a = array(arguments);
        a = a.concat(array(args,1));
        return f.apply(this,a);
    };
}
//The arguments to this function are used as templates, as shown in the argument list undefined Values are populated
function partial(f){
    var args = arguments;
    return function(){
        var a = array(args,1);
        var i = 0, j = 0;
        //ergodic args,Fill in from internal arguments undefined value
        for(;i<a.length;i++){
            if(a[i] === undefined){
                a[i] = arguments[j++];
            }
            //Now append the remaining internal arguments
        };
        a = a.concat(array(arguments,j));
        return f.apply(this,a);
    }
}
//This function has three arguments
var f = function(x,y,z){
    return x*(y - z);
}
//Note the difference between these three incomplete calls
partialLeft(f,2)(3,4);//2*(3-4)=-2
partialRight(f,2)(3,4);//3*(4-2)=6
partial(f,undefined,2)(3,4);//3*(2-4)=-6

Using this programming technique for incomplete functions, you can write interesting code that uses existing functions to define new ones

var increment = partialLeft(sum,1);
var cuberoot = partialRight(Math.pow,1/3);
String.prototype.first = partial(String.prototype.charAt,0);
String.prototype.last = partial(String.prototype.substr,-1,1);

Events become particularly interesting when incomplete calls are combined with other higher-order functions.For example, the following example defines the not() function

var not = partialLeft(compose,function(x){
    return !x;
});
var even = function(x){
    return x % 2 === 0;
};
var odd = not(even);
var isNumber = not(isNaN);

The code for averaging and standard deviation can be reorganized using a combination of incomplete calls, a very pure functional programming style

var data = [1,1,3,5,5];
var sum = function(x,y){return x+y;}
var product = function(x,y){return x*y;}
var neg = partial(product,-1);
var square = partial(Math.pow,undefined,2);
var sqrt = partial(Math.pow,undefined,.5);
var reciprocal = partial(Math.pow,undefined,-1);
var mean = product(reduce(data,sum),reciprocal(data.length));
var stddev = sqrt(product(reduce(map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1))));

 

memory

In functional programming, this caching technique is called memory.Memory is just a programming technique, essentially sacrificing the spatial complexity of the algorithm for better time complexity. The execution time complexity of code in client-side javascript is often a bottleneck, so in most scenarios, this sacrifice of space for time to improve program execution efficiency is notCommonly desirable

//Return f()Version with memory
//Only when f()It only works if the string representations of arguments are different
function memorize(f){
    var cache = {};//Save Value in Closure
    return function(){
        //Converts an argument to a string and uses it as a cached key
        var key = arguments.length + Array.prototype.join.call(arguments ,",");
        if(key in cache){
            return cache[key];
        }else{
            return cache[key] = f.apply(this,arguments);
        }
    }
}

The memorize() function creates a new object that is hosted by the cache and assigned to a local variable, so it is private to the returned function.The returned function converts its real parameter group to a string and uses the string as the property name of the cache object.If this value exists in the cache, it is returned directly; otherwise, the parameters are calculated by calling the defined function, cached and returned

//Returns the greatest common divisor of two integers
function gcd(a,b){
    var t;
    if(a < b){
        t = b, b = a, a = t; 
    }
    while(b != 0){
        t = b, b = a % b, a = t;
    }
    return a;
}
var gcdmemo = memorize(gcd);
gcdmemo(85,187);//17

When writing a recursive function, memory is often required. We prefer to call a recursive function that implements memory instead of the original recursive function.

var factorial = memorize(function(n){
    return (n<=1) ? 1 : n*factorial(n-1);
});
factorial(5);//120

 

Continuous calls to uniparameter functions

The following uses continuous calls to uniparameter functions to implement a simple addition operation

add(num1)(num2)(num3)...; 
add(10)(10) = 20
add(10)(20)(50) = 80
add(10)(20)(50)(100) = 180

If you implement exactly as above, you cannot because add(1)(2) if you return 3, add(1)(2)(3) will necessarily fail.Thus, there are two methods of distortion

The first distortion is as follows:

add(num1)(num2)(num3)...; 
add(10)(10)() = 20
add(10)(20)(50)() = 80
add(10)(20)(50)(100)() = 180
function add(n){
    return function f(m){  
        if(m === undefined){
            return n;
        }else{
            n += m;
            return f;
        }
    }
}
console.log(add(10)());//10
console.log(add(10)(10)());//20
console.log(add(10)(10)(10)());//30

The second distortion is as follows:

add(num1)(num2)(num3)...; 
+add(10)(10) = 20
+add(10)(20)(50) = 80
+add(10)(20)(50)(100) = 180
function add(n) {
    function f(m){
        n += m;
        return f;
    };
    f.toString = f.valueOf = function () {return n}
    return f;
}
console.log(+add(10));//10
console.log(+add(10)(10));//20
console.log(+add(10)(10)(10));//30

Posted by talkster5 on Wed, 17 Jul 2019 10:05:42 -0700