首页 > 编程语言 >函数式编程

函数式编程

时间:2023-01-09 15:55:13浏览次数:56  
标签:object return 函数 编程 key const name

函数式编程

先举个栗子

需求:转换数组为JSON数组

// 数据源
['牛','马','牛马','健康马']
//转换
[{name:'小牛'},{name:'小马'},{name:'小牛马'},{name:'小健康马'}]

我们来采用命令式

const arr = ['牛', '马', '牛马', '健康马'];
const result = [];
for (let i = 0; i < arr.length; i++) {
    let name = arr[i];
    let changeName = `小${name}`;
    result.push({ name: changeName });
};

console.log(result);

一般面向过程都是这样,以此执行下面的方法

  1. 第一步遍历数组
  2. 定义存储结果变量result
  3. 抽出数组每一项
  4. 定义改变后结果拼接
  5. push结果进result
  6. 返回结果

需求解决了吗?当然解决了!但是这样做存在什么问题?中间变量很多,变量多了容易搞不清楚,得从头到尾读一遍,才能了解处理的过程,出问题就很难定位。

函数式写法

  1. 把 String 数组转换成 Object 数组
     convertNames :: [String] ——> [Object]
    
  2. String 数组转换成 Object 数组 可以拿出来做 String转换Object
     covertObj :: String ——> Object
    
  3. String 转换 Object 中间过程需要对 String 进行处理
    • changeName():String ——> String
    • genObj():任意类型 ——> Object

它的着眼点是函数,而不是过程,强调通过 函数组合变换 解决问题。

概念

  • 高中数学书里就早就有了函数的概念: 描述集合和集合之间的转换关系,输入通过函数都会返回有且只有一个输入值
    函数x2
    函数实际上算是一种映射关系,而且可以组合,我们可以函数套函数,让一个函数的输出类型编程另一个函数的输入类型:f(g(x)),数学上一般是这样的写法。

  • 编程工作就是找映射关系,函数式编程就相当于是流水线工作。
    函数流水线

特点

  • 函数是一等公民
    这是函数式编程的大前提,函数的地位和其他数据类型一样,处于同等地位,给以赋值给其他变量,也可以作为另一函数的参数,或者别的函数的返回值。

    const funA = funB(gen('aaa'), name);
    
  • 声明式编程
    函数式编程很多时候都在声明我需要做什么,而非怎么做。代码可读性高,我们不需要去考虑具体怎么实现。

  • 惰性执行
    函数只在需要的时候才去执行,不产生中间变量,从头到尾都在写函数,只有最后调用产生结果。

  • 无状态和数据不可变(核心概念)

    • 无状态: 一个函数,无论何时运行,都像第一次运行一样,给相同的输入,产生相同的输出,不依赖外部状态
    • 数据不可变: 所有数据不可变,如果你想修改一个对象,应该创建新对象来修改,而不是修改已有对象

      这种函数一般是纯函数

纯函数

  • 不依赖外部变化(无状态)

  • 没有副作用(数据不变)

    举个栗子:
    非纯函数

    const testObj = {
      name: '测试对象'
    }
    
    //全局引用了testObj,非参数传递
    const saySomething = (str) => {
        return `${testObj.name}:${str}`
    }
    // 通过引用类型修改了入参
    const changeName = (obj, name) => {
        obj.name = name
    }
    
    changeName(testObj,'新名称') //{ name: '新名称' }
    saySomething('厚礼谢!') //新名称:厚礼谢!
    

    纯函数

    const testObj = {
        name: '测试对象'
    }
    
    const saySomething = (obj, str) => {
        return `${obj.name}:${str}`
    }
    
    const changeName = (obj, name) => {
        return { ...obj, name:name }
    }
    
    changeName(testObj, '新名称') //{ name: '新名称' }
    saySomething(testObj, '厚礼谢!') //测试对象:厚礼谢!
    
  • 方便测试优化:一个函数永远返回相同结果,发现问题时,可以很容易判断函数的返回结果,优化函数内部不会影响其他代码执行。

  • 可缓存:可以提前缓存函数的执行结果。

  • 自动文档化:没有副作用,每个函数的功能固定,更容易礼节

  • 减少bug,减少共享状态

