FP的要求
在函数式编程语言世界里面:
function必须是一等公民。一等公民是指function可以作为一个function参数,也可以作为function返回值,也可以赋值给变量或者其他对象属性。
JavaScript中,function就是一等公民。
引用透明。同样的in params,不论多少次,返回必须要一样,没有造成副作用(side effect),函数是纯函数(pure function)。
什么是Side Effect?修改传入的参数、外部状态、发送http请求、db查询、打印log、获取input、dom查询、访问(系统)状态等等
将简单的指令式调用封装为函数。例如👇
1 | function formatter(formatFn) { |
FP的优势
- 低复杂度:function不会有状态,也不会直接存取或读取外接状态。对于相同输入,一定会有相同输出。
- 无需语句(statement):所有Pure Functional Programming Languages 都是由表达式(expression) 所组成的,这跟其他大多数语言不同,大多数程式语言由表达式(expression) 和语句(statement) 组成。
Pure Function
纯函数是指没有副作用的函数。在js中,最常见的是如果将对象传给参数,那么函数拿到的是对象的引用,内部就可能会修改外部数据状态,函数不再pure了。
可以使用不可变数据来避免这种情况,可以参考 ImmutableJS 实战 、ES6语法中的let/const与Object.freeze()/Object.isFrozen()
Higher-order Function
高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入
- 输出一个函数
Closure
闭包定义
当一个函数可以记住并存取到不同scope(作用域)的变量,甚至这个函数在不同scope被执行,称之Closure
举个例子:
1 | function greaterThan(n) { |
n的scopre是greaterThan。greaterThan运行之后,按理说n应该被回收。但是由于inner中用到了n,所以它被“暂时保留”了下来。这个称之为:n被内部函数inner **closure
**。
Partial Application
偏函数应用定义:partial application是指一种减少函数参数个数Arity
的过程。
什么 **
Arity
**?
指的是形式参数parameter的个数。js中可以通过func.length获取到必填参数的个数。
偏函数工具函数实现:
1 | function partial(fn, ...presetArgs) { |
可以利用偏函数,基于基础函数(参数多,更灵活),二次封装函数(参数少,针对某种场景)。例如:
1 | function ajax( url, data, cb ) { } // ajax 异步请求 |
偏函数用途:
- 切割传参数的时空背景(时间、程式的不同区块),原本的方式需要在调用的时候立刻传入所有的参数,如果你的函数中有些参数待会才传入,可以考虑使用currying和partial application。
- 实现柯里化(见下一部分)
- 可以隐藏细节,增强可读性(例如上面基于ajax的封装)
参考文章:
Point-free style
Point-free(又写成Pointfree,中文:无参数,无点),正式名称为:tacit programming,其中的point(点)指的就是函数的parameter(形式参数)。
作用:Pointfree透过隐藏parameter - argument(形参-实参对应),减少视觉上的干扰,上层操作不直接操作数据,只合成运算过程。
举个例子:
1 | function double(x) { |
Functor 函子
定义
A functor is something that can be mapped over.
来自 Haskell 和 Fantasy Land specification。
那something 是什么?就是一组值放在某个容器(集合)里,容器就是指这些值怎么摆放,比如说阵列(依序列)或者物件(用key 来取值…等)。
那什么叫that can be mapped over?也就是js中map(..) 做的事,把每个值经过mapper(..) 得到新值,最后再把新值依照放进同样结构的容器后return 。
JS的Array对象上,使用Map还是forEach呢?
map(..)
是用来映射值的,不是来产生副作用的。如果要传入带有副作用的函数,建议还是使用forEach(..)
或者干脆写 loop 避免造成困惑。
总结来说,任何具有**map
**方法(映射关系)的资料结构,都可以视为functor。例如:
1 | class Wrapper { |
class Wrapper 就可以视作一个 functor,因为它具有map方法。而且在map方法中,通过传入的mapper function,使内部值转化,最后返回映射后的新值。
Continuation-passing style(CPS)
在JS中,continuation表示函数结束后下一步骤的callback,也就是接着要做的事,而CPS就是在函数结束后把要做的事指定给下一个函数(当作参数)。
我理解是这种回调callback风格就是cps。
代码参考:
1 | ; |
可以参考:
参数处理
过滤参数
场合:将两个函数组合,比如说把A function 传入B function ,但此时B function 传入的参数跟A function 数量不符合。比如说:
- B:map(…)传3个变数
value
,index
,array
- A:parseInt(…)接收两个变数
string
,radix
例如:
1 | ['1', '2'].map(parseInt) |
封装unary
函数,仅让一个参数能通过:
1 | function unary(fn) { |
使用效果:
1 | ['1', '2'].map(unary(parseInt)) // parseint经过包装后,只会接收到第1个参数 |
传1返1
基础函数:接受一个参数,然后原封不动返回
1 | function identical(v) { |
这样某些场景下,就不用写() =>v
的语法,例如:
1 | str.split().filter(identical) // 过滤空串 |
条件判断
取反函数:
1 | function not (testerfn) { |
条件函数:
1 | function when (testerfn, fn) { |