函数式编程相关概念介绍

news2024/9/28 15:31:11

什么是函数式编程

函数式编程(Functional Programming)也称函数程序设计是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及可变物件。
在js中,函数是一等公民,函数本身既可以作为其他函数的入参,也可以作为一个函数的返回值。而函数式编程强调函数执行的结果而非过程。倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算结果,而不是设计一个复杂的执行过程

函数式编程中的概念

  1. 纯函数
  2. 高阶函数(Higher-Order Function)
  3. 柯里化(Currying)
  4. 组合(composition)和管道(pipe)
  5. 无值编程风格(Pointfree)

纯函数

概念

相同的输入,总是会的到相同的输出,并且在执行过程中没有任何副作用。
比如一个函数,不管什么时候在哪里调用这个函数,只要输入一致,那么得到的输出就一致

const double = (val) => 2*val

作用

  1. 使用纯函数可以编写可测试的代码
  2. 不依赖外部环境计算,不会产生副作用,提高函数的复用性,也更加容易维护和重构,代码质量更高
  3. 更加容易被调用,可以组装成复杂任务的模块或函数,符合模块化概念及单一职责原则
  4. 结果可以缓存,因为只要输入相同,其输出就一定相同

应用

  1. 数组对象中的很多基本方法都是纯函数,比如map, forEach, reduce…
  2. Redux中的reducer,Redux中的基本原则之一就是使用纯函数来修改,reducer中接受的state和action描述如何改变state
  3. lodash中的工具库也都是纯函数来实现的

其他补充

什么是副作用:指的是函数在执行过程中产生了外部可观察变化,比如:

  1. 发起HTTP请求
  2. 操作DOM
  3. 修改外部数据(不在函数体内的数据,见下反例)
  4. console.log()打印数据
  5. 调用Date.now()或者Math.random()
let count = 0;
const addCount = () => count += 1;

// 每次调用addCount ,都会修改外部数据 count的值。就会有副作用
addCount()

高阶函数

概念

高阶函数:以函数作为输入或者输出的函数被称为高阶函数(Higher-Order Function)

作用

抽象通用逻辑

比如使用”命令式“编程的话,注重如何做的”过程“,比如下面这个for循环,通过循环拿到每一个值,然后再循环体中写出”如何做“的具体逻辑

let arr = [1,2,3];
for(let i=0;i<arr.length;i++){
  console.log(arr[i]);
}