函数式编程构建流水线(柯里化和函数组合)

  • 加工站 —— 柯里化
    多元函数转依次调用的单元

    f(a,b,c) ——> f(a)(b)(c)
    

    函数的返回值有且只有一个,如果我们想要顺利组装,那上一个输入结果刚好流向下一个输入。流水线上的加工站必须是单元函数
    柯里化的处理结果就是单输入的

  • 流水线 —— 函数组合
    将多个函数组合成一个函数

    const compose = (f, g) => {
      return (x) => {
          return f(g(x));
      }
    }
    
    const f = (x) => {
        return x + 1;
    }
    const g = (x) => {
        return x * 2;
    }
    
    const fg = compose(f, g)
    fg(1) //3   x * 2 + 1 
    

    形成了一个全新的函数fg,并且这种结合方式可以满足结合律(牛逼啊!)

    compose(f,compose(g,t)) === compose(compose(f,g),t) === f(g(t(x)))
    

    只要保证f,g,t顺序一致,返回的结果都是f(g(t(x)))
    来个复杂的?

    const compose = (...fns) => {
        return (...args) => {
            return fns.reduceRight((val, fn) => fn.apply(null, [].concat(val)), args);
        }
    }
    
    const f = x => x + 1
    const g = x => x * 2
    const t = (x, y) => x + y
    
    let fgt = compose(f, g, t)
    console.log(fgt(1, 2)) //7 3—>6——>7
    

    应用
    假设我们要大写数组最后一项的字符串 反转,取首,大写,打印
    命令式写法

    log(toUpperCase(head(reverse(arr))))
    

    面向对象的写法

    arr.reverse()
       .head()
       .toUpperCase()
       .log()
    

    链式调用从书面上看上去很容易,但是链式调用的函数数量是有限的,需求是无限的
    函数组合的方式

    const upperLastItem = compose(log,toUpperCase,head,reverse)
    

    组合函数会经过reverse —> head ——> toUpperCase ——> log 这些函数都是纯函数,你可以拿去组合使用,不用考虑任何顾虑,这种方式类似于pipe(管道)不过我们是从右往左的方式执行的,Lodash Ramda库里的pipe方法从左往右组合

实际操作

/*
 * 编写一个过滤用户信息的函数
 * 统计18岁以下
 * 统计男性
 * 统计18岁以下男性
 * 且记录他们的name和age
 * 更新一个指定名称用户的年龄
 */
const testData = [
    {
        sex: "M",
        name: "3U5",
        age: 17,
        grade: 82
    },{
        sex: "F",
        name: "Hu8M",
        age: 17,
        grade: 62
    },{
        sex: "M",
        name: "8GPC",
        age: 18,
        grade: 79
    },{
        sex: "F",
        name: "QC3",
        age: 17,
        grade: 59
    }
];

const pipe = function () {
    const args = [].slice.apply(arguments);
    return function (x) {
        return args.reduce((res, cb) => cb(res), x);
    }
}
const curry = (fn) => {
    return function recursive(...args) {
        // 如果args.length >= fn.length则表明传入了足够的参数,此时调用fn并返回
        if (args.length >= fn.length) {
            return fn(...args);
        }

        // 否则表明没有传入足够的参数,此时返回一个函数,用这个函数接受后面传递的新参数
        return (...newArgs) => {
            // 递归调用recursive函数,并返回
            return recursive(...args.concat(newArgs));
        };
    };
};

//判断key值小于某val 返回Boolean
const propGt = (key, val, object) => object[key] < val
//判断key值相等某val 返回Boolean
const propEq = (key, val, object) => object[key] === val
//获取对象key值构建新对象
const pickAll = (keys, object) => {
    const res = {};
    keys.map(key => res[key] = object[key])
    return res
}
//柯里化转换成单元函数
const curryGt = curry(propGt)
// 过滤某一年龄下
const filterAge = curryGt('age', 18) //filterAge(object)
const ageUnder18 = (data) => data.filter(filterAge)

//过滤某一性别
const curryEq = curry(propEq)
const filterSex = curryEq('sex', 'F') // filterM(object)
const sexM = (data) => data.filter(filterSex)

/**既过滤性别也过滤年龄 */
const getAgeUnder18Male = pipe(ageUnder18, sexM)
// console.log(getAgeUnder18Male(testData))

//获取name和age
const cPickAll = curry(pickAll)
const pickKeys = cPickAll(['name', 'age', 'sex'])
const pickData = (data) => data.map(pickKeys)
// console.log(pickData(testData))

