【JavaScript ES6 函数式编程入门经典】读书笔记(第一章)

就是这本书

写在前面的话

之前想看函数式编程时找了许多资料,许多对这个只是大概的描述了一段,然后看到有人推荐这本书,所以写下这本读书笔记,给有需要的人参考。

第一章 函数式编程简介

函数的第一条原则是要小。函数的第二条原则是要更小。 ——ROBERT C.MARTIN

1.1 什么是函数式编程?

f(x) = Y
  • 函数必须总是接受一个参数
  • 函数必须总是返回一个值
  • 函数应该依据接收到的参数(X)而不是外部环境运行
  • 对于一个给定的X,只会输出唯一的一个Y

函数式编程技术主要基于数学函数它的思想。<br />
看个栗子:

var percentValue = 5;
var caculateTax = (value) => {
  return value/100 * (100 + percentValue)
}

这个caculateTax依赖于全局变量percentValue, 因此在数学意义上不能被称作真正的函数。修改成:

var caculteTax = (value, percentValue) => {
  return value/100 * (100 + percentValue)
}

现在caculateTax算得上一个真正的函数了,通过这个简单的栗子,可以简单定义一下函数式编程:函数式编程是一种范式,我们能够一次创建仅依赖输入就可以完成自身逻辑的函数。这保证了当函数被多次调用时仍然返回相同的结果,不会改变任何外部环境的变量,这将产生可缓存、可测试的代码库。

函数与javascript方法
函数是一段可以通过其名称被调用的代码。它可以传递参数并返回值。
var simple = (a) => { return a } // 一个简单的函数
simple(5) // 用其名称调用
然而方法是一段必须通过其名称及其关联对象的名称被调用的代码。
var obj = { simple : (a) =>{ return a } }
obj.simple(5) // 用其名称及其关联对象调用

1.2 引用透明性

所有的函数对于相同的输入都将返回相同的值,这一特性被称为引用透明性。

一个简单的函数

var identity = (i) => { return i }

该函数满足引用透明性,现在它被用于其它函数调用之间。

sum(4, 5) + identity(1)

根据引用透明性,上面的语句可以转换成

sum(4, 5) + 1

这个过程被称为替换模型, 因为可以直接替换函数的结果(主要是因为函数的逻辑不依赖其它全局变量),这与它的值是一样的。这也使得并发代码缓存成为可能。

遵循引用透明性的函数只依赖来自参数的输入, 不依赖全局数据。因此线程可以自由地运行,没有任何锁机制。

引用透明性是一种哲学
“引用透明性”一词来自分析哲学。该哲学分支研究自然语言的语义及其含义。单词“Referential”或“Referent”意指表达式引用的事物。句子中的上下文是“引用透明的”,如果用另一个引用相同实体的词语替换上下文中的一个词语,并不会改变句子的含义。
替换函数的值并不影响上下文,这就是函数式编程的哲学!

1.3 命令式、声明式与抽象

函数式编程主张声明式编程和编写抽象的代码。

来看个栗子:

// 打印数组的每个元素
var array = [1, 2, 3]
for (i = 0; i < array.length; i++)
  console.log(array[i]) // 打印 1, 2, 3

用数组长度的索引计算结果编写了一个隐式的for循环并打印出数组项,"打印数组的元素",但是看起来像是在告诉编译器该做什么,在上面栗子中,告诉编译器“要获得数组长度、循环数组、用索引获取每一个数组元素,等等”我们将之称为“命令式”解决方案。命令式编程主张告诉编译器“如何”做。

现在来看另外一方面,声明式编程。在声明式编程中,我们要告诉编译器做“什么”,而不是“如何”做。“如何”做的部分将被抽象到普通函数中(这些函数被称为高阶函数),更改如下:

var array = [1, 2, 3]
array.forEach((element) => {
  console.log(element) // 打印 1, 2, 3
})

这个代码片段移除了如何做的部分,只关心做什么的部分。

函数式编程主张以抽象的方式创建函数,这些函数能够在代码的其他部分被重用。

1.4 纯函数

大多数函数式编程的好处来自于编写纯函数,那么什么是纯函数?

纯函数:对给定的输入返回相同的输出的函数。

var double = (value) => value * 2

这个"double"函数就是一个纯函数,遵循引用透明性。

  • 纯函数产生可测试的代码
    不纯的函数具有副作用,例如之前的这个函数
var percentValue = 5;
var caculateTax = (value) => {
  return value/100 * (100 + percentValue)
}

caculateTax不是纯函数,因为它依赖外部环境计算其逻辑。假设在运行相同的测试用例时,外部环境也在改变变量percentValue的值,那么之前给的定输入对应的就不是唯一的值了。

纯函数有一个重要属性:纯函数不应改变任何外部环境的变量。换而言之,纯函数不应依赖任何外部变量也不应改变任何外部变量。

// 举个反例
bar global = 'globalValue'
var badFunction = (value) => {
  global = 'changed'
  return value * 2
}

看看这个反例,当badFunction被调用时,它将全局变量global的值变更了,假设另一个函数的逻辑依赖global变量的话,调用badFunction就影响了其他函数的行为。具有这种性质的函数会使得代码库变得难以测试。

  • 合理的代码
    通过创建和使用纯函数,能够让我们非常简单的推理代码或函数。函数(无论它是否为纯函数)必须总是具有一个有意义的名称。例如double加倍函数就不能命名成dd

