第二部分函数基础部分
模块化且可重用的代码
模块化的意义:将程序分成较小独立的程度。这些部分都是可重复使用的组件,并可以合并为一个系统整体或单独在其他系统中使用
1. 方法链与函数管道
在函数式编程中,函数是输入与输出类型之间的数学映射
函数式编程能够消除方法链中存在的限制,使任何函数的组合都更加灵活。管道是松散结合的有向函数序列。一个函数的输出会作为下一个函数的输入。
方法链通过对象的方法紧密连接,而管道以函数作为组件将函数的输入和输出松散的连接在一起。
函数的输入和输出需要满足的条件
- 类型--函数返回类型必须与接收函数的参数类型相匹配
- 元数--接收函数必须声明至少一个参数才能处理上一个函数的返回值
元数:是函数所接收的参数数量,也被称为函数的长度。
- 只具有单一参数的纯函数是最简单的,因为其实现的目的非常单纯,也就意味着职责单一。
- 元组是有限的,有序的元素列表,通常由两个或三个值成组出现
元组是不可变结构,将不同类型的元素打包在一起,以便传递到其他函数中。
元组的优点
a). 不可变
b). 避免创建临时类型
c). 避免创建异构数组
元组是减少函数元数的方式之一。
2. 柯里化的函数求值
柯里化是一种在所有参数被提供之前,挂起或延迟函数执行。将多元函数转换成一元函数。柯里化的可替代方案是部分应用和函数绑定
// lodash 中 _.curry()
var sum = function(a, b, c) {
return a + b + c;
};
var currries = _.curry(sum)
console.log(currries(1)(2)(3)) // 6
2.1. 部分应用和函数绑定
部分应用:通过将函数的不可变参数子集初始化为固定值来创建跟小元数函数的操作
部分应用和柯里化的区别:
主要区别在于参数传递的内部机制和控制
- 柯里化在每次分步调用时都会生成嵌套的一元函数。在底层,函数的最终结果其实是由这些一元函数的逐步组合产生的
- 部分应用将函数的参数与一些预设值绑定(赋值)从而产生一个拥有更少参数的新函数。该函数的闭包中包含了这些已赋值的参数,在之后的调用中被完全求值
// _.partial(func, [partials])
var greet = function(greeting, name) {
return greeting + ' ' + name;
};
// 将 greeting这个的值记住,放在闭包中,不会被销毁
var sayHello = _.partial(greet,'hello')
const res = sayHello('fff')
const res1 = sayHello('mmmmm')
console.log(res,res1) //hello fff hello mmmmm
_.partial创建一个函数来调用func函数并且将局部应用参数添加到新函数接收到是参数列表的最开头。
和_.bind方法类似,但是bind会改变this绑定
const obj = {
'user':'fres'
}
var greet = function(greeting, punctuation) {
return greeting + ' ***' + this.user + punctuation;
};
const ress = _.bind(greet,obj,'hi')
console.log(ress('!')) //hi ***fres!
3. 组合函数管道
一个由纯函数构建的程序本身也是纯的,因此能够进一步组合更为复杂的解决方案,而不会影响系统的其他部分。
本质上看,函数组合是一种将已被分解的简单任务组织成复杂行文的整体过程
3.1. 使用函数组合子来管理程序的控制流
命令式代码中可以使用if-else / for 这样的过程控制机制。但是在函数式中则不能,所以其替代方案--可以使用函数组合子
- 组合子是一些可以组合其他函数,并且作为控制逻辑运行的高阶函数
组合子不需要声明变量,不包含任何业务逻辑
常见的组合子:identify / tap / alt / seq / fork(join)
identify
identify:返回与参数同值的函数
用途:
- 为以函数为参数的更高阶函数提供数据
- 在单元测试的函数组合器控制流中作为简单的函数结果来进行断言
- 函数式的从封装类型中提取数据
tap(K-组合子)
_.tap(value,interceptor)
调用一个interceptor并返回value
iterceptor调用1个参数
该方法的目的是进入方法链序列以便修改中间结果
const a = _([1,2,3]).tap(arr => {
arr.pop();
}).value()
console.log('a',a) // a [ 1, 2 ]
alt(OR-组合子)
alt组合子能够在提供函数响应的默认行为时执行简单的条件逻辑。该组合子以两个函数为参数,如果第一个函数返回值已定义(不是 false / null / undefined)则返回该值,否则返回第二个结果
const alt = function(f1,f2){
return function(val){
return f1(val) || f2(val)
}
}
或者
_.curry( (f1,f2,val)=> f1(val) || f2(val) )
seq(S-组合子)
seq组合子用于遍历函数序列。它以两个或更多个函数作为参数并返回一个新的函数,会用相同的值顺寻调用所有这些函数
const seq = function(){
const funcs = Array.prototype.slice.call(arguments);
return function(val){
funcs.forEach(element => {
element(val)
});
}
}
function f1(val){
return val+3
}
function f2(val){
return val*2
}
const f3 = seq(f1,f2)
f3(4) // 7 8
seq组合子不会返回任何值,只会一个一个的执行一系列操作。
fork(join)组合子
fork组合子用于需要以两种不同的方式处理单个资源的情况,该组合需要以3个函数作为参数,即以一个join函数和两个fork函数来处理提供的输入。两个分叉函数的结果最终传递到的接受两个函数的join函数中
const fork = function(join, func1, func2){
return function(val){
return join(func1(val),func2(val))
}
}
function sum(a, b, c) {
return a + b + c
}
function len(a, b, c) {
return arguments.length
}
function device(a, b) {
return a / b
}
const c = fork(device, sum, len)
console.log(c(1, 2, 3))
基于不可变性和纯性,函数式编程可以有效提程序代码的模块化和可重用性水平
纯函数的组合是函数式编程的支柱
总结
- 用于连接可重用的,模块化的,组件化程序的函数链与管道
- 可以通过部分求值和柯里化来减少函数元数,利于对参数自己的部分求值将函数转化为一元函数
- 可以将任务分解为多个简单的函数,在通过组合来获得整个解决方案
网友评论