前言
背景引入
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
|
const _array = ['progressive$%coding', 'objective$%coding', 'functional$%coding']; const _objArr = [];
const nameParser = (array, objArr) => { array.forEach(item => { let names = item.split('$%'); let newName = [];
names.forEach(name => { let nameItem = name[0].toUpperCase() + name.slice(1);
newName.push(nameItem); }) objArr.push({ name: newName.join(' '); }); }) return objArr }
console.log(nameParser(_array, _objArr));
|
解决方案
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
|
const _array = ['progressive$%coding', 'objective$%coding', 'functional$%coding'];
const assembleObj = (key, x) => { let obj = {};
obj[key] = x; return obj; } const capitalize = name => name[0].toUpperCase() + name.slice(1);
const formatName = R.compose(join(' '), map(capitalize), split('$%')); const objHelper = R.compose(assembleObj('name'), formatName); const nameParser = map(objHelper);
nameParser(_array);
let class = 'functional'; let isOvered = false;
classArr.forEach(item => { isOvered = item === class; })
classArr.map(item => item.name === 'functional')
|
函数式编程特点
函数: 一等公民,辑功能实现的落脚点
声明式编程 => 声明需求 => 语义化
惰性执行 - 无缝连接,性能节约
本质
- 加法结合律 | 因式分解 | 完全平方公式 => a + b + c = (a + b) + c 原子组合的变化
- 水源 => 组合(水管 + 走线)=> 浴缸
理论思想
- 函数是一等公民:函数可以作为参数传递给其他函数或作为返回值返回。
- 纯函数:函数的输出仅由输入决定,不会对外部状态产生影响。
- 没有副作用:函数不会修改程序状态或全局变量,只会返回新的值。
- 高阶函数:函数可以接受其他函数作为参数,也可以返回一个函数。
- 数据不可变性:数据一旦创建就不能被修改。因此,函数式编程通常使用不可变数据结构,例如列表、字典、集合等。函数式编程还鼓励使用递归而不是循环,因为递归更符合函数式编程的思想。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let program = (name) => { if (name === "progressive") { return (program = () => { console.log("progressive"); }); } else if (name === "objective") { return (program = () => { console.log("objective"); }); } else { return (program = () => { console.log("functional"); }); } };
program("progressive")(); console.log("lazy"); program();
|
无状态与无副作用 - rxjs
- 无状态 - 幂等;数据不可变 - 不可操作改变源数据
- 无副作用 - 函数内部不应该直接对整个系统中任何参数变量做改动
优缺点
- 优点:变量不可变,引用透明,天生适合并发。表达方式更加符合人类日常生活中的语法,代码可读性更强。实现同样的功能函数式编程所需要的代码比面向对象编程要少很多,代码更加简洁明晰。函数式编程广泛运用于科学研究中,因为在科研中对于代码的工程化要求比较低,写起来更加简单,所以使用函数式编程开发的速度比用面向对象要高很多,如果是对开发速度要求较高但是对运行资源要求较低同时对速度要求较低的场景下使用函数式会更加高效。
- 缺点:由于所有的数据都是不可变的,所以所有的变量在程序运行期间都是一直存在的,非常占用运行资源。同时由于函数式的先天性设计导致性能一直不够。虽然现代的函数式编程语言使用了很多技巧比如惰性计算等来优化运行速度,但是始终无法与面向对象的程序相比,当然面向对象程序的速度也不够快。函数式编程虽然已经诞生了很多年,但是至今为止在工程上想要大规模使用函数式编程仍然有很多待解决的问题,尤其是对于规模比较大的工程而言。如果对函数式编程的理解不够深刻就会导致跟面相对象一样晦涩难懂的局面。
vs POP
- 状态和副作用的处理方式不同:过程式编程中,通常会有多个全局变量,函数会修改这些全局变量的值,从而实现状态的更新和副作用的产生。而函数式编程中,函数通常不会修改传入的参数和外部状态,它们只是根据输入计算输出,避免了副作用。
- 数据结构的处理方式不同:过程式编程中,通常会使用诸如数组和链表等数据结构,这些数据结构可以在程序的执行过程中被修改和操作。而函数式编程中,通常会使用不可变的数据结构,例如列表和元组,这些数据结构在创建之后不可变,任何修改操作都会返回一个新的数据结构。
- 控制流的处理方式不同:过程式编程中,通常会使用循环、条件分支等控制流结构,这些结构会改变程序的执行顺序和流程。而函数式编程中,通常会使用递归和高阶函数等技术来控制程序的执行流程。
- 函数的作用不同:过程式编程中,函数通常被视为一系列指令的集合,用来完成某个具体的任务。而函数式编程中,函数通常被视为一种映射关系,用来将输入映射为输出,函数的作用是描述输入和输出之间的关系。
vs OOP
- 数据和行为的处理方式:在 OOP 中,数据和行为通常是紧密耦合的,一个对象包含一些属性和方法来操作这些属性。在函数式编程中,数据和行为通常是分离的,函数只处理输入数据,而不改变它们的状态。
- 程序的设计和组织方式:在 OOP 中,程序通常是由一些对象组成,每个对象负责处理一些任务。在函数式编程中,程序通常是由一些函数组成,每个函数负责处理一个特定的问题。
- 程序的可读性和可维护性:函数式编程通常使用不可变的数据结构,这可以使程序更加可读和可维护。同时,函数式编程也通常使用纯函数,这可以使程序更加可靠和可测试。
- 各有优缺点:在处理递归问题时,函数式编程可以比面向对象编程更加简洁和自然;而在处理大规模、复杂的系统时,面向对象编程可以提供更好的组织和抽象能力。
实际开发
纯函数改造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const _class = { name: "objective", };
const score = (str) => _class.name + ":" + str;
const changeClass = (obj, name) => (obj.name = name);
changeClass(_class, "functional"); score("good!");
const _class = { name: "objective", };
const score = (obj, str) => obj.name + ":" + str; const changeClass = (obj, name) => ({ ...obj, name });
changeClass(_class, "functional"); score(_class, "good!");
|
流水线组装 - 加工 & 组装
a. 加工 - 柯里化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const sum = (x, y) => { return x + y; }; sum(1, 2);
const add = (x) => { return (y) => { return x + y; }; }; add(1)(2);
const fetch = ajax(method, url, params);
const request = ajax(method); const fetch = request(url); ajax(method)(url)(params);
|
问题: 问题:手写构造可拆分传参的累加函数 add(1)(2)(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
|
const add = function () { let args = Array.prototype.slice.call(arguments);
let inner = function () { args.push(...arguments); return inner; };
inner.toString = function () { return args.reduce((prev, cur) => { return prev + cur; }); };
return inner; };
"" + add(1)(2)(3)(4); parseInt(add(1)(2)(3)(4), 10);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function add() { let args = [...arguments];
let inner = function () { args.push(...arguments);
if (![...arguments].length) { return args.reduce((prev, cur) => { return prev + cur; }); }
return inner; };
return inner; }
console.log(add(1)(10, 100)(1000)());
|
b. 流水线 - 组装函数
1 2 3 4 5 6
| const compose = (f, g) => (x) => f(g(x));
const sum1 = (x) => x + 1; const sum2 = (x) => x + 2; const sum12 = compose(sum1, sum2); sum12(1);
|
实际实现使用
1 2 3 4 5 6 7 8 9
| trim(reverse(toUpperCase(map(arr))));
arr.map().toUpperCase().reverse().trim();
const result = compose(trim, reverse, toUpperCase, map); pipe(map, toUpperCase, reverse, trim);
|
BOX 与 函子
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
| class Mail { constructor(content) { this.content = content; } map(fn) { return new Mail(fn(this.content)); } }
let mail1 = new Mail("love");
let mail2 = mail1.map(function (mail) { return read(mail); });
let mail3 = mail1.map(function (mail) { return burn(mail); });
mail3.map(function (mail) { return check(mail); });
new mail("love").map(read).map(burn).map(check);
|