美文网首页
JS函数式编程

JS函数式编程

作者: Code_Rush | 来源:发表于2017-05-09 19:56 被阅读0次

一等公民

当我们说函数是“一等公民”的时候,我们实际上说的是它们和其他对象都一样...所以就是普通公民(坐经济舱的人?)。函数真没什么特殊的,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量...等等。
添加一些没有实际用处的间接层实现起来很容易,但这样做除了徒增代码量,提高维护和检索代码的成本外,没有任何用处。
在 Github 上随便一搜就能看到对这个概念的集体无视,或者也可能是无知。我们来看一个杜撰的例子:

var hi = function(name){
  return "Hi " + name;
};

var greeting = function(name) {
  return hi(name);
};

greeting 只不过是转了个身然后以相同的参数调用了 hi 函数而已,因此我们可以这么写:

var greeting = hi;


greeting("times");
// "Hi times"

换句话说,hi 已经是个接受一个参数的函数了,为何要再定义一个额外的包裹函数,而它仅仅是用这个相同的参数调用 hi?完全没有道理。这就像在大夏天里穿上你最厚的大衣,只是为了跟热空气过不去,然后吃上个冰棍。真是脱裤子放屁多此一举。

另外,如果一个函数被不必要地包裹起来了,而且发生了改动,那么包裹它的那个函数也要做相应的变更。

httpGet('/post/2', function(json){
  return renderPost(json);
});

如果 httpGet 要改成可以抛出一个可能出现的 err 异常,那我们还要回过头去把“胶水”函数也改了。

// 把整个应用里的所有 httpGet 调用都改成这样,可以传递 err 参数。
httpGet('/post/2', function(json, err){
  return renderPost(json, err);
});

写成一等公民函数的形式,要做的改动将会少得多:

httpGet('/post/2', renderPost);  // renderPost 将会在 httpGet 中调用,想要多少参数都行

纯函数

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
在函数式编程中,我们讨厌这种会改变数据的笨函数。我们追求的是那种可靠的,每次都能返回同样结果的函数

// 不纯的
var minimum = 21;

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


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

在不纯的版本中,checkAge 的结果将取决于 minimum 这个可变变量的值。换句话说,它取决于系统状态(system state);这一点令人沮丧,因为它引入了外部的环境,从而增加了认知负荷(cognitive load)。
另一方面,使用纯函数的形式,函数就能做到自给自足。我们也可以让 minimum 成为一个不可变(immutable)对象,这样就能保留纯粹性,因为状态不会有变化。
副作用让一个函数变得不纯是有道理的:从定义上来说,纯函数必须要能够根据相同的输入返回相同的输出;如果函数需要跟外部事物打交道,那么就无法保证这一点了。

追求“纯”的理由

可缓存性(Cacheable)

首先,纯函数总能够根据输入来做缓存。实现缓存的一种典型方式是 memoize 技术:

var squareNumber  = memoize(function(x){ return x*x; });

squareNumber(4);
//=> 16

squareNumber(4); // 从缓存中读取输入值为 4 的结果
//=> 16

squareNumber(5);
//=> 25

squareNumber(5); // 从缓存中读取输入值为 5 的结果
//=> 25

可移植性/自文档化(Portable / Self-Documenting)

可测试性(Testable)

合理性(Reasonable)

很多人相信使用纯函数最大的好处是引用透明性(referential transparency)。如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么我们就说这段代码是引用透明的。
由于纯函数总是能够根据相同的输入返回相同的输出,所以它们就能够保证总是返回同一个结果,这也就保证了引用透明性。

并行代码

最后一点,也是决定性的一点:我们可以并行运行任意纯函数。因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态(race condition)。

柯里化(curry)

curry 的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
你可以一次性地调用 curry 函数,也可以每次只传一个参数分多次调用。

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12

这里我们定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。
通过简单地传递几个参数,就能动态创建实用的新函数;而且还能带来一个额外好处,那就是保留了数学的函数定义,尽管参数不止一个。

代码组合(compose)

函数饲养

这就是 组合(compose,以下将称之为组合):

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};

f 和 g 都是函数,x 是在它们之间通过“管道”传输的值。

组合看起来像是在饲养函数。你就是饲养员,选择两个有特点又遭你喜欢的函数,让它们结合,产下一个崭新的函数。组合的用法如下:

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);

shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"

pointfree

pointfree 模式指的是,永远不必说出你的数据。
函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。

// 非 pointfree,因为提到了数据:word
var snakeCase = function (word) {
  return word.toLowerCase().replace(/\s+/ig, '_');
};

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

看到 replace 是如何被局部调用的了么?这里所做的事情就是通过管道把数据在接受单个参数的函数间传递。利用 curry,我们能够做到让每个函数都先接收数据,然后操作数据,最后再把数据传递到下一个函数那里去。另外注意在 pointfree 版本中,不需要 word 参数就能构造函数;而在非 pointfree 的版本中,必须要有 word 才能进行进行一切操作。
pointfree 模式能够帮助我们减少不必要的命名,让代码保持简洁和通用。对函数式代码来说,pointfree 是非常好的石蕊试验,因为它能告诉我们一个函数是否是接受输入返回输出的小函数。比如,while 循环是不能组合的。不过你也要警惕,pointfree 就像是一把双刃剑,有时候也能混淆视听。并非所有的函数式代码都是 pointfree 的,不过这没关系。可以使用它的时候就使用,不能使用的时候就用普通函数。

参考资料:https://www.gitbook.com/book/llh911001/mostly-adequate-guide-chinese/details

相关文章

  • 深入浅出Rxjs笔记 一

    一.函数式编程 函数式编程要求: 声明式 纯函数 数据不可变js 不算纯粹意义上的函数式编程语言,但是,在js中函...

  • JavaScript中的函数式编程

    JS 函数式编程指南 什么是函数式编程 简单说,"函数式编程"是一种"编程模型"(programming para...

  • 【JavaScript】技术参考资料

    JS基础、高级、进阶 MDN·JavaScript 函数式编程 阮一峰老师的入门简介: 函数式编程初探、函数式编程...

  • 函数式编程语言Elixir

    函数式编程可能js中用得也不少,至于什么是函数式编程,思想精髓暂时没有。 Elixir : 函数式编程 1、安装环...

  • 《JS函数式编程指南》读书笔记

    JS函数式编程指南 函数是一等公民 函数式编程的目的 函数式编程的目的是使用函数来抽象作用在数据之上的控制流和操作...

  • Node.js学习(8.5)

    Node.js安装配置 指令式编程思维 顺序 选择 循环 函数式编程思维 函数 抽象化函数 JavaScript语...

  • Ramda.js 入门

    1、前言 Ramda.js 是函数式编程领域中类似 lodash、underscore 的存在。在函数式编程流行的...

  • Java中的回调函数为什么只能访问final变量?

    经常写JS的话对函数式编程应该不陌生,JS是天然支持函数式的,而在Java中,需要用一个接口来实现伪函数编程,类似...

  • 函数式编程阅读笔记

    《js函数式编程指南》 函数式编程中 不涉及this改变的问题(因为用不到this)。 纯函数是这样一种函数,即相...

  • 函数式编程学习资料汇总

    在线阅读 函数式编程入门教程-阮一峰的网络日志 JS函数式编程式指南 Transducers Functor 图书...

网友评论

      本文标题:JS函数式编程

      本文链接:https://www.haomeiwen.com/subject/vouitxtx.html