//获取18岁以下男性的name和age
const pickMaleUnder18ByNameAndAge = pipe(ageUnder18, sexM, pickData)
console.log(pickMaleUnder18ByNameAndAge(testData))

/**通过指定key对应的对象改变val值 */
const upDatePropBy = curry((key, changeVal, keyVal, newVal, object) => {
    if (object[key] === keyVal) {
        let newObj = { ...object }
        newObj[changeVal] = newVal
        return newObj
    } else {
        return { ...object }
    }
})

const upDateByName = upDatePropBy('name') //upDatePropBy(changeVal, keyVal, newVal, object)
const upDataAgeByName = upDateByName('age') // upDataAgeByName(keyVal,newVal,object)
const updateUsersAgeByName = (name, val, data) => {
    return data.map(upDataAgeByName(name, val))
}

// console.log(updateUsersAgeByName('Uktg', 300, data))

优缺点

1. 优点

  • 开发快速,代码简洁:代码都是函数拼接,可复用率告,减少代码重复
  • 大量声明式,便于理解
  • 没有副作用
  • 更少的出错:每个函数很小,相同的输入永远得到相同的输出

2. 缺点

  • 性能问题:包装过度
  • 资源占用:遵循状态不可变,需要拷贝创建新对象,会给垃圾回收带来比较大的压力
  • 在函数式编程中,为了实现迭代,通常会采用递归操作,为了减少递归的性能开销,我们往往会把递归写成尾递归形式,以便让解析器进行优化。JS 是不支持尾递归优化的

标签:object,return,函数,编程,key,const,name
From: https://www.cnblogs.com/JsonPig/p/17037288.html

相关文章

  • Flink处理函数
    ProcessFlink提供了8个不同的处理函数:(1)ProcessFunction最基本的处理函数,基于DataStream直接调用.process()时作为参数传入。(2)KeyedProcessFunction对流按键分区......
  • EXCEL数据分析,常用的五类函数汇总
    为什么要学习Excel?Excel是一个好用的工具,不因为你会Python而成为数据分析师,而是能用任何工具解决问题,Excel因为其简单易用,而受到人们的青睐。一般学习数据分析,都是以了解Exc......
  • 关于如何学好游戏3D引擎编程的一些经验
    ​ 此篇文章献给那些为了游戏编程不怕困难的热血青年,它的神秘要我永远不间断的去挑战自我,超越自我,这样才能攀登到游戏技术的最高峰        ——阿哲VS自己QQ79......
  • 常用库函数
    1.reverse和unique#include<algorithm>#include<iostream>#include<vector>usingnamespacestd;intmain(){vector<int>v({1,2,3,4,5,5});reverse(v.be......
  • Linux学习记录(四)Shell编程
    0、学习shell的目的:方便运维;编写shell程序管理集群、提高开发效率;1、Shell概述(1)shell是解释器;​ 核心:硬件系统(主机+外设);​外层:操作系统;​......
  • Qt应用程序初始化图片文件对话框静态函数(设置图片文件对话框的打开目录为系统图片标准
    //初始化图片文件对话框staticvoidinitializeImageFileDialog(QFileDialog&dialog,QFileDialog::AcceptModeacceptMode){staticboolfirstDialog=true;/......
  • 读编程与类型系统笔记02_基本类型
    1. 空类型1.1. uninhabitabletype1.1.1. 声明从不返回的函数1.2. 不能有任何值的类型,其可取值的集合是一个空集合1.3. 函数不返回的原因1.3.1. 函数在所有代......
  • 异步编程的历史演进
    或许你也听说了,摩尔定律失效了。技术的发展不会永远是指数上升,当芯片的集成度越来越高,高到1平方毫米能集成几亿个晶体管时,也就是人们常说的几纳米工艺,我们的半导体行业就......
  • Python笔记——列表一:列表简介(Python编程:从入门到实践)
    一、列表是什么列表:由一系列按特定顺序排列的元素组成(列表是有序集合)。表示:用方括号[]来表示,并用逗号来分隔其中的元素。访问:访问列表元素,可指出列表的名称,再指出......
  • 2.4JS中的函数的使用
    ​  什么是JS的函数:类似于java中的方法,JS中也可以定义一些函数,java中的方法签名包含访问修饰符,返回值类型,方法名,参数列表,异常列表,但是JS中定义函数的语法相对......