关于范畴论
- 函数式编程是范畴论的数学分支是一门很复杂的数学,认为世界上所有概念体系都可以抽象出一个个范畴
- 彼此之间存在某种关系概念、事物、对象等等,都构成范畴。任何事物只要找出他们之间的关系,就能定义
- 箭头表示范畴成员之间的关系,正式的名称叫做"态射" (morphism)。范畴论认为,同一个范畴的所有成员, 就是不同状态的"变形"(transformation)。通过"态射", 一个成员可以变形成另一个成员。
- 函数式编程与范畴论的关系:本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。 (摘自阮一峰老师的函数式编程入门教程)。
所以,函数式编程必须要使用纯函数进行,因为本质来讲函数式编程就是一种数学运算,例如:1+1永远都等于2,不会因为任何外部因素而改变。
函数式必须了解的概念
1. 纯函数
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。例:
let arr = [1,2,3,4,5];
xs.slice(0,3);
xs.slice(0,3);//slice是纯函数。两次相同的输入,输出同样的结果
xs.splice(0,3);
xs.splice(0,3);//splice非纯,改变原数组导致俩次输出不同的结果
2.幂等性
幂等性是指执行无数次后还具有相同的效果,同一的参 数运行一次函数应该与连续两次结果一致。幂等性在函 数式编程中与纯度相关,可以理解为条件更严格的纯函数。例:
Math.abs(Math.abs(-1))//取绝对值无论套多少层结果永远唯一
3.偏应用函数
传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
const partial = (f, ...args) =>
(...moreArgs) => f(...args, ...moreArgs)
const addThree = (a, b, c) => a + b + c
// 偏应用 2 和 3 到 addThree 给你一个单参数的函数
const fivePlus = partial(add3, 2, 3)
fivePlus(1)//6
柯里化(Curried) 通过偏应用函数实现。
function add(x){
return function(y){
return x+y;
}
}
add(1)(2)
bind就是通过柯里化偏应用函数实现的
4.高阶函数
把函数作为参数,将这个函数进行封装,再返回这个抽象后的函数。
function add(x,y){
return x + y;
}
function Math(func,arr){
return func(arr[0],arr[1]);
}
Math(add,[1,2]);//高阶函数简单的实现
5.尾调用优化
指函数内部的最后一个动作是函数调用。该调用的返回值,直接返回给函 数。函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归需要 保存大量的调用记录,很容易发生栈溢出错误,如果使用尾递归优化,将递归 变为循环,那么只需要保存一个调用记录,这样就不会发生栈溢出错误了。下面是一个斐波那契函数:
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
<!-- 存储了五个执行堆栈,如果传入参数是很大,那么很容易发生爆栈(Stack overflow)
sum(5)
(5 + sum(4))
(5 + (4 + sum(3)))
(5 + (4 + (3 + sum(2))))
(5 + (4 + (3 + (2 + sum(1))))) (5 + (4 + (3 + (2 + 1))))
(5 + (4 + (3 + 3)))
(5 + (4 + 6))
(5 + 10)
15
-->
function factorial(n,sum){
if (n === 1) return sum+n;
return factorial(n-1,sum+n);
}
<!--整个计算过程是线性的,调用一次sum(x,total)后,会进入下一个栈,相关的数
据信息和 跟随进入,不再放在堆栈上保存。当计算完最后的值之后,直接返回到最上层
的 factorial(5,0)。这能有效的防止堆栈溢出。-->
提示:尾调用优化是浏览器自己去执行的,我们只是在代码层面模拟了尾调用优化,如上两个函数其实都存储了5个执行堆栈。
参考