原文地址
译者的Github 系列文章地址
本文原做者还没有所有完成,有兴趣的能够到原文或者译文地址关注更新javascript
本文的主要目的便是但愿可以有一种通俗易懂的方式来阐述函数式编程中常见的理论术语概念java
Arity代指一个函数的参数数量,该关键字来源于相似于unary、binary、ternary等等,由两个后缀
-ary
、-ity
组成。譬如,若是一个函数容许输入两个参数,那就称为所谓的binary function(二元函数),或者一个有两个参数的函数。有时候这种函数也会被喜欢拉丁语法的人称为"dyadic(二价的)"函数。以此类推,不定参数的方程也就被称为variadic(可变参数函数)
。git
const sum = (a, b) => a + b; const arity = sum.length; console.log(arity); // => 2 // The arity of sum is 2
一个接收某个函数做为参数的函数成为高等函数,该函数能够选择返回一个函数也能够返回其余类型github
const filter = (pred, xs) => { const result = []; for (var idx = 0; idx < xs.length; idx += 1) { if (pred(xs[idx])) { result.push(xs[idx]); } } return result; };
const is = type => x => Object(x) instanceof type;
filter(is(Number), [0, '1', 2, null]); //=> [0, 2]
将本来一个多参数值的函数封装为固定参数数目的函数的过程称为Partial Applicationexpress
let sum = (a, b) => a + b; // partially applying `a` to `40` let partial = sum.bind(null, 40); // Invoking it with `b` partial(2); //=> 42
将一个N参数值的函数转化为N个一元函数的组合,Currying与Partial Application的区别在于Partial Application最终生成的函数容许接收多个值,而Currying生成的函数序列中的每一个函数只容许接收一个参数编程
let sum = (a, b) => a + b; let curriedSum = (a) => (b) => a + b; curriedSum(40)(2) // 42.
感受有点像设计模式里的Decorator,即可以将两个指定类型组合转化为一个新值的函数设计模式
最多见的组合便是常见的函数组合,容许你将不一样的函数组合成一个返回单值的函数数组
const compose = (f, g) => a => f(g(a)) // Definition const floorAndToString = compose((val)=> val.toString(), Math.floor) //Usage floorAndToString(121.212121) // "121"
一个没有任何反作用,而且返回值只由输入决定的函数成为纯函数app
let greet = "yo"; greet.toUpperCase(); // YO; greet // yo;
As opposed to:dom
let numbers = [1, 2, 3]; numbers.splice(0); // [1, 2, 3] numbers // []
若是一个函数,除了返回值以外,还会修改某些其它状态,或者与外部函数等有可观测的交互
console.log("IO is a side effect!");
屡次执行下都不会产生反作用的函数被称为具备幂等性的函数
f(f(x)) = f(x)
Math.abs(Math.abs(10))
那些并无线性定义参数的函数风格被称为Point-Free Style,这类型每每须要currying 或者 Higher-Order functions。
// Given let map = fn => list => list.map(fn); let add = (a, b) => a + b; // Then // Not points-free - `numbers` is an explicit parameter let incrementAll = (numbers) => map(add(1))(numbers); // Points-free - The list is an implicit parameter let incrementAll2 = map(add(1));
incrementAll
明确规定了参数numbers
, 而incrementAll2
是对于参数的封装,并无显性说明numbers
参数,所以它能够被称为Points Free。通常来讲,Points-free的函数都不会用常见的function
或者=>
关键字来定义。
关联到遵循某些规则的函数的对象,譬如monoid
计算中经常使用到的一些复合值(complex)或者简单值(primitive),包括函数。通常来讲,函数式编程中的值都被认为是不可变值。
5 Object.freeze({name: 'John', age: 30}) // The `freeze` function enforces immutability. (a) => a
注意,譬如Functor, Monad这样包含其余值的结构体自己也是值,这就是说,这些复合值也能够相互包含。
对于一个值的不可变引用,不能跟变量相混淆。Variable即指那些可能在任意点呗更改的引用。
const five = 5 const john = {name: 'John', age: 30}
常量通常认为是透明的,也就是说,它们能够被值自己代替而不影响最终的计算结果,上面的两个常量也能够用下述方式表述:
john.age + five === ({name: 'John', age: 30}).age + (5)
上述表达式会一直返回真。
Functor即指那些能够引用
map
函数的对象,JavaScript中最简单的函数就是Array
。
[2,3,4].map( n => n * 2 ); // [4,6,8]
假设func
构造为一个实现了map
函数的对象,f
、g
则是任意的函数,只要func
遵循如下规则就能够将func
称为一个functor:
Let func
be an object implementing a map
function, and f
, g
be arbitrary functions, then func
is said to be a functor if the map function adheres to the following rules:
func.map(x => x) == func
以及
func.map(x => f(g(x))) == func.map(g).map(f)
咱们将Array
称为Functor,也是由于它遵循了如下规则:
[1, 2, 3].map(x => x); // = [1, 2, 3]
以及
let f = x => x + 1; let g = x => x * 2; [1, 2, 3].map(x => f(g(x))); // = [3, 5, 7] [1, 2, 3].map(g).map(f); // = [3, 5, 7]
实现了
of
方法的Functor,Of
会将任何单值转化为一个Functor
Pointed Functor在Array中的实现为:
Array.prototype.of = (v) => [v]; [].of(1) // [1]
Lift很相似于
map
,不过它能够用于多个Functors:
在单值函数下,Map与Lift的做用是一致的:
lift(n => n * 2)([2,3,4]); // [4,6,8]
而Lift能够容许输入多个值:
lift((a, b) => a * b)([1, 2], [3]); // [3, 6]
一个能够直接用其值来替换而不会影响到程序表现的表达式称为透明引用
譬如咱们有一个叫greet
的引用
let greet = () => "Hello World!";
greet()
的调用均可以被Hello World!
直接替换,所以能够将greet
称为透明引用。当一个应用由表达式组合而成而且没有任何反作用的时候,该系统能够由部分推导而来
Lazy evaluation 便是所谓的只有在须要某个值的时候才进行计算的机制。在函数式语言中,这个机制就容许对于那些近乎无限的列表进行操做。
let rand = function*() { while(1<2) { yield Math.random(); } }
let randIter = rand(); randIter.next(); // Each exectuion gives a random value, expression is evaluated on need.
一个monoid就是与某个恒等值进行组合以后不会影响现有结果的数据类型
一个最简单的Monoid就是以下所示:
1 + 1; // 2
数据类型是number,函数是+
:
1 + 0; // 1
恒等式的值是0
,将0
与任何数相加并不会改变值。有时候,monoid类型进行不一样的交换操做也不会影响结果:
1 + (2 + 3) == (1 + 2) + 3; // true
数组链接也能够认为是一个monoid:
[1, 2].concat([3, 4]); // [1, 2, 3, 4]
恒等值便是空数组: []
[1, 2].concat([]); // [1, 2]
['cat,dog','fish,bird'].chain(a => a.split(',')) // ['cat','dog','fish','bird'] //Contrast to map ['cat,dog','fish,bird'].map(a => a.split(',')) // [['cat','dog'], ['fish','bird']]
You may also see of
and chain
referred to as return
and bind
(not be confused with the JS keyword/function...) in languages which provide Monad-like constructs as part of their standard library (e.g. Haskell, F#), on Wikipedia and in other literature. It's also important to note that return
and bind
are not part of the Fantasy Land spec and are mentioned here only for the sake of people interested in learning more about Monads.
实现了
extract
与extend
函数的对象
let CoIdentity = v => ({ val: v, extract: this.v, extend: f => CoIdentity(f(this)) })
Extract 能够将值从Functor中吐出来:
CoIdentity(1).extract() // 1
Extend则是会返回一个跟Commonad相同值的函数:
CoIdentity(1).extend(co => co.extract() + 1) // CoIdentity(2)
一个Applicative Functor就是一个实现了
ap
函数的对象,Ap
能够将某个对象中的某个值转化为另外一个对象中的相同类型的值
[(a)=> a + 1].ap([1]) // [2]
一个转化函数
用不一样方式存储的可以代表相同数据的转换
譬如,一个二维的数组能够存储为数组:[2,3]
或者对象:{x: 2, y: 3}
。
// Providing functions to convert in both directions makes them isomorphic. const pairToCoords = (pair) => ({x: pair[0], y: pair[1]}) const coordsToPair = (coords) => [coords.x, coords.y] coordsToPair(pairToCoords([1, 2])) // [1, 2] pairToCoords(coordsToPair({x: 1, y: 2})) // {x: 1, y: 2}
实现了
equals
函数的对象,便可以与其余对象进行对比判断是否属于同一类型,被称为Setoid。
下面对于原型的扩充能够将Array变成Setoid。
Array.prototype.equals = arr => { var len = this.length if (len != arr.length) { return false } for (var i = 0; i < len; i++) { if (this[i] !== arr[i]) { return false } } return true } [1, 2].equals([1, 2]) // true [1, 2].equals([0]) // false
一个拥有concat
,即将另外一个对象转化为相同类型的函数,函数的对象称为Semigroup。
[1].concat([2]) // [1, 2]
实现了reduce函数,便可以将一个对象转化为其余类型的函数,的对象称为Foldable对象。
let sum = list => list.reduce((acc, val) => acc + val, 0); sum([1, 2, 3]) // 6
通常来讲,函数都会注释代表它们的参数类型和返回值类型
// functionName :: firstArgType -> secondArgType -> returnType // add :: Number -> Number -> Number let add = x => y => x + y // increment :: Number -> Number let increment = x => x + 1
若是一个函数接收其余函数做为参数,譬如这样:
// call :: (a -> b) -> a -> b let call = f => x => f(x)
这里的a
, b
, c
, d
代表参数能够是任意类型,不过它会将类型a
转化为另外一个类型b
,而对于下面这个map,它的注释代表了它会输入一个a类型的列表,而后转化为另外一个包含了b类型的列表。
// map :: (a -> b) -> [a] -> [b] let map = f => list => list.map(f)