当前位置: 首页 > 后端技术 > Node.js

JS函数式编程-概念

时间:2023-04-04 00:03:12 Node.js

最近在看Typescript,顺便看了一些函数式编程,然后国庆假期就没了一半。做个笔记,然后分段写。刚开始接触函数式编程时,最先接触到的概念就是高阶函数和柯里化。乍一看,这不是用了很久的讲scope的demo吗?我也在日常生活中使用,有什么用吗?事实上,设计模式或编程范式往往不在于技巧,而在于思想。函数式编程是一种编程范式,不是因为技巧,而是因为它的思维。二是源于设计思维的技能。技能往往为思想服务。所以我觉得在学习函数式编程的时候还是先了解一些相关的概念和思想会比较好。如果直接理解成为一等公民的好处,函数就是一等公民(firstclass)。其实,说函数的一等公民,就是函数与其他“公民”具有相同的属性。与任何数据类型一样,它可以存储在数组中,可以用作函数的参数,还可以赋值给变量:consthello=(name)=>(`Hello${name}!`)constsayHello=你好;//AsavariableconsthelloArray=[hello,sayHello]上面的代码没有任何意义,只是表示函数在JavaScript中是一等公民,就像一个值一样。功能是一等公民的福利。以回调为例。比如你使用fetch发送请求时:fetch('getPostLink').then(res=>renderPosts(res)).catch(err=>handleError(error))上面其实可以直接传一个函数作为callback,而且不需要加一层包装:fetch('getPostLink').then(renderPosts).catch(handleError)多一层函数包装没有任何意义,完全是多余的代码。再看一个例子:constpostController={find(postId){returnDb.find(postId)},delete(postId){returnDb.delete(postId)},...}上面的代码实际上是将一些函数聚合为一个对象,但没必要加一层功能。阅读时会增加复杂度。其实postController.find===Db.find,所以根本不需要再包裹一层函数:constpostController={find:Db.find,delete:Db.delete,...}就是上面的代码表现力更强,但是如果js函数不能像值一样传递,上面的简写是不可能的。上面的代码其实还有一个好处,你不用担心两层函数之间的参数如何命名。这种代码风格是符合Pointfree的,后面会介绍。另外,函数式编程就是操作函数,所以函数是一等公民,是函数式编程的基石。基本上如果js不支持这一项,函数式编程根本行不通。纯函数让我举个例子。你在小学的时候学过更多的线性方程:f(x)=ax+b这是一个纯函数,一个输入,一个输出返回。一切都围绕着输入,一个输入可能只返回一个输出,然后对任何其他不在范围内的变量什么都不做。更书面的解释:具有一个输入的纯函数将始终具有相同的输出,并且不会有任何副作用。稍后我们将讨论副作用。非纯函数通常非纯函数分为两类,一类是改变输入:constnumbers=[1,2,3]//纯函数numbers.slice(0,3)//[1,2,3]numbers.slice(1,3)//[2,3]numbers.slice(0,2)//[1,2]//非纯函数numbers.splice(0,3)//[1,2,3]numbers.splice(0,3)//[]上述两个numbers数组的方法中,slice是一个纯函数。而且splice不是纯函数,它会改变输入值。做了一些额外的事情。另一个依赖于函数以外的状态:letendpointForYoung=18//impurefunctionconstcheckYoungPeople=age=>age<=endpointForYoung//purefunctionconstcheckYoungPeople=age=>{constendpointForYoung=18returnage<=endpointForYoung}像上面的函数,第一个是不纯的。它取决于范围外的变量。一旦这个变量发生变化,这个函数返回的值也会随之改变。SideEffects副作用是函数在计算过程中改变或与函数外部的状态交互的行为。首先,副作用会使得功能不纯,程序存在不可控的依赖关系,不易管理。然而,副作用是无法消除的,在正常的编程活动中必然伴随着副作用。所以当面对副作用时,问题不是如何消除它们,而是如何管理它们。当我们解释与类别理论相关的概念时,这将更深入。在正常的编程活动中会引入的副作用如下:文件读写操作增删改查数据库http请求打印log获取用户输入获取dom元素。纯函数的好处是可以缓存。纯函数的每个输入和输出都是无状态的,因此结果是相同的,并且可以在任何地方缓存而不会导致错误。可移植性,来自文档//非纯函数constsignUp=(attrs)=>{constuser=saveUser(attrs);欢迎用户(用户);};//纯函数constsignUp=(Db,Email,attrs)=>()=>{constuser=saveUser(Db,attrs);欢迎用户(电子邮件,用户);};第二个signUp依赖是从上面传过来的,所以可以直观看出saveUser需要Db,welcomeUser也需要Email。在一个不纯的函数中,你很难在调用它时知道它的依赖关系。需要查看代码才明白,“哦,原来Db是用来存储wave数据的”。依赖作为参数传入,非常简单。很容易移植到其他场景。函数毕竟只是函数,针对不同的场景操作不同的数据。易于测试在编写单元测试时,最麻烦的是如何模拟数据。通常,有两种类型的数据最难模拟。第一个是全局变量,比如document,另一个是importdependencies。对于这两种类型,虽然在一些测试套件中有现成的工具库用于mocking。然而,这一切都以一种奇怪的方式出现。而如果是功能性的,你测的是一进一出,没有外界影响,所以很容易测试。并行代码的纯函数是无状态的,所以即使运行在多机多进程上,各单元之间也没有耦合关系。关于Pointfree,可以看阮老师的博客了解一下:http://www.ruanyifeng.com/blo...我就说说Pointfree风格代码的好处吧:上面说了,中间变量是没有意义的,还有无需给变量命名,代码更简洁精炼,不会暴露过多的状态给消费者。当然,也有人认为它隐藏了太多的状态。初读代码很难理解。只有看具体功能实现功能,才能知道真正的用意,这对代码的可读性来说很糟糕。这是一个讨论Point-Free风格:它有什么用?关于黑客新闻。另外还有一篇文章,具体使用场景https://medium.freecodecamp.org/how-point-free-composition-will-make-you-a-better-functional-programmer-33dcb910303a。有兴趣的朋友可以自己看看。添加我杂乱无章的笔记的思维导图。OK,下一篇介绍函数组合和柯里化。