老规矩,这篇文章记录书中的重点部分,外加自己的见解,不会对全书进行复述,但记录的绝对是最重要的部分,想要了解跟多内容请看原版图书。
函数式编程的目标:使用函数来抽象作用在数据之上的控制流与操作,从而在系统中消除副作用并减少对状态的改变。
纯函数所具有的性质:
仅取决于提供的输入,而不依赖于任何在函数求值期间或调用间隔时可能变化的隐藏状态和外部状态。
不会造成或超出其作用域的变化。如修改全局变量对象或引用传递的参数。
引用透明:如果一个函数对于相同的输入始终产生相同的结果,那么说它是引用透明的。
函数式编程是指为创建不可变的程序,通过消除外部可见的副作用,来对纯函数的声明式的求值过程。
高阶函数:某些函数可以接收其它的函数作为参数,或者返回一个函数,这样的函数称为高阶函数。
map函数的简单实现:
1 | function map(arr, fn) { |
reduce函数的简单实现:
1 | function reduce(arr, fn,accumulator) { |
filter函数的简单实现:
1 | function filter(arr, predicate) { |
lodash对象已经定义了好了很多函数,在本章中_代表lodash对象。使用lodash的reduce对数组求和:
1 | _([0,1,3,4,5]).reduce(_.add); //-> 13 |
实战:格式化名字
1 | var names = ['alonzo church', 'Haskell curry', 'stephen_kleene', |
_.chain
可以添加一个输入对象(或数组)的状态,从而能将这些输入转换为所需输出的操作链接在一起。_.chain
的另一个好处是可以惰性计算,在调用value()
前并不会真正的执行任何操作。
它返回的是一个lodash包装对象,而不是原生的对象。
像写SQL一样编程
假如有SQL语句:
1 | SELECT p.firstname FROM Person p |
使用_.mixin
可以给lodash对象添加新的函数(这里其实相当于起别名),如:
1 | _.mixin({'select': _.map, |
那么查询语句就可以修改为:
1 | _.from(persons) |
递归求和
1 | function sum(arr) { |
尾递归的递归求和
1 | function sum(arr, acc = 0) { |
元数:函数所接受的参数数量,也被称为函数的长度。
柯里化:柯里化是一种在所有参数被提供之前,挂起或“延迟”函数执行,将多个参数转化为一元函数序列的技术。
如一个三个参数的柯里化函数定义:curry(f) :: ((a,b,c) -> d) -> a -> b -> c -> d
。
一个二元柯里化的实现:
1 | function curry2(fn) { |
多元柯里化实现:
1 | function curry(fn, ...args) { |
应用部分:应用部分是一种通过将函数的不可变参数自己初始化为固定值来创建更小元数函数的操作。就比如一个5个参数的函数我们通过应用部分可以定义为一个给定了2个特定参数的函数,那么调用的时候只要给另外三个就行了。
应用部分的实现:
1 | function partial() { |
实战1:使用lodash的应用部分对核心语言扩展。
1 | // 注意lodash中的占位符是_,也就是_.partial参数中的_会在调用时替换为调用时的参数 |
实战2:生成特定秒数的延迟函数。
1 | const Scheduler = (function () { |
函数组合:函数组合是一种将已被分割的简单任务组织成复杂行为的整体过程。
定义如下:
1 | g :: A -> B //函数g输入A返回B |
组合的实现:
1 | function compose(/* fns */) { |
identity(I-组合子):返回和参数相同的组合子。
1 | // identity :: (a) -> a |
注意:这里的组合子只写了单纯的一层的实现,实际使用的时候都是柯里化后的结果。就比如identity组合子其实是R.curry(identity)
,柯里化后的组合子操作起来更方便。
tap(K-组合子):将没有函数返回值的函数返回输入值。
1 | // tap :: (a -> *) -> a -> a |
alt(OR-组合子):如果第一个函数返回有值,那么就返回第一个函数的返回值否则返回第二个参数的返回值。
1 | const alt = function (func1, func2) { |
seq(S-组合子):两个或者多个函数作为参数并返回一个新的函数,会用相同的值顺序调用所有的这些函数。
1 | const seq = function(/*funcs*/) { |
fork(join-组合子):需要三个函数作为参数,即以一个join函数将两个fork函数的处理结果再次进行处理。
1 | const fork = function(join, func1, func2){ |
函数式编程空值的处理:Functor和Monad。
函数式编程对空值的处理通常不是用try-catch
和判断是否为空来处理,它的处理方式通常是在外面包一层数据结构。类似与jQuery处理DOM元素一样,会包装一层jQuery对象,这样如果没有找到DOM元素处理起来也不会报错。
Functor(函子)是一个可以将函数应用到它包裹的值上,并将结果再包裹起来的数据结构。
1 | class Wrapper { |
Functor的局限性:使用compose组合包装函数后会有多层包装结构,也就是需要多个.map(R.identity)
来抽出结果,比较麻烦。可以使用Monad来处理。
1 | class Wrapper { |
Maybe Monad用来处理是否为空的判断逻辑。它有2个具体的类型:Just和Nothing。
Just(value)表示有值时的容器。
Nothing()表示没有值时的容器。
1 | class Maybe { |
Either Monad用来或的判断逻。它也有2个具体的类型:Left和Right。
Left(a)包含一个可能的错误消息或抛出的异常对象。
Right(b)包含一个成功值。
1 | class Either { |
IO Monad用来处理不纯的函数。
1 | class IO { |
记忆化(memoization):将函数的计算结果保存起来,如果下次传入相同的参数那么就直接返回结果。
1 | Function.prototype.memoized = function () {// 记忆化函数 |
上述斐波拉契函数使用了递归会有较高的空间使用率可以使用尾递归来优化:
1 | const factorial = (n, current = 1) => |
最后我们简单的用Promise封装一下AJAX作为本篇文章的结束:
1 | var getJSON = function (url) { |