函数式编程不是用函数来编程,旨在将复杂的函数符合成简单的函数。
1.函数是一等公民。所谓”第一等公民”(first class),指的是函数 与其他数据类型一样,处于平等地位,可以赋值给其他变量,也 可以作为参数,传入另一个函数,或者作为别的函数的返回值。
2..不可改变量。在函数式编程中,我们通常理解的变量在函数式 编程中也被函数代替了:在函数式编程中变量仅仅代表某个表达 式。这里所说的’变量’是不能被修改的。所有的变量只能被赋一次 初值 。
3.map & reduce他们是常用的函数式编程的方法。
函数式编程常用核心概念:
1.纯函数
对于相同的输入,永远会得到相同的输出,而且没有任 何可观察的副作用,也不依赖外部环境的状态。
var x = [1,2,3,4,5,6];
console.log(x.slice(0,3)); //[1,2,3]
console.log(x.slice(0,3)); //[1,2,3]
console.log(x.splice(0,3)); //[1,2,3]
console.log(x.splice(0,3)); //[4,5,6]
对于上述例子,slice就是一个纯函数,而splice就不是,因为两次得到的结果不同。
//不纯的
var min = 18;
var checkage = age => age>min;
//纯的
var checkage = age => age>18;
上面的例子,不纯是因为依赖了外部变量min。
但是上面的纯函数有个问题就是18被写死了,这样函数的扩展性就比较差,下面我们用柯里化来解决这个问题。
2.函数的柯里化
柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
对于前面说的问题,现用柯里化解决
var checkage = min => (age => age>min);
var checkage18 = checkage(18);
console.log(checkage18(20)); //true
// 上面的函数等价于
function checkage(min){
return function(age){
return age>min
}
}
console.log(checkage(18)(20));
事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数, 得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种 对参数的“缓存”,是一种非常高效的编写函数的方法。
3.函数组合
纯函数以及如何把它柯里化写出的洋葱代码 h(g(f(x))), 为了解决函数嵌套的问题,我们需要用到“函数组合”:
const compose = (f,g) => (x =>f(g(x)));
var first = arr => arr[0];
var reverse = arr => arr.reverse();
var last = compose(first,reverse);
console.log(last([1,2,3,4,5])); //5
//第一行代码等价于
function compose(f,g){
return function(x){
return f(g(x));
}
}
4.point Free
point Free把一些对象自带的方法转化成纯函数,不要命名转瞬即逝 的中间变量。
const compose = (f,g) => (x => f(g(x)));
var toUpperCase = word =>word.toUpperCase();
var split = x => (str => str.split(x));
var f = compose(split(''),toUpperCase);
console.log(f("abcd efgh")); // ["A", "B", "C", "D", " ", "E", "F", "G", "H"]
5.声明式与命令式代码
命令式代码的意思就是,我们通过编写一条又一条指令去让计算 机执行一些动作,这其中一般都会涉及到很多繁杂的细节。而声 明式就要优雅很多了,我们通过写表达式的方式来声明我们想干 什么,而不是通过一步一步的指示。
//命令式
let ceos= [];
for(var i = 0; i < companies.length; i++){
ceos.push(companies[i].ceo)
}
//声明式
let ceos = companies.map(c => c.ceo);
函数式编程的一个明显的好处就是这种声明式的代码,对 于无副作用的纯函数,我们完全可以不考虑函数内部是如何实 现的,专注于编写业务代码。优化代码时,目光只需要集中在 这些稳定坚固的函数内部即可。
相反,不纯的函数式的代码会产生副作用或者依赖外部系 统环境,使用它们的时候总是要考虑这些不干净的副作用。在 复杂的系统中,这对于程序员的心智来说是极大的负担。
6.惰性求值、惰性函数
在指令式语言中以下代码会按顺序执行,由于每个函数 都有可能改动或者依赖于其外部的状态,因此必须顺序 执行。
一旦我们接纳了函数式哲学,惰性(或延迟)求值这一技术会变得非常有趣。在讨论并行时已经见过下面的代码片断:
String s1 = somewhatLongOperation1();
String s2 = somewhatLongOperation2();
String s3 = concatenate(s1, s2);
在一个命令式语言中求值顺序是确定的,因为每个函数都有可能会变更或依赖于外部状态,所以就必须有序的执行这些函数:首先是 somewhatLongOperation1,然后 somewhatLongOperation2,最后 concatenate,在函数式语言里就不尽然了。
前面提到只要确保没有函数修改或依赖于全局变量,somewhatLongOperation1 和 somewhatLongOperation2 可以被并行执行。
假设我们不想并行运行这两个函数,那是不是就按照字面顺序执行他们好了呢?答案是否定的,我们只在其他函数依赖于 s1 和 s2 时才需要执行这两个函数。我们甚至在 concatenate 调用之前都不必执行他们——可以把他们的求值延迟到 concatenate 函数内实际用到他们的位置。
如果用一个带有条件分支的函数替换 concatenate 并且只用了两个参数中的一个,另一个参数就永远没有必要被求值。在 Haskell 语言中,不确保一切都(完全)按顺序执行,因为 Haskell 只在必要时才会对其求值。
7.高阶函数
函数当参数,把传入的函数做一个封装,然后返回这个封装 函数,达到更高程度的抽象。
//命令式
var add = function(a,b){
return a+b;
}
function math(func,array){
return func(array[0],array[1]);
}
math(add,[1,2]); //3
8.尾调用优化
指函数内部的后一个动作是函数调用。该调用的返回值,直接返回给函数。。 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归需要保存大 量的调用记录,很容易发生栈溢出错误,如果使用尾递归优化,将递归变为循 环,那么只需要保存一个调用记录,这样就不会发生栈溢出错误了。
//不是尾递归
function sum(n){
if(n===1){
return 1;
}else{
n+sum(n-1);
}
}
其运算过程如下:
sum(5)
(5+sum(4))
(5+(4+sum(3)))
(5+(4+(3+sum(2))))
(5+(4+(3+2+sum(1))))
(5+(4+(3+(2+1))))
(5+(4+(3+3)))
(5+(4+6))
(5+10)
15
//细数尾递归
function sum(x,total){
if(x === 1){
return x+toal;
}else{
return sum(x-1,x+total);
}
}
其运算过程如下:
sum(5,0)
sum(4,5)
sum(3,9)
sum(2,12)
sum(1,14)
15
整个计算过程是线性的,调用一次sum(x, total)后,会进入下一个栈,相关的数据信息和 跟随进入,不再放在堆栈上保存。当计算完后的值之后,直接返回到上层的 sum(5,0)。这能有效的防止堆栈溢出。 在ECMAScript 6,我们将迎来尾递归优化,通过尾递归优化,javascript代码在解释成机器 码的时候,将会向while看起,也就是说,同时拥有数学表达能力和while的效能。
9.闭包
闭包就是能够读取其他函数内部变量的函数
10.范畴与容器
我们可以把”范畴”想象成是一个容器,里面包含两样东西。值 (value)、值的变形关系,也就是函数。
范畴论使用函数,表达范畴之间的关系
伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法 起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今 天的”函数式编程"。
本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、 行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。为 什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种 数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数 运算法则了。
函数不仅可以用于同一个范畴之中值的转换,还可以用于将 一个范畴转成另一个范畴。这就涉及到了函子(Functor)。
函子是函数式编程里面重要的数据类型,也是基本的运算 单位和功能单位。它首先是一种范畴,也就是说,是一个容 器,包含了值和变形关系。比较特殊的是,它的变形关系可 以依次作用于每一个值,将当前容器变形成另一个容器
当下函数式编程比较火热的库
RXJS*
cycleJS
lodashJS、lazy*
underscoreJS
ramdaJS*
资料:https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/
网友评论