zoukankan      html  css  js  c++  java
  • javascript 函数式编程(1)

    函数式编程(Functional Programming) 简单说就是把函数当参数传递给其他函数。个人认为 FP 在软件抽象中占很重要的地位,作为程序员的话极力推荐掌握其中的思维方法。最早这思想出现在数学中, f(x) 中的x可以是变量也可以是函数,比如 f(f(y))。而当时计算机语言的函数还都只能接收变量参数而不接受函数参数,于是数学家发明了一种新的语言 scheme (Lisp的一个变种),到现在所有语言无不支持函数式编程。.net, java, python, php5.3+, c的函数指针,c++11还引入了lambda和functional,对js开发者来说更是重要,ext,jq等随处可见FP的影子。

    FP 的最大特点是使代码更贴近于大脑思维流程,所以代码会更容易掌控。

    1. 从一个例子开始:遍历一个数组,打印所有元素

    // 方法1
    for(var i=0; i<arr.length; i++) {
        print(arr[i]);
    }
    
    // 方法2
    function forEach(action, arr) {
      for(var i=0; i<arr.length; i++) 
        action(arr[i], i);
    }
    forEach(print, arr);
    

    先不考虑代码重用性,方法2的优点就是直观。forEach(print, arr) 对应 遍历(打印,数组)  -- 大脑差不多就是这么想的。 这个例子可能过于简单,不能体现方法2的好处,后续碰到的问题越复杂越能体现FP的直观。

    上面通过 forEach 把遍历这个行为封装起来,而把各种行为函数作为参数传递,通过修改各种行为来实现各种功能,比如用 forEach 实现累加(sum):

    function sum(numbers) {  
        var total = 0;  
        forEach(function(number) {  
            total += number;  
        }, numbers);  
        return total;  
    }  
    print(sum([1, 10, 100])); //结果为 111  
    

    在看点复杂的,比如函数构造器:

    function makeAddFunction(amount) {  
        function add(number) {  
            return number + amount;  
        }  
        return add;  
    }  
    var addTwo = makeAddFunction(2);  
    var addFive = makeAddFunction(5);  
    print( addTwo(1) + addFive(1) ); //结果为 9  
    

    和 函数适配器:

    function negate(func) {  
        return function() {  
            return ! func.apply(null, arguments);  
        };  
    }  
    var isNotNaN = negate(isNaN);  
    print( isNotNaN(NaN) );//结果为 false  
    

    可以看到,不光参数可以是函数,返回值也一样可以,这种能操纵其他函数的函数叫作高阶函数

    2. 如果随便找一本关于FP的书,一定可以看到这两个函数 reduce & map,因为这两种数组操作实在太常见了。

    reduce: 遍历数组,用combine函数把所有元素合并到一个值

    function reduce(combine, base, array) {  
        forEach(array, function(element) {  
            base = combine(base, element);  
        });  
        return base;  
    }
    

    之前的 sum 函数就是一个典型(把元素累加到一个值),所以能用 reduce 来改造 sum:

    function add(a, b) {  
        return a + b;  
    }  
    function sum(numbers) {  
        return reduce(add, 0, numbers);  
    }  
    

    提问:写一个函数countZeroes,输入是个数字数组,返回出现0的次数

    function countZeroes(numbers) {  
        function counter(total, element) {  
            return total + (element === 0 ? 1: 0);  
        }  
        return reduce(counter, 0, numbers);  
    }
    

    写完后有没想到什么?yes,"数数"这个概念可以提取出来,成为高阶函数count:

    function count(test, array) {  
        return reduce(function(total, element) {  
            return total + (test(element) ? 1 : 0);  
        }, 0, array);  
    }  
    function equals(x) {  
        return function(element) {  
            return element === x;
        };  
    }  
    function countZeroes(numbers) {  
        return count(equals(0), numbers);  
    }  
    

      

    map: 遍历数组,把每一项都用函数func处理一遍,返回处理过的新数组

    function map(func, array) {  
        var result = [];  
        forEach(array, function (element) {  
            result.push(func(element));  
        });  
        return result;  
    }  
    print( map(Math.round, [0.01, 2, 9.89, Math.PI]) ); //结果为 [0, 2, 10, 3]

    怎么样?对FP有点不适应?反正我当时是花了点时间来适应这个,适应之后你看到什么都想把它搞成函数,拿加减乘除符号来说,就可以封装成函数-_-:

    var op = {  
        "+": function(a, b){return a + b;},  
        "==": function(a, b){return a == b;},  
        "===": function(a, b){return a === b;},  
        "!": function(a){return !a;},  
        "<": function(a, b) {return a<b;},  
        ">": function(a, b) {return a>b;}  
    };  
    

    这样一来,上面的 sum 函数又可以写成:

    reduce(op["+"], 0, [1, 2, 3, 4, 5]) 
    

    提问: 把数组[0, 2, 4, 6, 8, 10]的每一项都加1,生成新数组

    最先想到的可能是这样:

    arr = [0, 2, 4, 6, 8, 10];  
    function add1(x) {  
        return op['+'](1, x);  
    }  
    map(arr, add1); 
    

    问题是,add1只是每项加1,如果还有加2,加3, 那又要再写很多 add2, add3之类的辅助函数,其实区别就是 op['+'] 的第一个参数不同,而这个参数可以用偏函数来绑定。

    3. 偏函数(Partial Function),用来绑定函数的一个或多个参数,c++里也叫bind

    partial:

    //注:javascript的arguments表示函数调用时的参数,是一个类似数组的东西,只是类似,并不是数组  
    //    所以不具备数组的一些功能,比如concat,slice之类。因此需要用下面的asArray将其转换为数组  
    function asArray(quasiArray, start) {  
        var result = [];  
        for (var i = (start || 0); i < quasiArray.length; i++)  
            result.push(quasiArray[i]);  
        return result;  
    }  
    function partial(func) {  
        var fixedArgs = asArray(arguments, 1);  
        return function() {  
            return func.apply(null, fixedArgs.concat(asArray(arguments)));  
        };  
    }  
    

    之前的 equals 函数如果用偏函数改造:

    equals(10)  <==>  partial(op["=="], 10)
    

    回到刚才的问题

    //把每个元素加上1  
    print(map(partial(op["+"], 1), [0, 2, 4, 6, 8, 10])); //结果为: [1, 3, 5, 7, 9, 11]  
    //把每个元素加上2  
    print(map(partial(op["+"], 2), [0, 2, 4, 6, 8, 10])); //结果为: [2, 4, 6, 8, 10, 12]  
    

    来点复杂的:)

    提问: 一个二维数组,把其中所有值都求平方

    function square(x) {
        return x * x;
    }  
    print( map(partial(map, square), [[10, 100], [12, 16], [0, 1]]) );  
    //结果为: [[100, 10000], [144, 256], [0, 1]]  
     
    

    从上面可以看出,之所以map函数的第一个参数是func而不是数组,是因为可以通过传给partial一个函数来应用map,这种做法把函数从作用于一个值提升到作用于一组值 (表达能力有限,不明白的话请看代码吧-_-)

    4. 函数复合(Function Composition)

    这个比较简单,回一下之前的适配器函数 negate

    function negate(func) {  
        return function() {  
            return ! func.apply(null, arguments);  
        };  
    }  
    

    分析其原理: 调用func,把返回值取反,然后返回。  这种 调用函数A,把结果再经过函数B处理,最后返回B的处理结果 的过程就是函数复合,这种数学概念可以用这个高阶函数表示:

    function compose(func1, func2) {  
        return function() {  
            return func1(func2.apply(null, arguments));  
        };  
    }  
    var isUndefined = partial(op["==="], undefined);  
    var isDefined = compose(op["!"], isUndefined);  
    print(isDefined(Math.PI));  // true  
    print(isDefined(Math.PIE)); // false  
     

    5. 

    现在,我们可以定义很多新的函数,而完全不用到function关键字,实际工作可能远超出这些简单的例子,而通过稍微的学习,相信FP会让你减少一些加班时间。

    推荐几本讲FP和抽象的书

    1. Eloquent Javascript (上面例子大多来自该书第六章)

    2. The Little Schemer (scheme是FP老祖宗,该书以scheme语言讲的,但就算没学过scheme的人也能看懂)

    3. 计算机程序的构造和解释 (满星级推荐)

    下一篇将以一个例子介绍函数式编程

  • 相关阅读:
    递归函数底层原理浅析
    lambda expression & mutable
    命令mv
    printf的参数
    程序结构之静态本地变量
    汇编.align指令
    程序结构之全局变量
    命令touch
    更改gcc默认版本,实现gcc版本升降级
    命令chmod
  • 原文地址:https://www.cnblogs.com/aj3423/p/3143454.html
Copyright © 2011-2022 走看看