来源 | http://www.fly63.com/article/detial/2097
高阶函数 Higher-Order Functions
以
函数
为参数的
函数
,返回一个
函数
的
函数
const filter = (predicate, xs) => xs.filter(predicate)
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null])
函数的元 Arity
函数
所需的参数个数。来自于单词 unary, binary, ternary 等等。这个单词是由 -ary 与 -ity 两个后缀组成。
例如,一个带有两个参数的
函数
被称为二元
函数
或者它的 arity 是2。它也被那些更喜欢希腊词根而非拉丁词根的人称为 dyadic。
同样地,带有可变数量参数的
函数
被称为
var
iadic,而二元函数必须给出两个且只有两个参数,见下文,柯里化(Currying) 和 偏应用函数(Partial Application) 。
const sum = (a, b) => a + b
const arity = sum.length
console.log(arity)
惰性求值 Lazy evaluation
是一种按需求值机制,它会延迟对表达式的求值,直到其需要为止
const rand = function*() {
while (1 < 2) {
yield Math.random()
}
}
const randIter = rand()
randIter.next()
偏函数 Partial Application
即【降元】,将一个 n 元函数转换成一个 n - x 元函数
或者这样理解,通过对【复杂的函数】填充一部分
数据
来构成一个【简单的函数】
柯里化就是通过偏应用函数来实现。
function add(a, b,c) {
return a + b+c;
}
var addOne = add.bind(null, 1,2);
console.log(addOne(2));
var addTwo = add.bind(null, 1);
console.log(addTwo(3,4));
柯里化 Currying
将一个多参数函数转换成多个单参数函数,
也就是将一个 n 元函数转换成 n 个一元函数。
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
curriedSum(40)(2)
const add2 = curriedSum(2)
add2(10)
自动柯里化 Auto Currying
将多个参数的函数转换为单参数的函数,
如果,给定的参数数量少于正确的参数,则返回一个函数,该函数将获得其余的参数,
如果,函数得到正确数量的参数时,它就会被求值
示例,lodash 和 Ramda 都有一个 curry 函数,但 underscore 没有。
const add = (x, y) => x + y
const curriedAdd = _.curry(add)
curriedAdd(1, 2)
curriedAdd(1)
curriedAdd(1)(2)
compose 组合函数
概念:它将需要嵌套执行的函数平铺。嵌套执行指的是,一个函数的返回值将作为另一个函数的参数
作用:实现函数式编程中的 pointfree 风格(无参数),使我们专注于【转换】而不是【
数据
】
实现:接收多个函数作为参数,从右到左,一个函数的输入为另一个函数的输出
var compose = function(fun1,fun2){
return function(val){
return fun1(fun2(val));
}
}
var add = function(val){
return val + "111";
}
var upperCase = function(val){
return val.toUpperCase();
}
var double = function(val){
return val += val;
}
var upperCaseThenAdd = compose(add,upperCase);
var doubleThenAdd = compose(double,add);
var addThenAdd = compose(add,add);
var addThenAddThenUpperCase = compose(upperCase,addThenAdd);
console.log(upperCaseThenAdd("china"));
console.log(doubleThenAdd("china"));
console.log(addThenAdd("china"));
console.log(addThenAddThenUpperCase("china"));
var compose1 = function(){
var args = arguments;
return function(initVal){
var val = initVal;
for(key in args){
val = args[key](val);
}
return val;
}
}
var doubleThenUpperCaseThenAddThenAdd = compose1(double,upperCase,add,add);
console.log(doubleThenUpperCaseThenAddThenAdd("china"));
Continuation
概念:在一个程序执行的任意时刻,尚未执行的
代码
称为 Continuation
var addOneAndContinue = function(val,continueFun){
var val = val + 1;
return continueFun(val);
}
var mutiply = function(val){
return val * 5;
}
console.log(addOneAndContinue(100,mutiply));
纯函数 Purity
如果返回值仅由其输入值决定,并且不产生副作用,那么这个函数就是纯函数。
const greet = (name) => `Hi, ${name}`
greet('Brianne')
window.name = 'Brianne'
const greet = () => `Hi, ${window.name}`
greet()
let greeting
const greet = (name) => {
greeting = `Hi, ${name}`
}
greet('Brianne')
greeting
副作用 Side effects
如果函数与外部可变状态进行交互,则它是有副作用的.函数或表达式如果被认为具有副作用,那么除了返回值之外,它可以与外部可变状态(读取或写入)进行交互。
const differentEveryTime = new Date()
console.log('IO is a side effect!')
幂等性 Idempotent
foo(x) 将产生与 foo(foo(x))、foo(foo(foo(x))) 等相同的输出
[二元运算],它需要三个元素:二元运算符以及该运算符作用的两个变量。如四则运算的加、减、乘、除均属于二元运算。乘法下唯一两个幂等实数为0和1
[一元运算],例如 ++ ,正+,负-。比如[高斯符号],它是一个数学符号,形式为方括号[x],表示不大于x的最大整数,高斯符号是幂等的
对接口而言,幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。
比如,在App中下订单的时候,点击确认之后,没反应,就又点击了几次。在这种情况下,如果无法保证该接口的幂等性,那么将会出现重复下单问题
[http方法的幂等],指的是同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的(注意,只是服务器状态,和服务器返回状态无关)
GET /pageX HTTP/1.1是幂等的。连续调用多次,客户端接收到的结果都是一样的:
GET /pageX HTTP/1.1
GET /pageX HTTP/1.1
GET /pageX HTTP/1.1
GET /pageX HTTP/1.1
POST /add_row HTTP/1.1不是幂等的。如果调用多次,就会增加多行记录:
POST /add_row HTTP/1.1
POST /add_row HTTP/1.1 -> Adds a 2nd row
POST /add_row HTTP/1.1 -> Adds a 3rd row
DELETE /idX/delete HTTP/1.1是幂等的,即便是不同请求之间接收到的状态码不一样:
DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists
DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted
DELETE /idX/delete HTTP/1.1 -> Returns 404
对比:和纯函数相比,幂等主要强调多次调用,对内部的状态的影响是一样的(但多次调用返回值可能不同)。而纯函数,主要强调相同的输入,多次调用,输出也相同且无副作用。==纯函数一定是幂等的==
意义:在任何可能的情况下通过幂等的操作限制副作用要比不做限制的更新要好得多。确保操作是幂等的,可避免意外的发生
var Student = function(name,age){
this.name = name;
this.age = age;
};
Student.prototype.delName = function(){
var response = this.name ? this.name + "已被删除":"name不存在";
this.name = null;
return response;
}
var lilei = new Student("lilei",19);
console.log(lilei.delName());
console.log(lilei.delName());
console.log(lilei.delName());
Point-Free 风格
定义函数时,不显式地指出函数所带参数。这种风格通常需要柯里化或者高阶函数。也叫 Tacit programming
const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b
const incrementAll = (numbers) => map(add(1))(numbers)
const incrementAll2 = map(add(1))
incrementAll 明确的使用了参数 numbers,所以它是非 points-free 风格。incrementAll2 连接函数与值,并不提及它的参数。所以 是 points-free 风格. Point-Free 风格的函数就像平常的赋值,不使用 function 或者 =>。
断言函数 Predicate
根据输入返回 true 或 false。通常用在 Array.prototype.filter 的回调函数中。
const morethenTwo = (a) => a > 2;
;[1, 2, 3, 4].filter(morethenTwo);
契约 Contracts
契约保证了函数或者表达式在运行时的行为。当违反契约时,将抛出一个错误
const contract = (input) => {
if (typeof input === 'number') return true
throw new Error('Contract Violated: expected int -> int')
}
const addOne = (num) => contract(num) && num + 1
addOne(2)
addOne('hello')
范畴 Category
范
畴是指,对象(object)及它们之间的态射(箭头,箭头可以组合)
必有一个态射(函数),使得 map 一个对象是它自身
合成满足结合律。f ? (g ? h) 与 (f ? g) ? h 是等价的
态射 morphism
一个变形的函数。 某一范畴中,对象之前的变换关系(一个变形的函数)
函子 functor(范畴学的内容)
在
JavaScript
中一个常见的函子是 Array,因为它遵守因子的两个准则
一致性 Preserves identity,即范畴的第一个原则
object.map(x => x) ? object
var fun1 = function(x){
return x+1;
}
var fun2 = function(x){
return x*x;
}
var res1 = [1,2,3].map(fun1).map(fun2);
var res2 = [1,2,3].map(function(x){
return fun2(fun1(x));
});
console.log(res1,res2);
Pointed Functor
一个具有 of 函数的对象,它将 任何 单独的值放入其中
ES6增加了 Array.of ,使数组成为一个 Pointed Functor
引用透明性 Referential Transparency
定义:一个表达式在程序中可以被它等价的值替换,而不影响结果
对函数而言:如果函数的返回值只依赖于其输入值,这种特性就称为引用透明性
等式推理 Equational Reasoning
指当应用程序由表达式组成,并且没有副作用时,关于系统的真值可以从各个部分推导出来。
纯函数式语言的优点之一是易于进行等式推理,通过引用透明度实现,并且能够在所有上下文中用等号替换equals。
不可变性
Object.freeze({name: 'John', age: 30})
const name="haha"
匿名函数 Lambda
;(function (a) {
return a + 1
})
;(a) => a + 1
;[1, 2].map((a) => a + 1)
const add1 = (a) => a + 1
Monad 对象
拥有 of 和 chain 函数的对象。chain 很像 map, 除了用来铺平嵌套
数据
Array.of(1,2,3);
Array.prototype.chain = function (f) {
return this.reduce((acc, it) => acc.concat(f(it)), [])
};
Array.of('cat,dog', 'fish,bird').chain(s => s.split(','));
Comonad 对象
拥有 extract 与 extend 函数的对象。
const CoIdentity = (v) => ({
val: v,
extract () {
return this.val
},
extend (f) {
return CoIdentity(f(this))
}
})
Extend 在 comonad 上运行一个函数。函数应该返回与 comonad 相同的类型。
CoIdentity(1).extend((co) => co.extract() + 1)
自同态 Endomorphism
const uppercase = (str) => str.toUpperCase()
const decrement = (x) => x - 1
Applicative Functor
应用函子是具有ap函数的对象。ap将对象中的函数应用于同一类型的另一个对象中的值。
Array.prototype.ap = function (xs) {
return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
;[(a) => a + 1].ap([1])
如果你有两个对象,并需要对他们的元素执行一个二元函数
const arg1 = [1, 3]
const arg2 = [4, 5]
const add = (x) => (y) => x + y
const partiallyAppliedAdds = [add].ap(arg1)
由此得到了一个函数数组,并且可以调用 ap 函数得到结果:
partiallyAppliedAdds.ap(arg2)
同构 Isomorphism
例如,一个二维坐标既可以表示为数组 [2, 3],也可以表示为对象 {x: 2, y: 3}
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})
const coordsToPair = (coords) => [coords.x, coords.y]
console.log(pairToCoords([1, 2]));
console.log(coordsToPair({x: 1, y: 2}));
Setoid 对象
定义:拥有 equals 函数的对象。equals 可以用来和其它对象比较。
Array.prototype.equals = function (arr) {
const len = this.length
if (len !== arr.length) {
return false
}
for (let i = 0; i < len; i++) {
if (this[i] !== arr[i]) {
return false
}
}
return true
}
;[1, 2].equals([1, 2])
;[1, 2].equals([3, 4])
半群 Semigroup
定义:一个拥有 concat 函数的对象。concat 可以连接相同类型的两个对象
Foldable 对象
定义:一个拥有 reduce 函数的对象,reduce 可以把一种类型的对象转化为另一种类型
var sum = [1,2,3,4].reduce(function(total,val){
return total += val;
})
console.log(sum);
类型签名 Type Signatures
const add = (x) => (y) => x + y
const increment = (x) => x + 1
const call = (f) => (x) => f(x)
const map = (f) => (list) => list.map(f)
代数数据类型 Algebraic data type
由其他类型组合在一起的复合类型。两种常见的代数类型是 sum 和 product
联合类型(对象) Union Type
示例:add就是一个联合类型对象,因为
js
天然支持number和sting求和时,进行自动
数据
类型转换
const add = (a, b) => a + b
add(1, 2)
add('Foo', 2)
add('Foo', 'Bar')
Product type
const point = (x, y) => ({x: x, y: y})
Sum 类型(有时称为联合类型 )
之所以被称为 sum ,是因为结果类型中可能的值的数量是输入类型的总和
JavaScript
没有这样的类型,但是我们可以使用 Set 来假装
const bools = new Set([true, false])
const halfTrue = new Set(['half-true'])
const weakLogicValues = new Set([...bools, ...halfTrue])
Option | maybe
Option 是一种sum type ,它有两种情况,Some 或者 None。
在其它的一些地方,Option 也称为 Maybe,Some 也称为 Just,None 也称为 Nothing
闭包 Closure
闭包是访问其作用域之外的变量的一种方法。正式一点的解释,闭包是一种用于实现词法作用域的命名绑定的
技术
。它是一种用环境存储函数的方法。
闭包是一个作用域,在这个作用域能够捕获访问函数的局部变量,即使执行已经从定义它的块中移出。即,它们允许在声明变量的块执行完成之后保持对作用域的引用。
const addTo = x => y => x + y
var addToFive = addTo(5)
addToFive(3)
函数 addTo() 返回一个函数(内部调用 add() ),将它存储在一个名为 addToFive 的变量中,并柯里化(Curried)调用,参数为 5 。
通常,当函数 addTo 完成执行时,其作用域与局部变量 add,x,y 不可访问。但是,它在调用 addToFive() 时返回 8。
这意味着即使
代码
块执行完成后,函数 addTo 的状态也被保存,否则无法知道 addTo 被调用为 addTo(5),x 的值设置为 5。
词法作用域是能够找到 x 和 add 的值的原因 – 已完成执行的父级私有变量。这就称为 Closure(闭包) 。
堆栈伴随着函数的词法作用域存储在父对象的引用形式中。这样可以防止闭包和底层变量被当做垃圾回收(因为至少有一个实时引用)。
Lambda Vs Closure(闭包) :lambda 本质上是一个内联定义的函数,而不是声明函数的标准方法。lambda 经常可以作为对象传递。
闭合是一个函数,通过引用其函数体外部的字段来保持对外部变量的引用。