本文章记录本人在学习 函数式 中理解到的一些东西,加深记忆和并且整理记录下来,方便之后的复习。

函数是一等公民

"一等"这个术语通常用来描述值。当函数被看作“一等公民”时,那它就可以去任何值可以去的地方,很少有限制。比如那数值和函数做比较。

  • 函数与数字一样可以存储为变量

    var fortytwo = function () { return 42; };
    
  • 函数与数字一样可以存储为数组的一个元素。

    var fortytwos = [42, function () { return 42; }];
    
  • 函数与数字一样可以作为对象的成员变量。

    var fortytows = {number: 42, fun: function () {}}
    
  • 函数与数字一样可以在使用的时候直接创建出来。

    42 + (function () { return 42; })();
    
  • 函数与数字一样可以被传递给另一个函数。

    function weirdAdd(n, f) { return n + f() };
    
    weirdAdd(42, function() { return 42; });
    
  • 函数与数字一样可以被另一个函数返回。

    return 42;
    
    return function() { return 42; };
    

    关于最后两点,其实就是对“高阶”函数的定义,也就是:

  • 以一个函数作为参数。

  • 返回一个函数作为结果。

从上面举的栗子可以看来。做为“一等公民”的函数就会拥有类型数字的性质。

Applicative 编程

简单来说Applicative编程定义为函数A作为参数提供给函数B。在Applicative编程三个典型的例子是:maoreducefilter

看下面栗子:

var nums = [1, 2, 3, 4, 5];

// 数组的所有项都 * 2
function doubleAll(array) {
    return _.map(array, function (n) { return * 2 });
}

// 数组的所有项相加,返回 sum / _.size(array)
function average(array) {
    var sum = _.reduce(array, function (a, b) { return a + b });
    return sum / _.size(array);
}

// 塞选数组中的偶数项,并且返回一个新的数组
function onlyEven(array) {
    return _.filter(arr, function (n) {
        return (n % 2) === 0;
    });
}

doubleAll(nums); // [2, 4, 6, 8, 10];
average(nums); // 3
only(nums); / [2,4]

看了上面的栗子,能看出maoreducefilter会在某一个地方最终调用作为参数的匿名函数。实际上,这些函数的语义可以由这个调用关系来定义:

  • _.map()遍历集合并对每一个值调用一个函数,返回结果的集合。
  • _.reduce利用函数将值的集合合并成一个值,该函数接受一个累积和本次处理的值。
  • _.filter对集合每一个值调用一个谓词函数(也就是返回true或者false的函数),抽取谓词函数返回true的值的集合。

集合中心编程

函数式编程对于需要操作集合中元素的任务非常有用。新建一个简单的对象:{a: 1, b: 2},然后拿_.map()使用_.identity()函数(返回其参数的函数)。例如:

_.map({a: 1, b: 2}, _.identity); // [1, 2];

从集合为中心的角度看,Underscore和一般函数式编程所提倡的是要建立一个统一的处理方式,使我们可以重用一套综合的函数。

用 100 个函数操作一个数据结构,必用 10 个函数操作 10 个数据结构要好。

定义几个 Applicative 函数

Underscore提供了许多的applicative函数,有兴趣的可以去阅读一下API以及源码。通过小栗子来看看如何创建一个applicative函数:

// 一个简单、接受一定数量的参数并连接它们的函数并不是 applicative。

function cat() {
    var head = _.frist(arguments);
    if (existy(head))
        return head.concat.apply(head, _.reset(arguments));
    else
        return [];
}

cat([1, 2, 3], [4, 5], [6, 7, 8]); // [1, 2, 3, 4, 5, 6, 7, 8]

接着继续定义一个construct函数,construct函数接受一个元素和一个数组,并且cat将元素防止在数组前方:

function construct(head, tail) {
    return cat([head], _.toArray(tail)));
}
construct(42, [1, 2, 3]); // [42, 1, 2, 3]

虽然上面的construct函数中使用到了cat,但是它并没有将cat作为参数传入,所以不符合要求。

在定义一个函数mapcat

function mapcat(fun, coll) {
    return cat.apply(null, _.map(coll, fun);
}

mapcat(function (e) {
    return construct(e, [","]);
}, [1,2,3]); // [1, ",", 2, ",", 3, ","]

mapcat函数接受一个函数fun,与_.map用了相同的方式,对给定集合中的每个元素进行调用。这种fun的使用是mapcatapplicative本质。

当映射函数返回一个数组,mapcat可以将其展平。接着继续定义butLastinterpose函数:

function butLast(coll) {
    return _.toArray(coll).slice(0, -1);
}

function interpose(inter, coll) {
    return butLast(mapcat(function (e) {
        return construct(e, [inter]);
    }, coll));
}

interpose(",", [1, 2, 3]); // [1, ",", 2, ",", 3]

用较低级别的函数来逐步定义和使用离散功能。

数据思考

js中,对象类型是非常强大的,但与其一起工作的工具并不完全是函数式的。相反,用js对象更常用的模式是,以多态调度为目的来附加方法。

虽然把js对象当成数据映射来操作和访问的工具本身很少,但是幸好有Underscore提供了有用的一系列操作。其中有:_.keys_.values_.pluck。有兴趣的可以去看看API文档。

_.pluck函数和_.omit函数做一个小栗子:

var person = {name: "Romy", token: "j3983ij", password: "trigress"};

var info = _.omit(person, 'token', 'password'); // {name: "Romy"}

var creds = _.pick(person, 'token', 'password'); // {token: "j3983ij", password: "trigress"}

上面的栗子是,使用相同的“危险”键:tokenpassword_.onmit函数接受一个黑名单,从对象中删除键,而_.pick根据白名单保留相应键,且都不会修改原来的对象。

如果使用过了Underscore函数来操作对象,你会觉得非常类似与SQL,都是根据一个强大的声明规约进行过滤和处理逻辑数据表。

总结

最后总结一下一等函数:

  • 它们可以存储在变量中。
  • 它们可以被存储在数组中的插槽中。
  • 它们可以存储在对象的字段中。
  • 它们可以根据需求来创建。
  • 它们可以被传递到其他函数中。
  • 它们可以被其他函数返回。

一等函数的意思就是以上的几点了。可以简单它拥有数值一样的性质。

在总结一下applicative编程,简单来说就是函数A作为参数提供给函数B_.map_.reduce_.filter就是最好的例子。

有啥不对,请斧正!


_我已经从中二毕业了
7.9k 声望235 粉丝

不搞前端会死星人。