用一个内置的Math.max函数来测试一下推理能力。
给定函数调用:
Math.max(3, 4, 5, 6)
为了给出结果,是不是没有看max的实现?为什么?因为Math.max是纯函数。

1.5 并发代码

纯函数总是允许我们并发地执行代码,因为纯函数不会改变它的环境。当然,js并没有真正的多线程来并发地执行函数,但如果你的项目使用了webworker来模拟多线程并行执行任务,这种时候就需要用纯函数来代替非纯函数。
举个栗子:

let global = "something"
let function1 = (input) => {
  // 处理input
  // 改变global
  global = "somethingElse"
}
let function2 = () => {
  if (global === "something") {
    // 业务逻辑
  }
}

如果我们需要并发的执行function1function2,假设线程T-1选择function1执行,线程T-2选择function2执行,如果T-1在T-2之前执行,那么并发执行这些函数就会引起不良反应,现在把它们改为纯函数。

let function1 = (input, global) => {
  // 处理input
  // 改变global
  global = "somethingElse"
}
let function2 = (global) => {
  if (global === "somethins") {
    // 业务逻辑
  }
}

移动了global变量,把它作为两个函数的参数,使它们变成纯函数。由于函数并不依赖外部环境(global 变量),因此不必像之前那样担心线程的执行顺序。

1.6 可缓存

纯函数总是为给定的输入返回相同的输出,所以缓存纯函数的输出也是可能的。

举个栗子,有这么一个做耗时计算的函数。

var longRunningFunction = (ip) => {
  // do long running tasks and return
}

然后我们有一个记账对象,它存储了longRunningFunction函数的所有调用结果。

var longRunningFnBookKeeper = {
  2: 3,
  4: 5,
  ...
}

使用纯函数的定义,在调用longRunningFunction之前检查key是否存在longRunningFnBookKeeper中,比如说:

// 检查key是否在longRunningFnBookKeeper中
// 如果在,则返回结果,否则更新记账对象
longRunningFnBookKeeper.hasOwnProperty(ip) ?
  longRunningFnBookKeeper[ip] :
  longRunningFnBookKeeper[ip] = longRunningFunction(ip)

这样就是缓存了纯函数的调用结果。

1.7 管道与组合

使用纯函数,我们只需要在函数中做一件事。纯函数应该被设计为只做一件事。只做一件事并把它做到完美是UNIX的哲学,我们在实现纯函数时也将遵循这一原则。UNIX和LINUX平台有很多用户日常任务的命令,例如:cat用于打印文件内容,grep用于搜索文件,wc用于计算行数。这些命令的确一次只解决一个问题,但是可以通过组合或者管道来完成复杂的任务。假如我们要在一个文件中找到一个特定的名称并统计它出现次数,在命令提示符中命令如下:

cat jsBook | grep -i "composing" | wc

组合不是UNIX/LINUX命令行独有的,但它们是函数式编程范式的核心。称为函数式组合。

javascript不支持组合函数函数的操作符"|",但是我们可以创建自己的支持组合的函数。

1.8 纯函数是数学函数

先见1.6 “可缓存”中的那段longRunningFunctionlongRunningFnBookKeeper代码。
假设通过多次调用,longRunningFnBookKeeper增长为如下的对象:

longRunningFnBookKeeper = {
  1: 32,
  2: 4,
  3: 5,
  5: 6,
  11: 44
}

分析一下该对象,可见longRunningFnBookKeeper接受一个输入并为给定的范围映射输出,此处的关键是,输入具有强制的、相应的输出。在key中也不存在映射两个输出的输入。

比对数学函数的定义(维基百科看不了),基本上与纯函数一致,从上述例子也能看到数学函数的思想被借鉴到函数式范式的世界。

1.9 javascript是函数式编程语言吗

javascript是函数式编程语言,函数式编程主张函数必须接受至少一个参数并返回一个值。坦率的说,可以创建一个不接收参数并且实际上什么也不返回的函数,例如这个:var useless = () => {},这串代码执行并不会报错,原因是javascript不是一种纯函数语言(比如说Haskell),而更像是一种多范式语言。

javascript支持将函数作为参数,以及将函数传递给另一函数等特性——主要原因是javascript将函数视为一等公民,由于函数定义的约束,开发者需要在创建javascript函数时将其考虑在内。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 什么是函数式编程?为何它重要? 数学中的函数 关键点: 函数必须总是接受一个参数 函数必须总是返回一个值 函数应该...
    前端一菜鸟阅读 695评论 0 3
  • 原文链接:https://github.com/EasyKotlin 值就是函数,函数就是值。所有函数都消费函数,...
    JackChen1024阅读 5,922评论 1 17
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,067评论 1 32
  • 愿从从匆匆岁月的手中,偷一抹时光,闲暇之余,畅游心中的童话镇,遇见你,遇见我,又陌生,然然……
    醒梦可可西里阅读 151评论 0 0
  • 今天的你 依然坚毅 风吹不倒你 雨摧不毁你 今天的你 依然倔强的生存 不再畏惧 也不再无哭泣 今天的你 坚守着真诚...
    远古之旅阅读 320评论 0 9