来自 Professor Frisby’s Mostly Adequate Guide to Functional Programming
英文版本
中文版本-版本较老
函数式编程是一种编程范式
, 所谓范式, 就是一种编程规范
面向对象: 将现实世界的事物抽象为类与对象
, 通过封装, 继承, 多态来表示事物之间的联系函数式: 将现实世界事物与事物之间的联系抽象到程序中
这里的函数指的是映射形如 y=x2y = x^2y=x2
代码优化
思考: 一个函数, 参数在另一个函数中调用 是否可以直接将另一个函数拿过来?
形如以下函数
args => actualCallFunction(args)
❗️ 只会徒增代码量 这种形式等价于actualCallFunction
const log = arg => console.log(arg)const a = arg => log(arg)// a 和以下代码等价const b = loga(123)// 123b(456)// 456
再看个 🌰
const actualCallFunction = _ => console.log('qweasdzxc')const foo = aFunction => actualCallFunction(arg => aFunction(arg))// 优化一下const foo2 = aFunction => actualCallFunction(aFunction)// 接着优化const foo3 = actualCallFunctionfoo()// qweasdzxcfoo2()// qweasdzxcfoo3()// qweasdzxc
一些概念
纯函数
: 相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用
副作用
中的“副”是滋生 bug 的温床
副作用可能包含,但不限于:
更改文件系统往数据库插入记录发送一个 http 请求可变数据打印/log获取用户输入DOM 查询访问系统状态
curry
:只需传给函数一些参数,就能得到一个新函数
const add = x => y => x + y;const increment = add(1);const addTen = add(10);console.log(increment(2)) // 3console.log(addTen(2)) // 12
Compose (代码组合)
一个简单的 compose 函数如下
const compose = (f, g) => x => f(g(x));
可以看到, 代码执行顺序是从右向左
并且, 组合函数遵循结合律
// associativitycompose(f, compose(g, h)) === compose(compose(f, g), h);
举个 🌰 咯
const compose = (f, g) => x => f(g(x));const toUpperCase = x => x.toUpperCase()const excalim = x => x + '!'const logMidRes = x => console.log(x) || xconst convertString = compose(excalim, toUpperCase)const convertString2 = compose(compose(logMidRes, excalim), compose(logMidRes, toUpperCase))const a = convertString('welcome')const b = convertString2('welcome')// WELCOME WELCOME!console.log(a) // WELCOME!console.log(b) // WELCOME!
Pointfree style
函数不会提到其操作的数据, 如下
// not pointfree because we mention the data: nameconst initials = name => name.split(' ').map(compose(toUpperCase, head)).join('. ');// pointfreeconst initials2 = compose(intercalate('. '), map(compose(toUpperCase, head)), split(' '));const a = initials('hunter stockton thompson'); // 'H. S. T'const b = initials2('hunter stockton thompson'); // 'H. S. T'console.log(a)console.log(b)
以下为工具代码
// args 为实际使用数据 fn , 此时为数组, 展开(...)之后, 被 fn 调用, 调用后将返回的结果放入一个数组中, 以便下次可展开调用, 最后, 取出数组中唯一的值 const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];function curry(fn) {const arity = fn.length;return function $curry(...args) {if (args.length < arity) {// 参数未达到使用条件, 如 split(' '), 添加操作数并返回return $curry.bind(null, ...args);}// 实际调用, 传入实际使用参数return fn.call(null, ...args);};}// give it a sep and we get a function back=>waiting for its str argument.const split = curry((sep, str) => str.split(sep));const intercalate = curry((str, xs) => xs.join(str));const map = curry((fn, f) => f.map(fn));const toUpperCase = x => x.toUpperCase()const head = x => x[0];