- 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
- 我们可以把一个函数的执行结果交给另一个函数去处理
- 函数是一等公民
- 高阶函数-函数作为参数
- 高阶函数-函数作为返回值
纯函数: 相同的输入永远会得到相同的输出, 而且没有任何可观察的副作用
* 纯函数就类似数学中的函数(用来描述输入和输出之间的关系), y = f(x);
举例:
数组中的 slice 和 splice 分别是纯函数和不纯函数
* slice 返回数组中的指定部分, 不会改变原数组
* splice 对数组进行操作返回该数组,会改变原数组
好处:
可缓存
因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来
可测试
纯函数让测试更方便
并行处理
在多线程环境下并行操作共享的内存数据很可能会意外情况
纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数(Web Worker)
lodash 中有个记忆函数
_.memoize
// 模拟memoize方法的实现
function memoize (f) {
let cache = {};
return function () {
let key = JSON.stringify(arguments);
cache[key] = cache[key] || f.apply(f, arguments);
return cache[key]
}
}
副作用让一个函数变的不纯,纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
副作用来源:
- 配置文件
- 数据库
- 获取用户的输入
- ......
所有的外部交互都有可能代理副作用,副作用也使得方法能用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患 给程序带来不确定性,但是副作用不可能完全禁止,尽可能控制它们在可控范围内发生。
function checkAge (age) {
let min = 18;
return age >= min;
}
// 柯里化
function checkAge (min) {
return function (age) {
return age >= min;
}
}
// es6
let checkAge = min => (age => age >= min);
const _ = require('lodash')
// 要柯里化的函数
function getSum (a, b, c) {
return a + b + c;
}
// 柯里化后的函数
let curried = _.curry()
手写curry
function getSum(a, b, c) {
return a + b + c;
}
const curried = curry(getSum);
console.log(curried(1, 2, 3));
console.log(curried(1)(2, 3));
console.log(curried(1, 2)(3));
function curry (func) {
return function curriedFn (...args) {
// 判断实参和形参的个数
if (args.length < func.length) {
return function() {
// 注意:这里有些博客用的 arguments.callee
// 这个在es5的严格模式下禁止了
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments/callee
return curriedFn(...args.concat(Array.from(arguments)));
}
}
return func(...args);
}
}
a -> fn(也可以是多个管理-多个函数) -> b
fn = compose(f1, f2, f3);
b = fn(a);
// 组合函数
function compose (f, g) {
return function (x) {
return f(g(x))
}
}
// 模拟 lodash 中的 flowRight
const reverse = arr => arr.reverse();
const first = arr => arr[0];
const toUpper = s => s.toUpperCase();
const f = compose(toUpper, first, reverse);
console.log(f(['one', 'two', 'three']))
function compose (...args) {
return function (value) {
return args.reverse().reduce(function (acc, fn){
return fn(acc);
}, value)
}
}
// es6
const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value);
// 结合律 (associativity)
let f = compose(f, g, h);
let associative = compose(compose(f, g), h) == compose((f, compose(g, h));
// 举例
// NEVER SAY DIE --> never-say-die
const _ = require('lodash')
// _.split()
const split = _.curry((sep, str) => _.split(str, sep))
// _.toLower()
const join = _.curry((sep, array) => _.join(array, sep))
const map = _.curry((fn, array) => _.map(array, fn))
const f = _.flowRight(join('-'), map(_.toLower), split(' '))
console.log(f('NEVER SAY DIE'))
// lodash 模块
const _ = require('lodash')
_.map(['a', 'b', 'c'], _.toUpper)
// => ['A', 'B', 'C']
_.map(['a', 'b', 'c'])
// => ['a', 'b', 'c']
_.split('Hello World', ' ')
// lodash/fp 模块
const fp = require('lodash/fp')
fp.map(fp.toUpper, ['a', 'b', 'c'])
fp.map(fp.toUpper)(['a', 'b', 'c'])
fp.split(' ', 'Hello World')
fp.split(' ')('Hello World')
// 举例
/ NEVER SAY DIE --> never-say-die
const fp = require('lodash/fp')
const f = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '))
console.log(f('NEVER SAY DIE'))
const _ = require('lodash')
console.log(_.map(['23', '8', '10', parseInt))
// => [23, NaN, 2]
// 剖析
// 进制 2-36 0就是没传,默认10进制
// 1. parseInt('23', 0, array)
// 2. parseInt('8', 1, array)
// 3. parseInt('10', 2, array)
const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' '))
// 非 Point Free 模式
// Hello World => hello_world
function f (word) {
return word.toLowerCase().replace(/\s+/g, '_')
}
// Point Free
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
console.log(f('Hello World'))
// 把一个字符串中的首字母提取并转换成大写,使用. 作用分隔符
// world wild web ==> W. W. W
const fp = require('lodash/fp')
const firstLetterToUpper = fp.flowRight(fp.join('. ') ,fp.map(fp), fp.split(' '))
// 改进
const firstLetterToUpper = fp.flowRight(fp.join('. ') ,fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(' '))
console.log(firstLetterToUpper('world wild web'))
到目前为止已经学习了函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。
// Functor 函子
class Container {
constructor(value) {
this._value = value;
}
map(fn) {
return new Container(fn(this._value));
}
}
class Maybe {
static of(value) {
return new Maybe(value)
}
constructor(value) {
this._value = value
}
// 如果对宿舍变形的话直接返回 值为 null 的函子
map(fn) {
return this.isNothing() ? Maybe.off(null) : Maybe.of(fn(this._value))
}
isNothing() {
return this._value === null || this._value === undefined
}
}
// 传入具体值
// let r = Maybe.of('Hello World')
// .map(x => x.toUpperCase())
// console.log(r)
let r = Maybe.of(null)
.map(x => x.toUpperCase())
console.log(r)
class Left {
static of(value) {
return new Left(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return this
}
}
class Right {
static of(value) {
return new Right(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Right.of(fn(this._value))
}
}
// let r1 = Right.of(12).map(x => x + 2)
// let r2 = Left.of(12).map(x => x + 2)
// console.log(r1)
// console.log(r2)
function parseJSON(str) {
try {
return Right.of(JSON.parse(str))
} catch(e) {
return Left.of({ error: e.message })
}
}
// let r = parseJSON('{ name: zs }')
let r = parseJSON('{ "name": "zs" }').map(x => x.name.toUpperCase())
console.log(r)
const fp = require('lodash/fp')
class IO {
static of(value) {
return new IO(function() {
return value;
})
}
constructor(fn) {
this._value = fn;
}
map(fn) {
return new IO(fp.flowRight(fn, this._value));
}
}
// 调用
const r = IO.of(process).map(p => p.execPath);
console.log(r._value());
// folktale 中的 compose、curry
const { compose, curry } = require('folktale/core/lamba')
const { toUpper, first } = require('lodash/fp')
// 第一个参数是传入函数的参数个数
let f = curry(2, function(x, y) {
console.log(x + y)
})
f(3, 4)
f(3)(4)
// 函数组合
let f = compose(toUpper, first);
f(['one', 'two'])
const fs = require('fs')
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
function readFile(filename) {
return task(resolver => {
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) resolver.reject(err)
resolver.resolve(data)
})
})
}
readFile('package.json')
.map(split('\n'))
.map(find(x => x.includes('version')))
.run()
.listen({
onRejected: err => {
console.log(err)
},
onResolved: value => {
console.log(value)
}
})
// 就是of函数
class Container {
static of(value) {
return new Container(value)
}
constructor(value) {
this._value = value;
}
map(fn) {
return new Container(fn(this._value));
}
}
Container.of(2)
.map(x => x + 5)
var fs = require('fs');
const fp = require('lodash/fp')
class IO {
static of(value) {
return new IO(function() {
return value;
})
}
constructor(fn) {
this._value = fn;
}
map(fn) {
return new IO(fp.flowRight(fn, this._value));
}
}
let readFile = function(filename) {
return new IO(function() {
return fs.readFileSync(filename, 'utf-8')
})
}
let print = function(x) {
return new IO(function() {
console.log(x)
return x
})
}
let cat = fp.flowRight(print, readFile)
// 嵌套问题
let r = cat('package.json')._value()._value();
console.log(r)
var fs = require('fs');
const fp = require('lodash/fp')
class IO {
static of(value) {
return new IO(function () {
return value;
})
}
constructor(fn) {
this._value = fn;
}
map(fn) {
return new IO(fp.flowRight(fn, this._value));
}
join() {
return this._value()
}
flatMap(fn) {
return this.map(fn).join()
}
}
let readFile = function (filename) {
return new IO(function () {
return fs.readFileSync(filename, 'utf-8')
})
}
let print = function (x) {
return new IO(function () {
console.log(x)
return x
})
}
let r = readFile('package.json')
.flatMap(print)
.join()
console.log(r)