JS函数式编程小记

5次阅读

共计 2968 个字符,预计需要花费 8 分钟才能阅读完成。

JS 函数式编程思想

  • 维基百科定义:函数式编程(英语:functional programming),又称泛函编程,是一种编程范式,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。

个人觉得这个定义肥肠生动形象了。
函数式编程处处体现着数学的逻辑思想,通过减少外部依赖来避免外部变量的改变对于程式内部状态的改变,使得程序变得简易、声明式、易于维护。
这里推荐一下 F 大的《JS 函数式编程指南》,系统介绍了函数式编程的思想。
中文版电子书下载地址走你:https://legacy.gitbook.com/bo…
本文章是自己阅读后的一些理解和记录~

First Class Funtcion

这个概念同样出于书中。规定了变量可以取的值得范围,以及该类型的值可以进行的操作。根据类型的值的可赋值状况,可以把类型分为三类。

  1. 一级的(first class)。该等级类型的值可以传给子程序作为参数,可以从子程序里返回,可以赋给变量。大多数程序设计语言里,整型、字符类型等简单类型都是一级的。
  2. 二级的(second class)。该等级类型的值可以传给子程序作为参数,但是不能从子程序里返回,也不能赋给变量。
  3. 三级的(third class)。该等级类型的值连作为参数传递也不行。

在 scala 中, 函数是可以作为参数来传递并且返回的, 所以 scala 中的函数就是 first class function
PS:scala 是一门多范式(multi-paradigm) 的编程语言, 设计初衷是要集成面向对象编程和函数式编程的各种特性。
因此,function 作为变量的一种,可以被储存,当作参数传递,或者被复制给变量等等 …

Pure Function

首先弄清纯函数的概念:Pure function 意指相同的輸入,永遠會得到相同的輸出,而且沒有任何顯著的副作用。
它符合两个条件:

  1. 此函数在相同的输入值时,总是产生相同的输出。函数的输出和当前运行环境的上下文状态无关。
  2. 此函数运行过程不影响运行环境,也就是无副作用(如触发事件、发起 http 请求、打印 /log 等)。

简单来说,也就是当一个函数的输出不受外部环境影响,同时也不影响外部环境时,该函数就是纯函数,也就是它只关注逻辑运算和数学运算,同一个输入总得到同一个输出。
比如 slicesplice,但是 slice 是纯函数,而 splice 不是,因为后者会改变源数据。
这就是强调使用纯函数的原因,因为纯函数相对于非纯函数来说,在可缓存性、可移植性、可测试性以及并行计算方面都有着巨大的优势。

看一下书中的例子:

// impure
var minimum = 21;

var checkAge = function(age) {return age >= minimum;};

// pure
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};

在 impure 的版本中,checkAge 的結果將取決於 minimum 這個变量,换句话说,除了输入以外,它将取决于系统状态,一旦引用了外部环境,它就不符合 pure 了。
当然,也可以直接冻结变量,使得状态不再变化,那么它也是一个纯函数:

var immutableState = Object.freeze({minimum: 21,});

Curry(柯里化)

书中定义柯里化的概念是:你可以只透過部分的參數呼叫一個 function,它會回傳一個 function 去處理剩下的參數。
函数柯里化是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。从某种意义上来讲,这是一种对参数的缓存,是一种非常高效的编写函数的方法,将一个低阶函数转换为高阶函数的过程就叫柯里化。

// 举个栗子
var checkage = min => (age => age > min);
var checkage18 = checkage(18);
checkage18(20);
// =>true
var curry = require('lodash').curry;

// 柯里化两个纯函数
var match = curry((what, str) => str.match(what));
var filter = curry((f, ary) => ary.filter(f));

// 判断字符串里有没有空格
var hasSpaces = match(/\s+/g);

hasSpaces("hello world");  // [' ']
hasSpaces("spaceless");  // null

var findSpaces = filter(hasSpaces);

findSpaces(["tori_spelling", "tori amos"]);  // ["tori amos"]

Compose—Functional 饲养

假设要对某个字符串做一系列操作,我们做的事情一多,嵌套的层数会非常深。类似于巢状堆积(比如 c(b(a())))这种堆砌方式非常不直观,当我们希望代码以平行方式(书中成为左倾)组合执行时,就成为Compose
compose 的概念直接來自於數學課本,所以 compose 都有的一个特性:

// 結合律(associativity)var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true

PointFree

pointfree 模式指的是,永远不必说出你的数据。它的意思是说,函数无须提及将要操作的数据是什么样的

// 非 pointfree,因為我們提到資料:word
var snakeCase = function(word) {return word.toLowerCase().replace(/\s+/ig, '_');
};

// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

这种风格能够帮助我们减少不必要的命名,让代码保持简洁和通用。
当然,为了在一些函数中写出 Point Free 的风格,在代码的其它地方必然是不那么 Point Free 的,这个地方需要自己取舍。

声明式与宣告式代码

// 命令式
var makes = [];
for (var i = 0; i < cars.length; i++) {makes.push(cars[i].make);
}
// 宣告式
var makes = cars.map(function(car) {return car.make;});

命令式代码:命令“机器”如何去做事情 (how),这样不管你想要的是什么(what),它都会按照你的命令实现。
声明式代码:告诉“机器”你想要的是什么 (what),让机器想出如何去做(how)。
命令式的循环要求你必须先实例化一个数组,而且执行完这个实例化语句之后,解释器才继续执行后面的代码。然后再直接迭代 cars 列表,手动增加计数器,就像你开了一辆零部件全部暴露在外的汽车一样。这不是优雅的程序员应该做的。

声明式的写法是一个表达式,如何进行计数器迭代,返回的数组如何收集,这些细节都隐藏了起来。它指明的是做什么,而不是怎么做。除了更加清晰和简洁之外,map 函数还可以进一步独立优化,甚至用解释器内置的速度极快的 map 函数,这么一来我们主要的业务代码就无须改动了

正文完
 0