FP的要求FP的优势Pure FunctionHigher-order FunctionClosurePartial ApplicationPoint-free styleFunctor 函子Continuation-passing style(CPS)参数处理过滤参数传1返1条件判断参考文章
FP的要求
在函数式编程语言世界里面:
- function必须是一等公民。一等公民是指function可以作为一个function参数,也可以作为function返回值,也可以赋值给变量或者其他对象属性。
JavaScript中,function就是一等公民。
- 引用透明。同样的in params,不论多少次,返回必须要一样,没有造成副作用(side effect),函数是纯函数(pure function)。
什么是Side Effect?修改传入的参数、外部状态、发送http请求、db查询、打印log、获取input、dom查询、访问(系统)状态等等
- 将简单的指令式调用封装为函数。例如👇
function formatter(formatFn) { return function inner(str){ return formatFn( str ); }; } var lower = formatter( function (v){ return v.toLowerCase(); } );
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
举个例子:
function greaterThan(n) { return function inner (m) { return m > n; }; } var greaterThan10 = greaterThan(10); console.log(greaterThan10(11)); // true
n的scopre是greaterThan。greaterThan运行之后,按理说n应该被回收。但是由于inner中用到了n,所以它被“暂时保留”了下来。这个称之为:n被内部函数inner
closure
。Partial Application
偏函数应用定义:partial application是指一种减少函数参数个数
Arity
的过程。什么 Arity
?
指的是形式参数parameter的个数。js中可以通过func.length获取到必填参数的个数。
偏函数工具函数实现:
function partial(fn, ...presetArgs) { return function partiallyApplied(...laterArgs) { return fn( ...presetArgs, ...laterArgs ) } }
可以利用偏函数,基于基础函数(参数多,更灵活),二次封装函数(参数少,针对某种场景)。例如:
function ajax( url, data, cb ) { } // ajax 异步请求 let getOrder = partial(ajax, "http://some.api/order") // 用于请求order接口的异步函数 let getLastOrder = partial(getOrder, { id: ORDER_ID }) // 用于获取最后一个order的异步函数 let getLastOrder2 = partial(ajax, "http://some.api/order", { id: ORDER_ID }) // 第二种定义方法
偏函数用途:
- 切割传参数的时空背景(时间、程式的不同区块),原本的方式需要在调用的时候立刻传入所有的参数,如果你的函数中有些参数待会才传入,可以考虑使用currying和partial application。
- 实现柯里化(见下一部分)
- 可以隐藏细节,增强可读性(例如上面基于ajax的封装)
参考文章:
Point-free style
Point-free(又写成Pointfree,中文:无参数,无点),正式名称为:tacit programming,其中的point(点)指的就是函数的parameter(形式参数)。
作用:Pointfree透过隐藏parameter - argument(形参-实参对应),减少视觉上的干扰,上层操作不直接操作数据,只合成运算过程。
举个例子:
function double(x) { return x * 2 } [1,2,3,4,5].map( double ) // point free style [1,2,3,4,5].map( v => double(v) ) // not point free style
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。例如:class Wrapper { constructor (value) { this.value = value } map (f) { return new Wrapper(f(this.value)) } } // 使用代码 let something = new Wrapper(2) // something => { value: 2 } let otherthing = something.map(function (value) { return value + 3; }) // otherthing => { value: 5 }
class Wrapper 就可以视作一个 functor,因为它具有map方法。而且在map方法中,通过传入的mapper function,使内部值转化,最后返回映射后的新值。
Continuation-passing style(CPS)
在JS中,continuation表示函数结束后下一步骤的callback,也就是接着要做的事,而CPS就是在函数结束后把要做的事指定给下一个函数(当作参数)。
我理解是这种回调callback风格就是cps。
代码参考:
"use strict"; var sumRecursion = (function IIFE(){ return function sum(...nums) { return recursion(nums, v=>v) } function recursion([result, ...nums], cont) { if (nums.length == 0) return cont(result) return recursion( nums, function(v) { return cont(result + v) }) } })(); console.log(sumRecursion( 3, 1, 17, 94, 8 ));
可以参考:
参数处理
过滤参数
场合:将两个函数组合,比如说把A function 传入B function ,但此时B function 传入的参数跟A function 数量不符合。比如说:
- B:map(...)传3个变数
value
,index
array
- A:parseInt(...)接收两个变数
string
radix
例如:
['1', '2'].map(parseInt) // 返回:[1, NaN] // 因为'2'传进来相当于执行:parsetInt('2', 1)。1进制肯定是不可能出现'2'
封装
unary
函数,仅让一个参数能通过:function unary(fn) { return function onlyOneArg(arg){ return fn( arg ); }; }
使用效果:
['1', '2'].map(unary(parseInt)) // parseint经过包装后,只会接收到第1个参数 // 返回:[1, 2]
传1返1
基础函数:接受一个参数,然后原封不动返回
function identical(v) { return v }
这样某些场景下,就不用写
() =>v
的语法,例如:str.split().filter(identical) // 过滤空串 promise1.then(identical(val)).then(func1)
条件判断
取反函数:
function not (testerfn) { return function negated (...args) { return !testerfn(...args) } }
条件函数:
function when (testerfn, fn) { return function conditional (...args) { if (testerfn(...args)) { return fn(...args) } } }