- 函数式编程思维
- 函数式编程常用核心概念
- 当下函数式编程最热的库
- 函数式编程的实际应用场景
函数式编程思维
范畴
- 彼此之间存在某种关系概念、事务、对象等等,都构成范畴。
- 任何事物,只要找出他们之间的关系,就能定义。
- 箭头表示范畴成员之间的关系,正式名称“态射”。
- 同一个范畴的所有成员,就是不同状态的“变形”。即通过“态射”,一个成员可以变形成另一个成员。
总结
- 所有成员是一个集合
- 变形关系是函数
- 忘却掉if else
- 以数学思维考虑
关键字:态射 变形
函数式编程基础理论
- 给定x只会输出唯一的y。
- 将复杂函数符合成简单的函数。(嵌套的函数调用)。
- function xx(){}这是函数,方法是与指定的对象绑定,函数可直接调用。
- 基础模型来源于λ(lambda x=>x*2)。λ是在20世纪三十年代引入的一套用于研究函数定义、函数定义、函数应用和递归的形式系统。
- Javascript是披着C外衣的lisp(适用于符号处理、自动推理、硬件描述和超大规模集成电路设计等)
- React的高阶函数
总结
- 函数是一等公民。“第一等公民”,函数与其他数据类型一样,处于平等地位,柯赋值给其他变量也可以作为参数,传入另一个函数,或者作为别的函数的返回值。即可传、可执行、可返回。
- 只用表达式,不用语句(if else)。
- 没有副作用,即该函数只简单的返回了一个值,没有进行别的操作(修改变量、抛出异常)。
- 不可改变量,意味着类似于const 定义的变量,只能被赋值一次。
- 函数式编程常用方法:map、reduce。
- 引用透明(参数相同,输入相同,输出相同)。替换模型:identity=(i)=>{return i} identity(1)可替换为1
关键字:一等公民 副作用 引用透明
函数式编程常用核心概念
纯函数
相同输入,固定输出,没有任何可观察的副作用,也不依赖外部环境的状态
例如
优点
降低有效系统的复杂度,还有可缓存性等很棒的特性。(lodash 类库帮助)
缺点
//不纯的
var min = 18;
var checkage = age => age > min; //checkage取决于age和min
//纯的
var checkage = age => age > 18; //checkage取决于age
//关键数字18硬编码在函数内部,扩展性差,柯里化可以优雅的解决(柯里化定义见下方)
var checkage = min => (age=>age>min);
var checkage18 = checkage(18);
checkage18(20);
纯度和幂等性
- 幂等性:执行无数次后还具有相同的效果,同一的参数运行一次函数应该与连续两次结果一致,比如求绝对值。
- 幂等性在函数式编程中与纯度有关,但有不一致。比如求反。
偏应用函数
- 指定部分参数,返回 一个函数去处理剩下的参数。比如Promise .then bind。
- “偏”,只能处理那些能与至少一个case语句匹配的输入,而不能处理所有可能的输入。
示例
const partial = (f,...args)=>(moreArgs)=>f(...args,...moreArgs);
const add=(a,b,c)=>a+b+c;
const fivePlus=partial(add,2,3);
fivePlus(4)
//bind实现
const add1More = add.bind(null,2,3);//(c)=>2,3,c
遗留问题
bind是如何实现的?
函数的柯里化
偏应用函数的细节实现。把一个多参数函数转换为一个嵌套的一元函数的过程。 我的理解是先将参数一步一步的传入,直到所有参数齐全,然后获得最后的值
反柯里化
目的是为了扩大使用范围。
示例
柯里化
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
反柯里化
Function.prototype.uncurring = function() {
var self = this;
return function() {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
};
};
var push = Array.prototype.push.unCurrying(),
obj = {};
push(obj, "first", "two");
console.log(obj);
优点
“预加载”,通过传递较少的的参数,得到一个已经记住了这些参数的新函数。比如fetch
缺点
柯里化与偏应用函数的区别
const compose = (f,g)=>(x=>f(g(x)));
var first = arr => arr[0];
var reverse = arr => arr.reverse();
var last = compose(first,reverse);
last([1,2,3,4,5]);
- compose函数只能组合接受一个参数的函数; filter map接受两个参数 (投影函数:总是在应用转换操作,通过传入高阶函数后返回数组),不能被直接组合可以借助偏函数包裹后继续组合;
- 函数组合的数据流是从右到左,最右边的函数首先执行,如果需要从左执行呢?可以实现pipe(管道或者叫序列)
- 通过组合子交换数据方向
组合子
管理函数程序执行流程,并在链式调用中对中间结果进行操作。 不声明任何变量,不包含任何业务逻辑。
常用组合子
Point Free
把一些对象自带的方法转化为纯函数,不要命名转瞬即逝的中间变量 ,减少不必要的命名
const f = str => str.toUpperCase().split('');
//str作为中间变量除了代码变长没意义
var toUpperCase = word => word.toUpperCase();
var split = x => (str => str.split(x));
var f = compose(split(' '), toUpperCase);
f("abcd efgh");
//减少了不必要的命名
声明式与命令式代码
命令式代码:编写一条又一条指令让计算机执行一些动作; 用写表达式的方式来声明我们想干什么。
优点
声名式的代码,对于无副作用的纯函数,不用考虑其内部是如何实现的,专注于业务代码即可,所以优化代码时,只需要集中在函数内部。
缺点
使用不纯的函数时需要考虑副作用或者外部系统环境。
类SQL数据:函数即数据
惰性链、惰性求值、惰性函数
惰性链:添加一个输入对象的状态,从而能够将这些输入转换为所需的输出操作链接在一起<br /> 用到时再执行,避免创建任何变量,有效消除所有循环<br /> 惰性求值:当输入很大但只有一个小的子集有效时,避免不必要的函数调用 尽可能推迟求值,直到依赖的表达式被调用 || ppt 37 尝试
专业术语
高阶函数 函数当参数,把传入的函数做一个封装,然后返回这个封装函数,达到更高程度的抽象
一等公民、以一个函数作为参数,以一个函数作为返回
尾调用优化PTC 尾调用 递归,就是函数调用自身,尾调用自身,就是尾递归,递归需要大量的调用记录,很容易发生栈溢出错误,如果使用尾递归优化,将递归变为循环,那么就只保存一个调用记录; 谁出来谁又出去
蹦床函数 解决了递归问题
闭包 会对内存产生影响 函数执行完毕以后,栈上的调用帧被释放,堆上的作用域并不被释放,闭包就是在堆上,除非调用GC
容器,有valu就是个容器、容器会升级成Functor函子 vue的reduce 能作用于自己的变形关系就是函子 map
拆箱 装箱
Pointed函子的子集 of 生成新的容器
Maybe函子用来处理错误和异常
Either函子 处理左值 右值
AP函子 value可能是函数 帮你执行错误处理、Either、AP
- IO 把不纯的操作 包裹到一个函数内 网络请求,DOM,从而延迟这个操作的执行
- Monad 是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤 Promise
loadish rx