函数式编程

函数式编程

含义

函数式编程属于声明式编程的一部分,我们常常会拿命令式编程与函数式编程作比较

初步定义

命令式编程语言泛指所有把修改变量的值当作最基本计算方式的语言。

函数式编程语言指把一个程序的输出定义为其输入的数学函数的语言,纯函数式编程没有内部状态的概念,也没有副作用。

区别

1.计算模型的区别

命令式模型:图灵-图灵机 通过修改变量的值来影响后续的计算

函数式模型:邱奇-lambda演算 强调变换规则的应用


2.设计的区别

命令式:冯诺依曼体系结构(存储程序原理,把程序本身当作数据来对待,程序和该程序处理的数据用同样的方式储存。)

函数式:数学函数(把程序输出定义为其输入的一个数学函数,无内部状态无副作用)

副作用:在计算机科学中,一个函数或表达式,如果除了返回值之外,还修改了某个状态或者和调用它的函数或外部环境进行了明显的交互,就被称为是有副作用的。


3.风格区别

命令式语言:面向过程(c等) 面向对象(c++、java等,在不断进化,向函数式语言靠拢)

函数式语言:函数被当做第一类对象处理(作为参数传递,从函数中返回等)


js

js没有明确的归类,多范式编程语言。

核心概念

  1. 不可变性
  2. 纯函数
  3. 数据转换
  4. 高阶函数
  5. 递归

1. 不可变性: 数据是不可变的

1
2
3
4
5
6
7
8
9
10
//赋值对象某个字段
const rateColor = (color, rating) => ({...color, rating});
let lawnColor = { rating: 5, title: 'lawn'};

console.log(rateColor(lawnColor, 5));

//数组添加新元素
// const addColor = (color, array) => array.concat({color});
const addColor = (color, array) => [...array,{color}];
console.log(addColor('blue', [{color: 'red'}]));

2. 纯函数:一个返回结果只依赖于输入参数的函数。

纯函数至少需要接受一个参数并且总是返回一个值或者其他函数,不会产生任何副作用,不修改全局变量。 将输入的参数当做不可变的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 let frank = {
name: 'frank',
canRead: false,
canWrite: false,
}

//非纯函数
const selfEducate1 = function(){
frank.canRead = true;
frank.canWrite = true;
return frank;
}

//首先没有接受任何参数,还修改了除其作用域之外的变量

//纯函数
const selfEducate2 = function(person){
return {
...person,
canRead: true,
canWrite: true,
}
}

console.log(selfEducate2(frank));
console.log(frank);

3. 数据转换:使用函数生成转换后的副本

如果数据不可变,那如何进行状态转换, 函数式编程的做法是将一种数据转换成另外一种数据,我们使用函数生成转换后的副本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 当从数组中移除某个元素时 我们倾向于使用 Array.filter 替代 Array.pop 或 Array.splice 因为filter不会改变原数组
// Array.map 与 Array.reduce

const schools = ['Yorktown', 'Washington & Lee', 'Wakefield'];

// remove item
const cutSchool = (cut, list) => list.filter(school => school !== cut);

const highSchools = schools.map(school => {name: school});

// 改变数组中某个值
const editName = (oldName, name, arr) => arr.map(item => (item.name === oldName) ? ({...item, name}) : item);


// 将对象转换成数组
const schoolObj = {
'Yorktown': 10,
'Washington & Lee': 2,
'Wakefield': 5,
}

Object.keys(schoolObj).map(item => ({
name: item,
wins: schoolObj[item],
}))

// 数组去重
const colors = ['red', 'blue', 'red', 'green'];

colors.reduce((distinctColors, color) => (distinctColors.includes(color) ? distinctColors : [...distinctColors, color]), []);

4. 高阶函数 可以操作其他函数的函数

可以将函数作为参数传递,也可以返回一个函数,或者二者兼而有之

1
2
const invokeIf = (conditon, fnTrue, fnFalse) => conditon ? fnTrue() : fnFalse();
console.log(invokeIf(false, () => 'success', () => 'failed'));

