JS函数式编程读书笔记 - 2
本文章记录本人在学习 函数式 中理解到的一些东西,加深记忆和并且整理记录下来,方便之后的复习。
函数是一等公民
"一等"这个术语通常用来描述值。当函数被看作“一等公民”时,那它就可以去任何值可以去的地方,很少有限制。比如那数值和函数做比较。
函数与数字一样可以存储为变量
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编程
三个典型的例子是:mao
、reduce
和filter
。
看下面栗子:
var nums = [1, 2, 3, 4, 5];// 数组的所有项都 * 2function 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); // 3only(nums); / [2,4]
看了上面的栗子,能看出mao
、reduce
和filter
会在某一个地方最终调用作为参数的匿名函数。实际上,这些函数的语义可以由这个调用关系来定义:
_.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
的使用是mapcat
的applicative
本质。
当映射函数返回一个数组,mapcat
可以将其展平。接着继续定义butLast
和interpose
函数:
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"}
上面的栗子是,使用相同的“危险”键:token
和password
,_.onmit
函数接受一个黑名单,从对象中删除键,而_.pick
根据白名单保留相应键,且都不会修改原来的对象。
如果使用过了Underscore
函数来操作对象,你会觉得非常类似与SQL
,都是根据一个强大的声明规约进行过滤和处理逻辑数据表。
总结
最后总结一下一等函数:
- 它们可以存储在变量中。
- 它们可以被存储在数组中的插槽中。
- 它们可以存储在对象的字段中。
- 它们可以根据需求来创建。
- 它们可以被传递到其他函数中。
- 它们可以被其他函数返回。
一等函数的意思就是以上的几点了。可以简单它拥有数值一样的性质。
在总结一下applicative
编程,简单来说就是函数A
作为参数提供给函数B
。_.map
、_.reduce
和_.filter
就是最好的例子。
有啥不对,请斧正!