函数式编程

author author     2022-09-03     200

关键词:

和Lisp、Haskell不同,javascript并非函数式编程语言,但在javascript中可以操控对象一样操控函数,也就是说可以在javascript中应用函数式编程技术。ES5中的数组方法(如map()和reduce())就可以非常适合用于函数式编程风格。本文将详细介绍函数式编程

 

函数处理数组

  假设有一个数组,数组元素都是数字,想要计算这些元素的平均值和标准差。若使用非函数式编程风格的话,如下所示 

技术分享

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));

技术分享

  可以使用数组方法map()和reduce()实现同样的计算,这种实现极其简洁

技术分享

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));

技术分享

  在ES3中,并不包含这些数组方法,需要自定义map()和reduce()函数

技术分享

//对于每个数组元素调用函数f(),并返回一个结果数组//如果Array.prototype.map定义了的话,就使用这个方法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;
            }

技术分享

技术分享

//使用函数f()和可选的初始值将数组a减到一个值//如果Array.prototype.reduce存在的话,就使用这个方法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)指操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数

技术分享

//这个高阶函数返回一个新的函数,这个新函数将它的实参传入f(),并返回f的返回值的逻辑非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

技术分享

  上面的not()函数就是一个高阶函数,因为它接收一个函数作为参数,并返回一个新函数

  下面的mapper()函数,也是接收一个函数作为参数,并返回一个新函数,这个新函数将一个数组映射到另一个使用这个函数的数组上

技术分享

//所返回的函数的参数应当是一个实参数组,并对每个数组元素执行函数f(),并返回所有计算结果组成的数组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]

技术分享

  下面是一个更常见的例子,它接收两个函数f()和g(),并返回一个新函数用以计算f(g())

技术分享

//返回一个新的可以计算f(g(...))的函数//返回的函数h()将它所有的实参传入g(),然后将g()的返回值传入f()//调用f()和g()时的this值和调用h()时的this值是同一个thisfunction compose(f,g){    return function(){        //需要给f()传入一个参数,所以使用f()的call()方法
        //需要给g()传入很多参数,所以使用g()的apply()方法
        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

技术分享

 

不完全函数

  不完全函数是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数,每次函数调用叫做不完全调用。这种函数变换的特点是每次调用都返回一个函数,直到得到最终运行结果为止

  函数f()的bind()方法返回一个新函数,给新函数传入特定的上下文和一组指定的参数,然后调用函数f()。bind()方法只是将实参放在完整实参列表的左侧,也就是说传入bind()的实参都是放在传入原始函数的实参列表开始的位置,但有时希望将传入bind()的实参放在完整实参列表的右侧

技术分享

//实现一个工具函数将类数组对象(或对象)转换为真正的数组function array(a,n){    return Array.prototype.slice.call(a,n||0);
}//这个函数的实参传递到左侧function partialLeft(f){    var args = arguments;    return function(){        var a = array(args,1);
        a = a.concat(array(arguments));        return f.apply(this,a);
    };
}//这个函数的实参传递到右侧function partialRight(f){    var args = arguments;    return function(){        var a = array(arguments);
        a = a.concat(array(args,1));        return f.apply(this,a);
    };
}//这个函数的实参被用作模板,实参列表中的undefined值都被填充function partial(f){    var args = arguments;    return function(){        var a = array(args,1);        var i = 0, j = 0;        //遍历args,从内部实参填充undefined值
        for(;i<a.length;i++){            if(a[i] === undefined){
                a[i] = arguments[j++];
            }            //现在将剩下的内部实参都追加进去        };
        a = a.concat(array(arguments,j));        return f.apply(this,a);
    }
}//这个函数有三个实参var f = function(x,y,z){    return x*(y - z);
}//注意这三个不完全调用之间的区别partialLeft(f,2)(3,4);//2*(3-4)=-2partialRight(f,2)(3,4);//3*(4-2)=6partial(f,undefined,2)(3,4);//3*(2-4)=-6

技术分享

  利用这种不完全函数的编程技巧,可以编写一些有意思的代码,利用已有的函数来定义新的函数

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);

  当将不完全调用和其他高阶函数整合在一起时,事件就变得格外有趣了。比如,下例定义了not()函数

技术分享

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

技术分享

  可以使用不完全调用的组合来重新组织求平均数和标准差的代码,这种编码风格是非常纯粹的函数式编程

技术分享

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))));

技术分享

 

记忆

  将上次的计算结果缓存起来,在函数式编程中,这种缓存技巧叫做记忆(memorization)。记忆只是一种编程技巧,本质上是牺牲算法的空间复杂度以换取更优的时间复杂度,在客户端javascript中代码的执行时间复杂度往往成为瓶颈,因此在大多数场景下,这种牺牲空间换取时间的做法以提升程序执行效率的做法是非常可取的

技术分享

//返回f()的带有记忆功能的版本//只有当f()的实参的字符串表示都不相同时它才会工作function memorize(f){    var cache = {};//将值保存到闭包内
    return function(){        //将实参转换为字符串形式,并将其用做缓存的键
        var key = arguments.length + Array.prototype.join.call(arguments ,",");        if(key in cache){            return cache[key];
        }else{            return cache[key] = f.apply(this,arguments);
        }
    }
}

技术分享

  memorize()函数创建一个新的对象,这个对象被当作缓存的宿主,并赋值给一个局部变量,因此对于返回的函数来说它是私有的。所返回的函数将它的实参数组转换成字符串,并将字符串用做缓存对象的属性名。如果在缓存中存在这个值,则直接返回它;否则,就调用既定的函数对实参进行计算,将计算结果缓存起来并返回

技术分享

//返回两个整数的最大公约数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

