编程思想的蜕变?函数式编程

92 阅读3分钟

函数式

一、函数式编程的原理

1. 什么是函数式编程的原理

加法结合律 | 因式分解 | 完全平方公式

2. 理论思想

a. 一等公民 - 函数 => 1. 逻辑功能实现的落脚点 -- 函数 2. (实现 + 拼接) * 函数
b. 声明式编程 => 声明需求 => 语义化
c. 惰性执行 - 无缝连接,性能节约
    // 惰性执行
    let program = name => {
        if(name === 'progressive'){
            return program = () => {
                console.log('progressive')
            }
        } else if(name === 'objective'){
            return program = () => {
                console.log('objective')
            }
        } else {
            return program = () => {
                console.log('functional')
            }
        } 
    }
    
    program('progressive')();
    console.log('lazy');
    program();
    // progressive lazy progressive
    // 提示 函数变量提升 program被覆盖
    // 相当于最外层的program只执行了一次。 一般用于性能优化 如权限判断

3. 无状态与无副作用

无状态 - 幂等; 数据不可变(内部不对外部数据做操作)- 不可操作改变源数据

无副作用 - 函数内部不应该直接对整个系统中的任何参数变量做改动

二、实际开发

1. 纯函数的改造


    // 非纯函数写法
    const _class = {
        name: 'zhangsan'
    }
    // 函数内部引入外部变量后 -- 违反无状态
    const age = str => _class.name + ':' + str;
    
    // 修改了传入参数 -- 违反无副作用
    const changeClass = (obj, name) => obj.name = name;
    
    chhangeClass(_class, 'lisi');
    age('12')
    // ----------------------------
    
    // 纯函数写法
    const _class = {
        name: 'zhangsan'
    }
    const age = (obj, str) => obj.name + ':' + str; // 不依赖外部变量 
    
    const changeClass = (obj, name) =>  = ({...obj, name}); // 不修改外部变量
    
    chhangeClass(_class, 'lisi');
    age(_class, '12') 

2.流水线组装 - 加工和组装

a. 加工 - 科里化

多个参数的函数可以转化为单个参数传入 实现 体系 = 加工 + 组装, 单个加工输入输出应当单值化

    // f(x,y,z)  ->  f(x)(y)(z)
    const sum = (x, y) => {
        return x + y;
    }
    sum(1,2);
    
    const add = x => {
        return y => {
            return x + y;
        }
    }
    add(1)(2)

面试题:手写构造可拆分传参的累加函数 add(1)(2)(3)

    // 1. 构造科里化结构
    // 2. 输入 处理外部arguments => 类数组形态处理
    // 3. 传入参数无限拓展 => 递归 内层逻辑 => 返回函数
    // 4. 主功能实现 => 累加
    // 5. 输出
    
    const add = function () {
    // 输入
    let args = Array.prototype.slice.call(arguments);

    // 内存处理。 外面执行一次函数 就返回一个函数以供下一次的执行调用
    let inner = function () {
        args.push(...arguments);// 内外层参数合并 比如第二层的时候 把第一个参数1 跟第二个参数2合并
        return inner
    }
    // 返回的是一个函数 但是实际上要的是字符串
    inner.toString = function(){
        return args.reduce((prev, cur) => {
            return prev + cur
        })
    }
    return inner;
    }
    console.log('' +add(1)(2)(3)(4)); // 10

递归小技巧。 因为递归的函数返回值是一样的, 如果结果的数据类型跟递归函数返回值不一样 大概率是复写方法。 如这道题中 返回的是要字符串或者是数字, 但是递归函数返回的是函数,所以复写了toString方法

b. 流水线 - 组装函数
const compose = (f, g) => {
    return x => {
        return f(g(x));
    }
}

const sum1 = x => x + 1;
const sum2 = x => x + 2;
const sum12 = compose(sum1, sum2);
sum12(1)

实际表现

    // 命令式
    trim(reverse(toUpperCase(map(arr))));
    
    // 面向对象
    arr.map().toUpperCase().reverse().trim();
    
    // 函数式
    const result = compose(trim, reverse, toUpperCase, map); //执行从右往左
    pipe(map, toUpperCase, reverse, trim);//执行从左往右

三、 BOX 与 函子

    class Mail{
        constructor(content){
            this.content = content;
        }
        map(fn){
            return new Mail(fn(this.content));
        }
    }
    
    //拆开信
    let mail1 = new Mail('love');
    //读了信
    let mail2 = mail1.map(function(mail){
        return read(mail);
    })
    //烧了信
    let mail3 = mail1.map(function(mail){
        return burn(mail);
    })
    //老师查寝时
    mail3.map(function(mail){
        return check(mail);
    })
    // 链式
    new mail('love')
        .map(read)
        .map(burn)
        .map(check)