Function Programming

本篇文章是自己对于JavaScript函数式编程一书的读书笔记。利用underscore框架介绍几个函数式编程几个概念。

高阶函数

  1. 将函数作为参数;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!--repeatedly值版本-->
    function repeatedly(times,value){
    return _.map(_.range(times),()=>{ return value })
    }

    <!--repeatedly 函数版本-->

    function repeatedly(times,fun){
    return _.map(_.range(times),fun)
    }

    repeatedly(3,function(){
    return Math.floor(Math.random()*10+1)
    })
    repeatedly是函数式编程的一个典型思维,将值变成函数。
  2. 返回其他函数的函数。
    react和redux里面用了大量的返回其他函数的函数。包括高阶组件,applyMiddleware函数。
    Thunk函数也是一个返回其他函数的函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var Thunk = function(fn){
    return function(){
    var args = Array.prototype.slice.call(arguments);
    return function(callback){
    args.push(callback);
    return fn.apply.(this,args);
    }
    }
    }

    var readFileThunk = Thunk(fs.readFile);

    readFileThunk(fileA)(callback);

由函数构建函数

  1. 函数式的精华(dispatch函数);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    function existy(x) {
    return x != null;
    }
    function doWhen(cond, action) {
    if (tructhy(cond))
    return action();
    else
    return undefined;
    }
    function invoker(NAME, METHOD) {
    return function (target) {
    if (!existy(target)) {
    alert('Must provide a target');
    }
    var targetMethod = target[NAME];
    var args = _.rest(arguments);
    return doWhen(existy(targetMethod) && METHOD === targetMethod, function () {
    return targetMethod.apply(target, args);
    });
    }
    }
    function dispatch() {
    var funs = _.toArray(arguments);
    var size = funs.length;

    return function (target) {
    var ret = undefined;
    var args = _.rest(arguments);
    for (var funIndex = 0; funIndex < size; funIndex++) {
    var fun = funs[funIndex];
    ret = fun.apply(fun,construct(target,args))
    if(existy(ret)) return ret;
    }

    if(existy(ret)) return ret;
    }
    }

    var str = dispatch(invoker('toString',Array.prototype.toString),invoker('toString',String.prototype.toString));
    console.log(str('a'));
    console.log(str(_.range(10)));
    dispatch将多个invoker组合在一起,形成多态函数,或根据不同的参数产生不同行为的参数。
  2. 柯里化。
    对于每个逻辑参数,柯里化函数会逐渐返回已配置好的函数,直到所有的参数用完。
    1
    2
    3
    4
    5
    6
    7
    function curry(fun){
    return function(firstArg){
    retun function(secondArg){
    return fun(firstArg,secondArg);
    }
    }
    }
  3. 部分应用。
    柯里化函数逐渐返回消耗参数的函数,直到所有的参数耗尽,然而部分应用函数是一个”部分执行”,等待接收剩余的参数立即执行的参数。
    1
    2
    3
    4
    5
    function partApply(f, x) {
    return function(y) {
    return f(x, y);
    }
    }
  4. 通过组合端至端的拼接函数。
    一个理想化的函数式程序是向函数流水线的一端输送的一块数据,从另一端输出一个全新的数据块。
    例如: !_isString(name);
  • _isString接收一个对象,并返回一个布尔值。
  • !接收一个布尔值,并返回一个布尔值。

_.compose函数从右往左执行。也就是说,最右边的函数的结果会被送入其左侧的函数,一个接一个。

1
2
3
4
function not(x){
return !x;
}
var isntString = _.compose(not,_isString);

纯度,不变性和更改政策。

  1. pure function
  • 其结果只能从它的参数的值来计算。
  • 不能依赖于能被外部操作改变的数据。
  • 不能改变外部状态。
    javaScript会导致不纯的函数或方法如Date.now(),console.log(),this和全局变量。
  1. 数据的不可变性。(immutablejs);

    1
    2
    3
    4
    5
    function touchAndLog(touchFn) {
    let data = { key: 'value' };
    touchFn(data);
    console.log(data.key); // 猜猜会打印什么?
    }

    在不查看 touchFn 的代码的情况下,因为不确定它对 data 做了什么,你是不可能知道会打印什么(这不是废话吗)。但如果 data 是 Immutable 的呢,你可以很肯定的知道打印的是 value。

  2. 更改政策。
    随便拿到一个对象并改变它,更好的策略是把对象放入容器中,并更改容器。

基于流的编程。

  1. 惰性链。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function LazyChain(obj){
    this._calls = [];
    this._target = obj;
    }

    LazyChain.prototype.invoke = function(methodName){
    var args = _.rest(arguments);
    this._calls.push(function(target){
    var meth = target[methodName];
    return meth.apply(target,args);
    })

    return this;
    }


    LazyChain.prototype.force = ()=>{
    return _.reduce(this._calls,function(target,thunk){
    return thunk(target);
    },this._target)
    }

    new LazyChain([1,2,3]).invoke('sort').force();