技术分享

  写一个递归函数时,往往需要实现记忆功能,我们更希望调用实现了记忆功能的递归函数,而不是原递归函数

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

 

连续调用单参函数

  下面利用连续调用单参函数来实现一个简易的加法运算

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

  如果完全按照上面实现,则无法实现,因为add(1)(2)如果返回3,add(1)(2)(3)必然报错。于是,有以下两种变形方法

  第一种变形如下:

add(num1)(num2)(num3)…; 
add(10)(10)() = 20add(10)(20)(50)() = 80add(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)());//10console.log(add(10)(10)());//20console.log(add(10)(10)(10)());//30

技术分享

  第二种变形如下:

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));//10console.log(+add(10)(10));//20console.log(+add(10)(10)(10));//30



前端学习之函数式编程—函数式编程概念+头等函数(代码片段)

什么是函数式编程函数式编程(functionprogrammingFP)FP是编程范式之一,我们常听说的还有,面向过程编程,面向对象编程函数式编程的思维方式把现实世界的事物和事物之间的联系抽象到程序世界(对运算... 查看详情

函数式编程

  函数是编程就是用计算机函数取模拟数学里面的函数,如下:#数学函数:y=2*x+1#函数式编程:deftest(x):return2*x+1test(x)  函数式编程lisp就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,模拟数... 查看详情

kotlin函数式编程①(函数式编程简介|高阶函数|函数类别|transform变换函数|过滤函数|合并函数|map变换函数|flatmap变换函数)(代码片段)

文章目录一、函数式编程简介1、编程范式2、高阶函数3、函数式编程4、前端开发技术二、函数类别三、变换函数四、map变换函数1、map函数原型分析2、map函数设计理念3、代码示例五、flatMap变换函数1、flatMap函数原型分析2、代码... 查看详情

函数式编程语言

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambdacalculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。 和指令式编程相比,函数式编程... 查看详情

函数式编程

和Lisp、Haskell不同,javascript并非函数式编程语言,但在javascript中可以操控对象一样操控函数,也就是说可以在javascript中应用函数式编程技术。ES5中的数组方法(如map()和reduce())就可以非常适合用于函数式编程风格。本文将详细介... 查看详情

python3函数式编程

目录函数式编程高阶函数mapreducefilter返回函数匿名函数(lambda)装饰器参考资料相关知识:函数内部的变量在函数执行完后就销毁;Python中一切皆变量。函数式编程概念函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编... 查看详情

函数式编程

函数式编程(英语:functionalprogramming)或称函数程序设计、泛函编程,是一种编程范式,它将计算机运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambdacalculus)为该语言最重要的基础。而且,&lamb... 查看详情

函数式编程

编程范式函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperativeprogramming),函数式编程,逻辑式编程,常见的面向对象编程是也是一种命令式编程。命令式编程是面向计算机硬件的抽象,有变量(对应着存储... 查看详情

函数式编程vs命令式编程

函数式编程vs命令式编程函数式编程属于声明式编程(DeclarativeProgramming),SQL就是典型的声明式编程。函数式编程(FunctionalProgramming)关注行为强调what,对应于数学里面的函数的对应法则。像餐馆里吃饭,提出自己的要求即可数学... 查看详情

简单聊聊:函数式编程

函数式编程(FunctionalProgramming)是一种以函数为基础的编程方式和代码组织方式,能够带来更好的代码调试及项目维护的优势。本篇主要结合笔者在实际项目开发中的一些应用,简要谈谈函数式编程。函数在函数式编程中,任何代... 查看详情

你真的理解函数式编程吗?

你真的理解函数式编程吗? 大数据以及人工智能越来越流程,你是否可以轻松适应大数据编程,函数式编程在其中起着重要作用,如何从面向对象编程跳槽到函数式编程?你是否觉得函数式各种概念难于理解?本场Chat将为... 查看详情

函数式编程

函数式编程初探分享按钮作者: 阮一峰日期: 2012年4月6日诞生50多年之后,函数式编程(functionalprogramming)开始获得越来越多的关注。不仅最古老的函数式语言Lisp重获青春,而且新的函数式语言层出不穷,比如Erlang、clo... 查看详情

函数式编程和高阶函数

函数式编程函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设... 查看详情

javascript函数式编程

第1章JavaScript函数式编程简介11.1JavaScript案例11.2开始函数式编程41.2.1为什么函数式编程很重要41.2.2以函数为抽象单元71.2.3封装和隐藏91.2.4以函数为行为单位101.2.5数据抽象141.2.6函数式JavaScript初试171.2.7加速191.3Underscore示例221.4总结2... 查看详情

函数式编程与面向对象编程[关闭]

】函数式编程与面向对象编程[关闭]【英文标题】:FunctionalprogrammingvsObjectOrientedprogramming[closed]【发布时间】:2011-01-0522:39:56【问题描述】:到目前为止,我主要接触OO编程,并期待学习一门函数式语言。我的问题是:什么时候选... 查看详情

函数式编程基本概念(代码片段)

编程范式命令式编程(Imperative)声明式编程(Declarative)函数式编程(Functional)面向对象、泛型、元编程也都是很主要的编程范式命令式编程:命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么声... 查看详情

函数式编程简介-附入门方法(代码片段)

WHAT?什么是函数式编程?函数式编程是一种编程范式。编程范式又是什么?编程范式是一种解决问题的思路。我们熟悉的命令式编程把程序看作一系列改变状态的指令;而函数式编程把程序看作一系列数学函数映射的组合。编程... 查看详情

函数式编程

在函数式编程中,函数被视为一等公民,这意味着它们可以绑定到名称(包括本地标识符),作为参数传递,并从其他函数返回,就像任何其他数据类型一样。这允许以声明性和可组合的风格编写程序,其中小功能以模块化方式... 查看详情