# 函数式编程思想

为什么学习函数式编程

1、现有的redux react都有用到这些

2、node框架库用到了这些

3、能够很好的对业务进行封装

4、loadsh库的使用理解

# 一、纯函数

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任 何可观察的副作用。

优势:不变性,使得结果可缓存

偏应用函数,与柯里化类似

# 二、柯里化

定义:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

基于偏应用函数产生的, 参数列表是从左向右的,先缓存进的是左边的参数

固定部分参数,返回一个接受剩余参数的函数,目的是为了缩小适用范围

var	add	=	function(x)	{		
	return	function(y)	{				
		return	x	+	y;		
	}; 
};
var	increment	=	add(1); 
var	addTen	=	add(10);
increment(2); //	3
addTen(2); //	12

1
2
3
4
5
6
7
8
9
10

反柯里化:目的是创建适用范围更大的函数

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()

var obj = {}
push(obj,'first','two')
1
2
3
4
5
6
7
8
9
10
11
12

# 三、组合

compose 避免函数嵌套问题,洋葱式变成

var	compose	=	function(f,g)	{		
    return	function(x)	{				
        return	f(g(x));		
    }; 
};
1
2
3
4
5

# 四、高阶函数

传入一个函数,对函数做一个封装,然后返回一个封装后的函数

dubug

var	trace	=	curry(function(tag,	x){
	console.log(tag,	x);		
	return	x; 
});

var	dasherize = compose(join('-'),toLower,trace("after split"),split(' '),replace(/\s{2,}/ig,' ')); 
//	after	split	[	'The',	'world',	'is',	'a',	'vampire'	]
1
2
3
4
5
6
7

# Hindley-Milner 类型签名

代码注释,更好的解释函数的行为

//capitalize::String->String 
var	capitalize=function(s){		
    return	toUpperCase(head(s))+toLowerCase(tail(s)); 
}

//id::a->a 
var	id=function(x){return x;}
//map::(a->b)->[a]->[b]
var	map	=curry(function(f,xs){
    return xs.map(f); 
});


1
2
3
4
5
6
7
8
9
10
11
12
13

惰性链、惰性求值、惰性函数

# 尾递归优化

递归存在了堆栈溢出风险,所以在适当时候需进行优化

尾递归:函数最后一步调用自身

尾调用:函数最后一步调用其他函数

优化:调用下一步函数时,只更新当前调用栈,不创建新的调用栈

浏览器不实现优化的原因:1、这个优化属于隐式的行为,程序员意识不到;2、堆栈信息丢失了,难以调试

Trampoline蹦床函数:对尾递归函数的处理的一种技巧,需要对递归的函数进行改造,示例如下

const sum0 = (n, prevSum = 0) => {
  if (n <= 1) return n + prevSum;
  return () => sum0(n-1, n + prevSum)
}
const trampoline = f => (...args) => {
  let result = f(...args);
  while (typeof result === 'function') {
    result = result();
  }
  return result;
}
const sum = trampoline(sum0);
1
2
3
4
5
6
7
8
9
10
11
12

闭包:栈上的调用帧被释放,但是堆上的作用域并不被释放,仍可被其他函数访问,这样就形成了闭包

# 范畴与容器

**范畴:**彼此存在某种关系(函数变化)的概念、事物、对象(参数)等构成一个范畴;维基百科定义:使用箭头连接的物体

总结成模型就是:简单的理解就是"集合 + 函数"。

所有成员的集合

变形关系

**容器:**把范畴比作一个容器,那么里面包含两个东西:1、值;2、值的变形关系(函数)

代码上简单定义如下

class Category {
 constructor(val) {
 	this.val = val;
 }
 addOne(x) {
 	return x + 1;
 }
}
1
2
3
4
5
6
7
8

范畴论与函数式编程关系:范畴论使⽤函数,表达范畴之间的关系。发展形成一套函数运算方式,起初只应用于数学,后来在计算机上实现了,就形成了函数式编程;

本质上,函数式编程只是范畴论的运算⽅法,跟数理逻辑、微积分、⾏列式是同⼀类东⻄,都是数学⽅ 法,只是碰巧它能⽤来写程序。

函子 Functor: 遵守一些特定规则的容器类型

它的变形关系可以 依次作⽤于每⼀个值,将当前容器变形成另⼀个容器。

class Functor {
 constructor(val) {
 	this.val = val;
 }
 map(f) {
 	return new Functor(f(this.val));
 }
}
1
2
3
4
5
6
7
8

of ⽅法

函数式编程⼀般约定,函⼦有⼀个 of ⽅法,⽤来⽣成新的容器。

Maybe函子:为了解决容器内部值为空的问题

class Maybe extends Functor {
 map(f) {
 	return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
 }
}
1
2
3
4
5

Either 函⼦:函数式编程中的ifelse判断

class Either extends Functor {
 constructor(left, right) {
 	this.left = left;
 	this.right = right;
 }
 map(f) {
 	return this.right ?
 	Either.of(this.left, f(this.right)) :
 	Either.of(f(this.left), this.right);
 }
}
Either.of = function (left, right) {
 	return new Either(left, right);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

AP函子:函子的值为一个函数,同时有ap方法可以将函数应用于其他函子的值

ap 是 applicative(应⽤)的缩写。凡是部署了 ap ⽅法的函⼦,就是 ap 函⼦。

class Ap extends Functor {
 ap(F) {
 	return Ap.of(this.val(F.val));
 }
}
1
2
3
4
5

ap 函⼦的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函⼦的链式操作。

function add(x) {
	return function (y) {
		return x + y;
	};
}
Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));

1
2
3
4
5
6
7

IO函子

  • IO就是输入输出,IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
  • IO 函子可以把不纯的动作存储(IO操作等)到 _value 中,将这个动作保存成一个IO函子,由调用者来决定什么时候来执行这个动作,这样就达到了延迟执行这个不纯的操作(惰性执行)
  • 把不纯的操作交给调用者来处理
//因为IO函数需要用到组合函数,所以需要提前安装Lodash
const fp = require('lodash/fp')

class IO {
  // of方法快速创建IO,要一个值返回一个函数,将来需要值的时候再调用函数
  static of(value) {
    return new IO(() => value)
  }
  // 传入的是一个函数
  constructor (fn) {
    this._value = fn
  }

  map(fn) {
    // 这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
     
    return new IO(fp.flowRight(fn, this._value))
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Monad 函⼦

class Monad extends Functor {
 join() {
 	return this.val;
 }
 
 flatMap(f) {
 	return this.map(f).join();
 }
}
1
2
3
4
5
6
7
8
9

函子的值也可以是函子,这样就存在了多层嵌套的情况,避免多层嵌套而带来的麻烦,就有了Monad,来解除嵌套

# redux中函数式编程思想的应用

store-> 容器

state -> value

action -> f

currentReducer -> map

middleware -> IO函子(异步)

redux应用了高阶函数、curry 、compose

经典函数式编程实现

函数式编程是相对于命令式编程而言的。命令式编程依赖数据的变化来管理状态变化,而函数式编程为克服数据变化带来的状态管理的复杂性,限制数据为不可变的,其选择使用流式操作来进行状态管理。而流式操作以函数为基本的操作单元,通过对函数的抽象和组合来完成整个任务。

更新时间: 5/5/2023, 11:19:52 AM