美文网首页
函数式编程入门

函数式编程入门

作者: 小漠穷秋 | 来源:发表于2018-03-31 09:31 被阅读0次

    编程思路的概念[补充]

    //1.面向过程的 想到哪写到哪
    //2.面向对象的 共有的属性和方法封装到一个类里 封装
    //3.面向切面编程 统计一个函数执行的时间
    //4.函数式编程 提纯无关于业务的纯函数  函数套函数产生神奇的效果
    //5.函数式编程不是用函数来编程 函数套函数让函数更强大 OOP
    
    

    函数式编程思维

    范畴论

    • 范畴论是数学分支的一门学科, 世界上所有的体系都可以抽象出一个个范畴;
    • 彼此之间存在某种关系,概念,事物,对象等等,都构成范畴. 只要找出他们之间的关系就能定义;
    • 箭头函数表示范畴成员之间的关系,正式名称叫做态射,范畴论认为,同一个范畴的所有成员,就是不同状态的”变形”,通过 态射 一个成员可以变形成另一个成员.

    所有成员是一个集合; 变形关系是函数

    基本理论

    • javascript函数称为一等公民,指的是函数与其他数据类型一样.处于平等地位,可以赋值给其他变量,也可以作为参数,转入另一个函数,或者作为别的函数的返回值;
    • 不可改变量.在函数编程中, 我们通常理解的变量在函数式编程中也被函数代替了:在函数式编程中变量仅仅代表某个表达式, 这里所说的变量是不可以被修改的. 所有的变量只能被赋值一次初值;
    • map & reduce 是最常用的函数编程的方法;

    基本概念

    纯函数

    • 对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态;
    var xs=[1,2,3,4,5];
    //Array.slice是纯函数,因为他没有副作用,对于固定的输入和输出总是固定的;
    xs.slice(0,3);
    xs.slice(0,3);
    xs.splice(0,3);
    xs.splice(0,3);
    
    • 优缺点:
    
    //example 1
    import _ from 'lodash';
    var sin=_.mimorize(x=>Math.sin(x));
    //第一次计算的时候,会稍微慢一点,
    var a=sin(7);
    //第二次有了缓存,速度极快;
    var b=sin(7);
    

    优 : 纯函数不仅可以有效的降低系统的复杂度;还有很多很棒的特性,比如可缓存性;

    //example 2
    //不纯的
    var min=18;
    var checeage=age=>age>18;
    //纯的
    var checkage=age=>age>18;
    //在不纯的版本中,checkage不仅取决于age 还有 外部的依赖的变量min;而纯的版本中,checkage 把关键字 18 硬编码在函数内部,扩展性比较差,推荐使用 函数柯里化优雅的解决;
    

    函数的柯里化

    • 传递给函数的一部分参数来调用它, 让他返回一个函数去处理剩下的参数.
    • 我们一起用柯里化来改上面的那个代码
    //example 3 
    var checkage=age=>age>min;
    var checkage18=checkage(18)(20);
    
    //example 4  我们再来看一组函数柯里化的code
    //未柯里化之间
    function add(x,y){return x+y};
    add(1,2)//3
    //柯里化之后
    function add(x){
        return function(y){
            return x+y;
        }
    }
    add(2)(1)
    
    //example 5 优缺点
    import {curry} from 'lodash';
    var match = curry((reg,str)=>str.match(reg));
    var filter=curry((f,arr)=>arr.filter(f));
    var haveSpace = match(/\s+/g);//清楚空格
    filter(haveSpace)(["asdffgh","hello world"])
    

    事实上柯里化 是一种预加载 函数的方法, 通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的”缓存”,是一种非常高效的编写函数的的方法;

    函数的组合

    • 纯函数以及如何把柯里化写出的洋葱代码h(g(f(x))),为了解决函数嵌套问题,我们需要用到”函数”组合”;
    • 我们一起用柯里化开改他,让多个函数像拼积木一样
    //example 6
    const compose=(a,g)=>(x=>a(g(x)));
    var first = arr => console.log(arr[0]);
    var reverse=arr=>arr.sort();
    var last = compose(first, reverse);
    last([6,2,3,4,5,1])
    
    函数组合图解

    Point Free

    • 把一些对象自带的方法转化成纯函数,不要命名转瞬即逝的中间变量.(一般这种思路用于封装之间的API)
    //example 7 
    //一般我们是这样使用api的,
    const f=str=>str.toUpperCase().split(' ');
    //Point Free 改写之后呢.
    var toUpperCase=word=>word.toUpperCase();
    var split=x=>(str=>str.split(x));
    var f=compose(split(' '),toUpperCase);//example 6 的compose
    
    f("abcd efgh");
    

    这种风格能够帮我们减少不必要的命名,让代码保持简洁和通用

    声明式与命令式代码

    //example 8
    //先看段代码
    //命名式:需要我们一条一条的指令去执行代码
    let ceo=[];
    for(var i=0;i>arr.length;i++){
        ceo.push(arr[i])
    }
    //声明式:通过表达式的方式来声明我们想干的事,而不是一步一步指示;
    let CEO=arr.map(c=>c)
    

    优 : 这种声明式的代码,对于无副作用的纯函数,可以不考虑函数内部的实现,专注编写业务代码;相反对于不纯的函数,代码会出现副作用或者依赖外部系统的环境,这对于程序员来说式极大的负担.

    惰性求值,惰性函数

    • 定义 由于每个函数都有可能改动或者依赖于其外部状态,因此必须顺序执行;

    函数式编程常用的核心概念

    高阶函数

    • 函数当参数,把传入的函数做一个封装,然后返回这个封装函数,达到更高程度的抽象
    //example 9
    //命令式
    var add=function(a,b){
        return a+b;
    };
    function math(f,array){
        return f(array[0],array[1]);
    }
    math(add,[1,2]);//3
    

    尾调用优化

    • 函数内部最后的动作是函数调用,该调用的返回值, 直接返回给函数;函数调用自身,称为递归;如果尾调用自身,就称为尾递归。递归需要保存大量的调用记录,很容易发生栈溢出 错误,如果调用了尾递归优化,将递归变成循环,如果尾调用自身,就称为尾递归;
    //example 10
     // 不是尾递归,无法优化
        function factorial(n) {
            if (n === 1) return 1;
            return n * factorial(n - 1);
        }
    
        function factorial(n, total) {
            if (n === 1) return total;
            return factorial(n - 1, n * total);
        } //ES6强制使用尾递归,一定要调用自身
    

    闭包

    //example 11
    function makePowerFn(power) {
        function powerFn(base) {
            return Math.pow(base, power); //Math.pow(x,y)返回 x 的 y 次幂的值。
        }
        return powerFn;
    }
    
    var square = makePowerFn(2);
    square(3)//3
    

    虽然外层的makePowerFn函数执行完毕了,栈上的帧也被释放了,但是堆上的作用域并没有被释放,因此powerFn依旧可以被

    容器 \ Functor

    • 我们可以发”范畴”想象成一个容器,里面包含两样东西,值(value),值的变形关系,也就是函数
    • 范畴论使用函数,表达范畴之间的关系.
    • 伴随着范畴论的发展,就发展出一整套函数的运算方法.这套方法起初只是用于数学元算,后来有人将它在计算机上实现了. 就变成了今天的”函数式编程”.
    • 函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴,这就涉及到了函子(Functor)
    • 函子是函数式编程里面最重要的数据类型,也是基本的运算单位的功能单位.它首先是一种范畴,也就是说,是一种容器,包含了值和变形关系; 比较特殊的是,它的变形关系可以依次作用于每一个值,将当前的容器变形成一个容器.
    //example 12
    //先设定一个容器
    var Container=function (x) {
        this.__value=x;
    }
    //按照约定, 函子会有一个of方法
    Container.of = x => new Container(x);
    
    // 一般约定,函子的标志就是容器具有map方法. 该方法将容器里面的值映射到另一个容器
    Container.prototype.map=function (f) {
        return Container.of(f(this.__value))
    }
    Container.of(3).map(x=>x+1).map(x=>console.log('Result is ' + x));
    

    example 12中,Functor是一个函子,它的map方法接受函数f作为 参数,然后返回一个新的函子,里面包含的值是被f处理过的 (f(this.val))。 一般约定,函子的标志就是容器具有map方法。该方法将容器里 面的每一个值,映射到另一个容器。 上面的例子说明,函数式编程里面的运算,都是通过函子完成, 即运算不直接针对值,而是针对这个值的容器—-函子。函子本 身具有对外接口(map方法),各种函数就是运算符,通过接口 接入容器,引发容器里面的值的变形。 因此,学习函数式编程,实际上就是学习函子的各种运算。由 于可以把运算方法封装在函子里面,所以又衍生出各种不同类 型的函子,有多少种运算,就有多少种函子。函数式编程就变 成了运用不同的函子,解决实际问题

    .

    //example 13
    class Functor{
        constructor(val){
            this.val=val;
        }
        map(f){
            return new Functor(f(this.val))
        }
    }
    
    (new Functor(2)).map(two=>two+2)
    
    

    你可能注意到了,上面生成新的函子的时候,用了 new命令。这实在太不像函数式编程了,因为new命令是 面向对象编程的标志。 函数式编程一般约定,函子有一个of方法,用来生成新 的容器

    .

    //example 14 
    var Maybe = function (x) {
        this.__value = x;
    }
    Maybe.of = function (x) {
        return new Maybe(x);
    }
    Maybe.prototype.map = function (f) {
        return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
    }
    Maybe.prototype.isNothing = function () {
        return (this.__value === null || this.__value === undefined);
    }
    Maybe(null)
    

    函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个 空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错 上面的 example 14 使用三目运算来判断值, 产生新的容器称之为Maybe 函子

    错误处理 \ Either \ AP

    • 我们的容器能做的事太少了,try/catch/throw 并不是”纯”的,因为它从外部接管了我们的函数,并且在这个函数出错时抛弃了它的返回值;
    • Promise 是可以调用catch采集处理错误的
    • 事实上Either [译:两者之一] 并不是用来做错误处理的,它表示了逻辑”或”
    class Functor{
        constructor(val){
            this.val=val;
        }
        map(f){
            return new Functor(f(this.val))
        }
    }
    class Either extends Functor {
        constructor(left, right) {
            super();
            this.left = left;
            this.right = right;
        }
    
        map(f) {
            return this.right ? Either.of(this.left, f(this.right)) : Either.of(f(this.left), this.right);
        }
    }
    
    Either.of = function (left, right) {
        return new Either(left, right);
    };
    var addOne=function (x) {
        return x+1;
    };
    Either.of(5,6).map(addOne);
    // Either.of(1,null).map(addOne);
    // Either.of({address: "xxxx"}, currentUser.address).map(updataField);//真实场景
    
    

    IO

    Monad

        //做一些准备工作
        localStorage.test = ["a", "b"];
        //函数组合代码
        const compose = (f, g) => (x => f(g(x)));
    
        /**
         * 基础函子
         * 1,拥有map对象的容器变成函子
         * 2,map对象的作用是可以通过变形关系 f 函数 作用到每一个函子的值
         */
        class Functor {
            constructor(val) {
                this.val = val;
            }
    
            map(f) {
                return new Functor(f(this.val));
            }
        }
    
        /**
         * Monad 函子
         * 1, 核心作用是总是返回一个单层的函子 '
         * 2, 通过拆解成互相链接的多个步骤,只要提供下一一步运行所需的函数,整个运算就会自动进行下去;
         * 3, 可以让我们避开嵌套地狱,可以轻松的进行深度嵌套的函数, 比如IO和其他异步任务
         */
        class Monad extends Functor {
            join() {
                // 实现返回单层函子
                return this.val();
            }
    
            flatMap(f) {
                return this.map(f).join();
            }
        }
    
        /**
         * IO函子
         * 1,现实开发中不是所有的操作都是非常纯的,所以IO函子主要是封装那些不纯的操作;
         * 2,特别是要记下他的map方法
         */
        class IO extends Monad {
            map(f) {
                return IO.of(compose(f, this.val))
            }
        }
    
        IO.of = x => new IO(x);
    
        //这里有三个不纯的函数;所以要用IO函子包裹,然后就变纯了  一定要包裹到IO函子里面
        const print = function (x) {
            return new IO(function () {
                console.warn(x + "【step 2】");
                return x;
            });
        }
        const tail = function (x) {
            return new IO(function () {
                console.warn(x[x.length - 1] + "【step 1】");
                return x[x.length - 1] + "【step 1】";
            });
        }
        const readFile = function (data) {
            return new IO(function () {
                console.warn('chain start');
                return localStorage[data];
            });
        };
        //关键的核心代码
        //1.readFile('test') 创建了一个IO函子 值是 return localStorage["test"];
        //2.IO继承自Monad 所以拥有了flatMap(把它叫chain也行)
        //3.flatMap 接收了tail函数 tail干了啥呢 接受一个x返回一个新的IO 为啥呢??因为tail里的操作不纯啊
        //4.flatMap内部执行了map 这个map是IO的map哦 因为extend的时候重写了
        //5.IO.of(compose(f, this.val)) => IO函子(value = function(x){return f(g(x) })
        // var g = function () {
        //         console.log('chain start');
        //         return localStorage[data];
        // })
        // var f = function (x) {
        //     return new IO(function () {
        //         console.log(x[x.length - 1] + "【step 1】");
        //         return x[x.length - 1] + "【step 1】";
        //     });
        // }
        // f(g());
        //6.继续执行join函数 如果不执行join 最下面要一层层的执行val(可以去掉join试验一下) 这也是monad精髓所在
        //7.上面实际上返回了 一个新的IO 所以可以链式的继续flatMap 但是万万注意的是这个io的value是组合函数传回来的一个函数 需执行记住啦!!
        //8.所以join return this.val()会继续返回新的IO方便链式 完成了全部的操作
        const result = readFile('test')
            .flatMap(tail)
            .flatMap(print);
        //函数式编程只关心计算 和 数据的映射 并不关注该题的步骤 是旧的范畴到新范畴的映射
        //其余的什么curry 懒加载 递归 等等都是衍生知识 仅此而已 如果你更关注过程的话 最后的一步解答方式该是如下
        //result.val();
    

    当下函数式编程最热的库

    RxJS

    cycleJS

    lodashJS

    underscoreJS

    ramdajs

    函数式编程的实际应用场景

    相关文章

      网友评论

          本文标题:函数式编程入门

          本文链接:https://www.haomeiwen.com/subject/ilfgcftx.html