最近在看Typescript,顺便看了一些函数式编程,而后半个国庆假期就没有了。作个笔记,分几个部分写吧。html
最开始接触函数式编程的时候,第一个接触的概念就是高阶函数,和柯里化。咋一看,这不就是长期用来说做用域的demo吗?我在平常也有用啊,有啥吗?数据库
其实呢,设计模式或则编程范式每每不在于技巧,而在于思想。函数式编程就是一种编程的范式,并不在于技巧多么叼,而在于它的思想。其次才是由设计思想才衍生出来的技巧,技巧每每而言是服务于思想的。因此我以为最开始学习函数式编程最好先了解一些相关概念和思想会比较好。编程
若是理解直接看为一等公民的好处好处。设计模式
其实说函数式一等公民的意思就是说函数和其余“公民”具备相同的属性。就像任何一种数据类型,它可以被存储在数组中,可以做为函数的参数,可以赋值给变量:数组
const hello = (name) => (`Hello ${name}!`) const sayHello = hello; // 做为变量 const helloArray = [hello, sayHello]
上面的代码没有什么意义,只是表达函数在JavaScript中是一等公民,和一个值同样。缓存
拿一个callback的例子来说,好比你用fetch发个请求时:dom
fetch('getPostLink') .then(res => renderPosts(res)) .catch(err => handleError(error))
上面其实能够直接传递一个函数做为回调,加一层包裹其实没有必要:函数式编程
fetch('getPostLink').then(renderPosts).catch(handleError)
多一层函数的包裹并无任何意义,彻底是多余的代码。再看一个例子:函数
const postController = { find(postId) { return Db.find(postId) }, delete(postId) { return Db.delete(postId) }, ... }
上面的代码其实就是聚合一些功能做为一个对象,可是多加了一层的函数,也是没有必要的,在阅读的时候到会增长复杂度,其实postController.find === Db.find
,因此彻底没有再去包裹一层函数:工具
const postController = { find: Db.find, delete: Db.delete, ... }
上面的代码是否是更表意,然而若是js的函数不能像值同样传递,上面的简写都是不可能的。上面的代码其实还有一个好处,你不用去纠结如何命名在两层函数之间的参数了。这种风格代码是符合Pointfree的,咱们后面要介绍。另外,函数式编程是操做函数的,因此函数是一等公民也是函数式的基石,基本上若是js不支持这一项,函数式根本玩不转。
让我举一个例子,你们在小学多学过一元一次方程吧:
f(x) = ax+b
这就是一个纯函数,一个输入而后返回一个输出。全部的东西都是围绕输入的,一个输入只可能返回一个输出,而后对任何其余没在做用域中的变量没有任何操做。
更书面的解释:一个纯函数一个输入永远都只有一个一样的输出,而后不会产生任何反作用。反作用是啥咱们一下子再说。
一般不纯的函数分为两类,一种是会改变输入的:
const numbers = [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则不是纯函数,它会改变输入的数值。作了额外的事。
另一种是对函数之外的状态有依赖的:
let endpointForYoung = 18 // 不纯的函数 const checkYoungPeople = age => age <= endpointForYoung // 纯函数 const checkYoungPeople = age => { const endpointForYoung = 18 return age <= endpointForYoung }
像上面的函数,第一个就是不纯的,他依赖的做用域以外的一个变量,一旦这个变量改变,这个函数返回的值就会跟着改变。
反作用就是在函数计算过程当中,对函数外的状态进行更改或则与函数外状态进行交互的行为。首先反作用会致使函数不纯,是程序有不可控的依赖,不便于管理。可是,反作用是不可消除的,在正常的编程活动中是必然伴随着反作用的。因此在面对反作用时,问题不是如何消除反作用,而是如何管理反作用。这个,会在咱们讲解范畴论相关概念的时候再深刻。
正常编程活动中会引入的反作用有这些:
固然不限于上面这几种,还有不少行为都带有反作用。
纯函数的每次输入和输出都是没有状态的,因此结果都同样,可以被缓存在任何地方而不会形成错误。
// 不纯函数 const signUp = (attrs) => { const user = saveUser(attrs); welcomeUser(user); }; // 纯函数 const signUp = (Db, Email, attrs) => () => { const user = saveUser(Db, attrs); welcomeUser(Email, user); };
第二个signUp
依赖是从上传递的,因此能直观的看出saveUser
须要Db
,welcomeUser
还须要Email
。在不纯的函数中你很难在调用的时候知道他的依赖,你须要查看代码,才能搞清楚,“哦,原来还用了Db存了波数据啊。”
依赖做为参数传入,也很容易的在移植到其余场景使用,毕竟函数只是功能,针对不一样场景操做不一样的数据。
写单测的时候,最麻烦的就是如何mock数据。一般有两类数据最难mock,第一个是全局变量,好比document
,另一类是import
进来的依赖,对于这两种,虽然在一些测试套件中有现成的工具库去mock。可是,都是很诡异的方式。
而若是是函数式的话,你测的就是一个输入一个输出,没有外部的影响,是很是容易测试的。
纯函数都是没有状态的,那即便跑在多台机器多个进程,每一个单元相互之间是没有耦合关系的。
你们能够看阮老师的这篇博客了解一下:http://www.ruanyifeng.com/blo...
我只扯一下Pointfree风格代码的好处:
固然也有人认为其将太多的状态隐藏了,初读代码很难理解,只有看了具体函数实现功能才能知道真正的意图,对于代码的可读性而言,很糟糕。
这里有一处在hacker news上的讨论Point-Free style: What is it good for?。另外这还有一篇具体使用场景的文章https://medium.freecodecamp.org/how-point-free-composition-will-make-you-a-better-functional-programmer-33dcb910303a。有兴趣的小伙伴能够本身看看。
添加一张我笔记未整理的脑图。
OK,下一篇介绍一下函数组合和柯里化。