这种场景就可以通过高阶函数抽象过程,声明式编程(注重做“什么”,注重结果。比如下面的这种写法,通过高阶函数 “forEach”来抽象循环"如何"做的逻辑,直接关注 做"什么"(忽略怎么获取每一项的过程,关注回调函数做什么的逻辑)

const forEach = function(arr,fn){
  for(let i=0;i<arr.length;i++){
      fn(arr[i]);
  }
}
let arr = [1,2,3];
forEach(arr,(item)=>{
    console.log(item);
})

缓存特性

比如典型的应用场景就是防抖和节流

应用

防抖

n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

const debounce = (fn, wait = 1000) => {
    let time = null;
    return (...args) => {
    // 使用箭头函数,避免this指向问题 
        if (!time) {
        clearTimeout(timer)
    }
    // 第一/n次传递
    time = setTimeout(() => fn(...args), timer)
    }
}
节流

n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

const throttle = (fn, timer) => {
    const preTime = + new Date()
    return (...args) => {
    // 使用箭头函数,避免this指向问题 
        const now = + new Date()
        if (now - preTime >= timer) {
            fn(...args)
            preTime = + new Date()
        }
    }
}

柯里化

概念

柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程;
比如一个三元函数

const sum = (num1, num2, num3) => num1 + num2 + num3
// 正常多参数调用函数
const val = sum(2,3,5)

// 多参数的柯里化
const curry = (fn) => {
    // 使用箭头函数,避免this指向问题
    return curried = (...args) => {
        // fn.length 为fn函数的入参个数
        if (args.length >= fn.length) {
            // 接受到的参数达到fn的入参个数即运行fn
            return fn(...args)
        } else {
            // 接受到的参数未达到fn的入参个数,接着返回一个函数接受后面的参数
            return (...args2) => curried(...[...args, ...args2])
        }
    }
}

// 柯里化之后
const mySum = curry(sum)
const val2 = muSum(2)(3)(5)

作用

  • 让纯函数更”纯“,每次接受一个参数,松散解耦,便于我们将复杂的问题简单化
  • 某些语言及特定环境下只能接受一个参数
  • 函数的惰性执行,返回的是一个函数,而并不是立即运行函数
  • 参数的复用

应用

比如需要校验一个正则,我们将验证的逻辑封装为一个函数,然后在需要验证的地方调用这个函数,如果需要验证多次的话,验证规则就需要重复传递了

function checkByRegExp(regExp,string) {
    return regExp.test(string);  
}
// 校验电话号码
checkByRegExp(/^1\d{10}$/, '186xxx'); 
// 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); 


// 需要多次验证的时候
checkByRegExp(/^1\d{10}$/, '186xxx'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '131xxx'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '132xxx'); // 校验电话号码

checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test2@qq.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test3@gmail.com'); // 校验邮箱

可以看到如果需要多次验证的时候,就需要反复传入正则规则了,此时我们可以先将原来的函数柯里化之后再验证就只需要传如需要验证的电话号码和邮箱即可

// 原函数
function checkByRegExp(regExp,string) {
    return regExp.test(string);  
}

//生成工具函数,验证电话号码(curry 就是上面的柯里化函数)
let checkPhone = curry(checkByRegExp)(/^1\d{10}$/);
//生成工具函数,验证邮箱
let checkEmail = curry(checkByRegExp)(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

// 验证电话
console.log(checkPhone('186xxx'))
console.log(checkPhone('182xxx'));
console.log(checkPhone('181xxx'));

// 验证邮箱
console.log(checkEmail('test@163.com'));
console.log(checkEmail('test2@163.com'));
console.log(checkEmail('test3@163.com'));

在这里插入图片描述

组合(composition)和管道(pipe)

概念

在Unix中有这么一套思想:
1、每个程序只做好一件事情。为了完成一项新的任务,重新构建要好于在复杂的旧程序中添加新”属性“。在函数式编程中,”接受一个参数并返回数据“正是遵循了该条思路。
2、每个程序的输出应该是另一个尚未可知的程序的输入。

在这里插入图片描述

就像上面的图一样

  1. a作为最原始f1的输入,经过f1函数处理之后得到结果m
  2. m又作为f2的输入,经过f2的处理之后得到记过n
  3. n又作为f3的输入,经过f3的处理得到最终结果b

基于上面的思想,我们可以将函数的颗粒度拆分的足够小,每个函数都只解决一个问题,非常的轻量,可维护性高。更加容易测试。让后通过组合/管道的方式将简单的函数组合起来解决大而复杂的问题。
组合和管道基本一致,只是顺序不一样。compose 执行是从右到左,pipe是从左至右的执行。

作用

如上所说,我们可以基于此思想把很多小函数组合起来完成更复杂的逻辑。显然维护轻量级的函数要更加的容易。

应用

比如我们希望计算一句话中包含字符a的长度并判断是奇数还是偶数
那可以分三步。得到该字符串包含a的数组,第二步得到个数,第三步得到奇数还是偶数。第一步的结果是第二步的输入,第二步的结果是第三步的输入。三个组合之后就可以解决这个问题

// 接受若干个小函数组合
    const pipe = (...fns) => {
        // 返回一个高阶函数用于接受值
        return wrapFn = (val) => {
            // 将接受到的值使用reduce来依次计算并传递给所有函数
            return fns.reduce((acc, fn) => fn(acc), val)
        }
    }
    // 接受若干个小函数放入管道
    const compose = (...fns) => {
        // 返回一个高阶函数用于接受值
        return wrapFn = (val) => {
            return fns.reverse().reduce((acc, fn) => fn(acc), val)
        }
    }

    const str = "It's a fine day to go to work by bike"
    // 计算这一句话的字符a的长度并判断是奇数还是偶数

    const getCharNum = (str) => str.match(/a/g)
    const getLength = (list) => list.length
    const getOddOrEven = (num) => (num%2 === 0) ? 'Even' : 'Odd'

    // 使用管道
    const myPipeFn = pipe(getCharNum, getLength, getOddOrEven);
    const myComposeFn = compose(getOddOrEven, getLength, getCharNum);
    console.log(myPipeFn(str));
    console.log(myComposeFn(str));

无值编程风格(Pointfree)

其实在上面管道和组合中的应用demo就是pointfree(无值编程)风格,即不使用所要处理的值,只合成运算过程。
在上述demo中,使用管道/组合把需要操作的函数合在一起,定义成了一种和参数无关的合成运算,不需要用到代表数据的那个参数,只需要把一些简单的运算步骤合成在一起即可。
Pointfree 的本质就是使用一些通用的函数,组合出各种复杂运算。上层运算不要直接操作数据,而是通过底层函数去处理。这就要求,将一些常用的操作封装成函数。
其中使用无值编程风格的一个典型库就是Ramda函数库

参考文献

函数式编程-维基百科
Pointfree 编程风格指南
Ramda

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/688172.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spark12-13-14

12. Task线程安全问题 12.1 现象和原理 在一个Executor可以同时运行多个Task&#xff0c;如果多个Task使用同一个共享的单例对象&#xff0c;如果对共享的数据同时进行读写操作&#xff0c;会导致线程不安全的问题&#xff0c;为了避免这个问题&#xff0c;可以加锁&#xff…

操作系统—中断和异常、磁盘调度算法、操作系统其他内容

异常 时常由CPU*执行指令的内部事件引起&#xff0c;比如非法操作码、地址越界、算术溢出等&#xff0c;还有缺页异常、除0异常。同时&#xff0c;他会发送给内核&#xff0c;要求内核处理这些异常。 外中断 狭义上的中断指的就是外中断。由CPU执行指令以外的事件引起&#…

linux高并发网络编程开发(广播-组播-本地套接字)14_tcp udp使用场景,广播通信流程,组播通信流程,本地套接字通信流程,epoll反应堆模型

01 tcp udp使用场景 1.tcp使用场景 对数据安全性要求高的时候  登录数据的传输  文件传输http协议  传输层协议-tcp 2.udp使用场景 效率高-实时性要求比较高  视频聊天  通话有实力的大公司  使用upd  在应用层自定义协议,做数据校验 02 广播通信流程 广播…

LLM 开发实战系列 | 01:API进行在线访问和部署

在本文中&#xff0c;我们将使用Python编程语言来展示如何调用OpenAI的GPT-3.5模型。在开始之前&#xff0c;请确保您已经注册了OpenAI API并获得了访问凭证。 环境准备 下载python 方法1&#xff1a;官网 www.python.org 从最开始的开始&#xff0c;先到Python官网下载一个…

零基础自学:2023年的今天,请谨慎进入网络安全行业

前言 2023年的今天&#xff0c;慎重进入网安行业吧&#xff0c;目前来说信息安全方向的就业对于学历的容忍度比软件开发要大得多&#xff0c;还有很多高中被挖过来的大佬。 理由很简单&#xff0c;目前来说&#xff0c;信息安全的圈子人少&#xff0c;985、211院校很多都才建…

Linux中安装部署docker

目录 什么是docker系统环境要求安装和使用docker 什么是docker Docker是一个开源的容器化平台&#xff0c;用于帮助开发者更轻松地构建、打包、分发和运行应用程序。它基于容器化技术&#xff0c;利用操作系统层级的虚拟化来隔离应用程序和其依赖的环境。通过使用Docker&#…

javaEE进阶 -初识框架

目录 1.为什么要学框架&#xff1f; 框架的优点展示 2、项目的开发 2.1 Servlet 项目的开发 2.2 Spring Boot 项目开发 3 、Spring Boot编写代码 4、 Spring Boot 运行项目 5、验证程序 6、发布项目 主要讲解 四个框架。 1、Spring 2、Spring Boot 3、Spring NVC 4、…

别只盯着Docker了,这十大容器运行时错过后悔

文章目录 一、Docker二、Containerd三、CRI-O四、Firecracker五、gVisor六、Kata七、Lima八、Lxd九、rkt十、runC如何选择适合自己的容器运行时&#xff1f; MCNU云原生&#xff0c;文章首发地&#xff0c;欢迎微信搜索关注&#xff0c;更多干货&#xff0c;第一时间掌握&#…

Apikit 自学日记:数据结构

您可以将API文档中的重复部分提取出来成为数据结构&#xff0c;方便其他文档中复用。当数据结构发生改变时&#xff0c;所有引用了该数据结构的API文档会同步发生改变。 创建数据结构 进入数据结构管理页面&#xff0c;点击 添加数据结构 按钮&#xff0c;输入相关内容并保存…

XXL-JOB任务调度

简介&#xff1a; XXL-JOB 是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 官网&#xff1a;https://www.xuxueli.com/xxl-job/ 以下业务场景可用任务解决 某电商平台需要每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发…

2023 高质量 Java 面试题集锦:高级 Java 工程师面试八股汇总

人人都想进大厂&#xff0c;当然我也不例外。早在春招的时候我就有向某某某大厂投岗了不少简历&#xff0c;可惜了&#xff0c;疫情期间都是远程面试&#xff0c;加上那时自身也有问题&#xff0c;导致屡投屡败。突然也意识到自己肚子里没啥货&#xff0c;问个啥都是卡卡卡卡&a…

炫技亮点 Websocket集群解决方案汇总

文章目录 问题方案方案一&#xff1a;~~Session共享~~&#xff08;不可行&#xff09;方案二&#xff1a;负载均衡器&#xff08;状态路由&#xff09;方案三&#xff1a;广播机制&#xff08;异步方式 - 建议&#xff09;方案四&#xff1a;路由转发&#xff08;同步方式&…

【JS经验分享】你真的会写JS吗?满满干货,建议读三遍(2)

大家好&#xff0c;最近准备总结一下JS的经验&#xff0c;分享分享&#xff0c;有不对的欢迎讨论哈~ JS作为前端的基础技能&#xff0c;每一位前端开发都要运用熟练&#xff0c;但你真的会写JS吗&#xff1f;js全称JavaScript&#xff0c;是运行在浏览器上的脚本语言&#xff0…

【高危】Nuxt.js <3.4.3 远程代码执行漏洞(POC公开)

漏洞描述 Nuxt.js(简称 Nuxt)是一个基于 Vue.js 的通用应用框架&#xff0c;用于构建服务端渲染的应用程序&#xff08;SSR&#xff09;和静态生成的网站。 Nuxt.js 3.4.3之前版本中的 test-component-wrapper 组件的动态导入函数存在代码注入漏洞&#xff0c;当服务器在开发…

Java集合流式编程

一、简介 1、什么是集合流式编程 集合流式编程&#xff08;Stream API&#xff09;是Java 8引入的一个功能强大的特性&#xff0c;它提供了一种更简洁、更高效的方式来操作集合数据。它的设计目标是让开发者能够以一种更声明式的风格来处理集合数据&#xff0c;减少了显式的迭…

Ubuntu部署jmeter与ant

为了整合接口自动化的持续集成工具&#xff0c;我将jmeter与ant都部署在了Jenkins容器中&#xff0c;并配置了build.xml 一、ubuntu部署jdk 1&#xff1a;先下载jdk-8u74-linux-x64.tar.gz&#xff0c;上传到服务器&#xff0c;这里上传文件用到了ubuntu 下的 lrzsz。 ubunt…

WordPress 备份插件 BackUpWordPress

WordPress备份是一件必不可少的事情&#xff0c;毕竟自己辛辛苦苦花了很多时间精力写得博客&#xff0c;经验总结&#xff0c;必须保留传承。WordPress备份可以在发生灾难性情况&#xff08;比如劫持或意外锁定&#xff09;下迅速恢复&#xff0c;确保了网站安全。 BackUpWord…

揭示不断增长的预切蔬菜市场:深入研究行业驱动因素和挑战

随着现代社会的快节奏和人们生活压力的增加&#xff0c;越来越多的人选择预制菜作为饮食解决方案&#xff0c;预制菜已经成为餐饮行业的新兴赛道。预制菜的优点包括方便快捷、卫生安全、节省时间、质量可靠&#xff0c;以及丰富的菜品选择和灵活的烹饪和食用方式&#xff0c;满…

基于SpringCloud微服务流动资金贷款业务系统设计与实现

一、引言 由于传统的贷款业务系统并不能够顺应时代的变化,同时在一定程度上对业务发展进行了限制,所以为了适应时代的发展,信息贷款业务应该能够被产品化、丰富化,同时还需要制定一套特定的流程来满足新时代用户的需求。流程化的规范管理是当今银行业务发展的必然趋势,研究并开…

基于Stable Diffusion的2D游戏关卡生成【实战】

接下来的几篇文章将与常规主题有所不同&#xff08;这是在从事通用机器人技术的职业中吸取的教训&#xff09;。 相反&#xff0c;我决定利用我的一些新空闲时间 1 边做边学&#xff0c;并使用所有酷孩子都在谈论的一些很酷的新 ML。 推荐&#xff1a;用 NSDT设计器 快速搭建可…