柯里化: 一种将某个操作中已经完成的状态保留,直到其余部分完成后可以一并提供的机制

通过在一个函数中返回另外一个函数实现

1
2
3
4
const userLogs = userName => message => console.log(`${userName} => ${message}`);

const log = userLogs('xiao ming');
log('play Basketball');

5. 递归 自己调用自己

是用户创建的函数调用自身的一种技术

1
2
3
4
5
6
7
// 倒计时
const countdown = (value, fn, delay=1000) => {
fn(value);
return value > 0 ? setTimeout(() => countdown(value - 1, fn),delay) : value;
}

countdown(10, value => console.log(value));

总结

  • 保持数据的不可变性
  • 确保尽量使用纯函数,只接受一个参数,返回数据或者其他函数
  • 尽量使用递归处理循环(如果有可能的话)

综合应用

构建一个时钟,显示hh:mm:ss tt(tt 为 am 或 pm)日期 每个字段保证是双位数字(如1 补为 01)显示每秒的时钟变化

命令式编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const oneSecond = 1000;

const prependZero = (value) => {
return value < 10 ? `0${value}` : `${value}`;
}

const getColckTime = () => {
const now = new Date();
const nowTime = {
hours: now.getHours(),
minutes: now.getMinutes(),
seconds: now.getSeconds(),
ampm: 'am',
};

if(nowTime.hours > 12){
nowTime.ampm = 'pm';
nowTime.hours -= 12;
}

return `${prependZero(nowTime.hours)}:${prependZero(nowTime.minutes)}:${prependZero(nowTime.seconds)} ${nowTime.ampm}`;
}

const logColckTime = () => {
console.clear();
console.log(getColckTime());
}

setInterval(logColckTime, oneSecond);

函数式编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 将这个程序分解成小的函数,最后组合成时钟程序
// 函数作为参数 返回一个参数为初始结果 返回值为顺序调用传入函数后的最终结果的函数
const compose = (...fns) => (args) => fns.reduce((result, fn) => fn(result), args);


const oneSecond = () => 1000;
const log = (message) => console.log(message);
const getCurrentTime = () => new Date();
const clear = () => console.clear();

const serializeColckTime = date => ({
hours: now.getHours(),
minutes: now.getMinutes(),
seconds: now.getSeconds(),
});

const civilianHours = clockTime => ({
...clockTime,
hours: (clockTime.hours > 12) ? (clockTime.hours - 12) : clockTime.hours,
})

const appendAMPM = clockTime => ({
...clockTime,
ampm: (clockTime.hours > 12) ? 'pm' : 'am',
})

const display = target => time => target(time);

const formatClock = format => time => format.replace('hh', time.hours).replace('mm', time.minutes).replace('ss', time.seconds).replace('tt', time.ampm);

const prependZero = key => clockTime => ({
...clockTime,
[key]: (clockTime[key] < 10) ? `0${clockTime[key]}` : clockTime[key],
})

const convertToCivilianTime = clockTime => compose(appendAMPM,civilianHours)(clockTime);

const doubleDigits = civilianTime => compose(
prependZero('hours'),
prependZero('minutes'),
prependZero('seconds')
)(civilianTime);

const startTricking = () => setInterval(compose(
clear,
getCurrentTime,
serializeColckTime,
convertToCivilianTime,
doubleDigits,
formatClock('hh:mm:ss tt'),
display(log),
), oneSecond());

startTricking();

落地

优点

1.单元测试
2.调试查错
3.并发执行
4.利于维护(受主观因素影响)
5.复用性好

没有银弹

混合使用(函数,对象)

参考

浅析函数式编程与命令式编程

wiki命令式编程

wiki函数式编程

傻瓜函数式编程

react学习手册

本文标题:函数式编程

文章作者:Coding_youth

发布时间:2019年03月11日 - 11:03

最后更新:2020年05月28日 - 19:05

原始链接:https://yangchendoit.github.io/2019/03/11/函数式编程/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!