From 50f798c582d6b39a94079c274afa1daf3a8af206 Mon Sep 17 00:00:00 2001 From: Steve245270533 Date: Tue, 30 Apr 2024 03:07:53 +0000 Subject: [PATCH] deploy: b7c5471a923b03d55c05e30b32076580dcbbccb5 --- 404.html | 4 ++-- README.html | 4 ++-- guide/Favoring-Curry.html | 4 ++-- guide/Introducing-Ramda.html | 4 ++-- guide/README.html | 4 ++-- guide/Thinking-in-Ramda-Combining-Functions.html | 4 ++-- guide/Thinking-in-Ramda-Declarative-Programming.html | 4 ++-- guide/Thinking-in-Ramda-Getting-Started.html | 4 ++-- guide/Thinking-in-Ramda-Immutability-and-Arrays.html | 4 ++-- guide/Thinking-in-Ramda-Immutability-and-Objects.html | 4 ++-- guide/Thinking-in-Ramda-Lenses.html | 4 ++-- guide/Thinking-in-Ramda-Partial-Application.html | 4 ++-- guide/Thinking-in-Ramda-Pointfree-Style.html | 4 ++-- guide/Thinking-in-Ramda-Wrap-Up.html | 4 ++-- guide/Why-Curry-Helps.html | 4 ++-- guide/Why-Ramda.html | 4 ++-- guide/ch1.html | 4 ++-- guide/ch10.html | 4 ++-- guide/ch11.html | 4 ++-- guide/ch12.html | 4 ++-- guide/ch13.html | 4 ++-- guide/ch2.html | 4 ++-- guide/ch3.html | 4 ++-- guide/ch4.html | 4 ++-- guide/ch5.html | 4 ++-- guide/ch6.html | 4 ++-- guide/ch7.html | 4 ++-- guide/ch8.html | 4 ++-- guide/ch9.html | 4 ++-- guide/code/part1_exercises/README.html | 4 ++-- guide/code/part2_exercises/README.html | 4 ++-- hashmap.json | 2 +- index.html | 4 ++-- 33 files changed, 65 insertions(+), 65 deletions(-) diff --git a/404.html b/404.html index f3169c2..1cebf42 100644 --- a/404.html +++ b/404.html @@ -10,13 +10,13 @@ - +
Skip to content

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.

Released under the MIT License.

- + \ No newline at end of file diff --git a/README.html b/README.html index 3d72f3c..db62099 100644 --- a/README.html +++ b/README.html @@ -13,13 +13,13 @@ - +
Skip to content

JavaScript函数式编程指南(Functional Programming)

在线文档地址

说明

📖 本仓库收集了GitHub开源库中最优秀最精华的函数式编程文章,将其整合到VitePress文档方便浏览,并非本人编写,如有侵权请及时联系本仓库作者删除:

联系方式:

QQ: 245270533

关于函数式编程

函数式编程是一种编程范式,它将计算视为数学函数的评估,并避免使用程序状态以及易变对象。在JavaScript中,函数式编程不仅可以提高代码的可读性和可维护性,还可以帮助开发者编写更少的错误和更易于测试的代码。

无论您是初学者还是有经验的开发者,这个文档库都是探索和深化JavaScript函数式编程知识的理想场所。通过详细的指南、实例和最佳实践,您将能够有效地利用JavaScript的函数式编程特性,提升您的编程技能和项目质量。欢迎深入探索,开启您的函数式编程之旅!

收集目录

名称地址
mostly-adequate-guide-chinesehttps://github.com/llh911001/mostly-adequate-guide-chinese
Ramda Documentationhttps://ramda.cn/
wangzengdi's Bloghttps://adispring.github.io/

Released under the MIT License.

- + \ No newline at end of file diff --git a/guide/Favoring-Curry.html b/guide/Favoring-Curry.html index ae763f1..d10763a 100644 --- a/guide/Favoring-Curry.html +++ b/guide/Favoring-Curry.html @@ -13,7 +13,7 @@ - + @@ -171,7 +171,7 @@ .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority']))) .then(R.sortBy(R.get('dueDate'))); };

这就是我们柯里化的原因。


课程结束了。

我警告过你的。

下一次,当我让你去看别人的东西而不是我的的时候,你会注意了吧。现在不读我的文章可能已经来不及了,但是他们的作品真的很棒,强烈推荐大家看一下:

这里还有一篇我今天刚看到的新的文章。不知它是否会经的其时间的考验,但现在看来值得一读:

一点不太好的小秘密

柯里化尽管非常强大,但单独使用并不足以让你的代码变得 "那么" 优雅。

应该有三个重要的组成部分:

Ramda 的目标之一便是:在一个简单的包里面提供所有这些功能。

致谢

buzzdecafe 帮助编辑了本文和上一篇文章,并且这次还起了一个完美标题。谢谢,Mike!

- + \ No newline at end of file diff --git a/guide/Introducing-Ramda.html b/guide/Introducing-Ramda.html index a913534..ec1b40a 100644 --- a/guide/Introducing-Ramda.html +++ b/guide/Introducing-Ramda.html @@ -13,7 +13,7 @@ - + @@ -49,7 +49,7 @@ // get passed in: var amountsToValue = map(amtAdd1Mod7); amountsToValue(amountObjects); // => [1, 6, 0]

Ramda 提供了 npm 包,可以下载下来尝试一下。如果你对 Ramda 库有什么想法或改进建议,请联系我们

- + \ No newline at end of file diff --git a/guide/README.html b/guide/README.html index 8170022..588c816 100644 --- a/guide/README.html +++ b/guide/README.html @@ -13,13 +13,13 @@ - +
Skip to content

This is the Simplified Chinese translation of mostly-adequate-guide, thank Professor Franklin Risby for his great work!

关于本书

这本书的主题是函数范式(functional paradigm),我们将使用 JavaScript 这门世界上最流行的函数式编程语言来讲述这一主题。有人可能会觉得选择 JavaScript 并不明智,因为当前的主流观点认为它是一门命令式(imperative)的语言,并不适合用来讲函数式。但我认为,这是学习函数式编程的最好方式,因为:

  • 你很有可能在日常工作中使用它

    这让你有机会在实际的编程过程中学以致用,而不是在空闲时间用一门深奥的函数式编程语言做一些玩具性质的项目。

  • 你不必从头学起就能开始编写程序

    在纯函数式编程语言中,你必须使用 monad 才能打印变量或者读取 DOM 节点。JavaScript 则简单得多,可以作弊走捷径,因为毕竟我们的目的是学写纯函数式代码。JavaScript 也更容易入门,因为它是一门混合范式的语言,你随时可以在感觉吃力的时候回退到原有的编程习惯上去。

  • 这门语言完全有能力书写高级的函数式代码

    只需借助一到两个微型类库,JavaScript 就能模拟 Scala 或 Haskell 这类语言的全部特性。虽然面向对象编程(Object-oriented programing)主导着业界,但很明显这种范式在 JavaScript 里非常笨拙,用起来就像在高速公路上露营或者穿着橡胶套鞋跳踢踏舞一样。我们不得不到处使用 bind 以免 this 不知不觉地变了,语言里没有类可以用(目前还没有),我们还发明了各种变通方法来应对忘记调用 new 关键字后的怪异行为,私有成员只能通过闭包(closure)才能实现,等等。对大多数人来说,函数式编程看起来更加自然。

以上说明,强类型的函数式语言毫无疑问将会成为本书所示范式的最佳试验场。JavaScript 是我们学习这种范式的一种手段,将它应用于什么地方则完全取决于你自己。幸运的是,所有的接口都是数学的,因而也是普适的。最终你会发现你习惯了 swiftz、scalaz、haskell 和 purescript,以及其他各种数学偏向的语言。

目录

第 1 部分

第 2 部分

未来计划

  • 第 1 部分是基础知识。这是初版草稿,所以我会及时更正发现的的错误。欢迎提供帮助!
  • 第 2 部分讲述类型类(type class),比如 functor 和 monad,最后会讲到到 traversable。我希望能塞进来一些 monad transformer 相关的知识,再写一个纯函数的应用。
  • 第 3 部分将开始游走于编程实践与学院学究之间。我们将学习 comonad、f-algebra、free monad、yoneda 以及其他一些范畴学概念。

Released under the MIT License.

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Combining-Functions.html b/guide/Thinking-in-Ramda-Combining-Functions.html index 0f2ad76..9919073 100644 --- a/guide/Thinking-in-Ramda-Combining-Functions.html +++ b/guide/Thinking-in-Ramda-Combining-Functions.html @@ -13,7 +13,7 @@ - + @@ -52,7 +52,7 @@ addOne, multiply )

这与上面的 pipe 几乎一样,除了函数的顺序是相反的。实际上,Ramda 中的 compose 函数的内部是用 pipe 实现的。

我一直这样思考 compose 的工作方式:compose(f, g)(value) 等价于 f(g(value))

注意,与 pipe 类似,compose 中的函数除最后一个外,其余都是一元函数。

compose 还是 pipe?

具有命令式编程背景的人可能觉得 pipe 更容易理解,因为可以按照从左往右的顺序进行阅读。但 compose 更容易对如上所示的嵌套函数进行转换。

我也不太清楚什么时候该用 compose,什么时候该用 pipe。由于它们在 Ramda 中基本等价,所以选择用哪个可能并不重要。只要根据自己的情况选择合适的即可。

结论

通过特定的方式进行函数组合,我们已经可以开始编写更强的函数了。

下一节

你可能已经注意到了,在进行函数组合时,我们多数情况下都可以省略函数参数。只有在最终调用组合好的函数时,才传入参数。

这在函数式编程中非常常见,我们将在下一节 Partial Application (部分应用)进行更多详细介绍。我们还会讨论如何组合多元(多参数)函数。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Declarative-Programming.html b/guide/Thinking-in-Ramda-Declarative-Programming.html index 75e72b3..5d103a3 100644 --- a/guide/Thinking-in-Ramda-Declarative-Programming.html +++ b/guide/Thinking-in-Ramda-Declarative-Programming.html @@ -13,7 +13,7 @@ - + @@ -57,7 +57,7 @@ [equals(100), always('water boils at 100°C')], [T, temp => `nothing special happens at ${temp}°C`] ])(temperature)

我目前还不需要在 Ramda 代码中使用 cond。但我很多年前编写过 Common Lisp 代码,所以 cond 函数感觉就像是位老朋友。

结论

本节中展示了很多将命令式代码转为函数声明式代码的 Ramda 函数。

下一节

你可能已经注意到了,最后我们编写的几个函数(forever21alwaysDrivingAgewater)都接受一个参数,构建一个新函数,然后将该函数作用于参数。

这也是一种常见的模式,并且 Ramda 照例提供了一些简化这些代码的便捷方法。下一节中,Pointfree Style 将演示如何简化符合这种模式的代码。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Getting-Started.html b/guide/Thinking-in-Ramda-Getting-Started.html index 790ef39..1cd6d96 100644 --- a/guide/Thinking-in-Ramda-Getting-Started.html +++ b/guide/Thinking-in-Ramda-Getting-Started.html @@ -13,7 +13,7 @@ - + @@ -31,7 +31,7 @@ filter(isEven, [1, 2, 3, 4]) //=> [2, 4]

filter 将断言函数(本例中为 isEven)作用于数组中的每个元素。每当断言函数返回 "真值" 时,相应的元素将包含到结果中;反之当断言函数返回为 "falsy" 值时,相应的元素将从结果数组中排除掉(过滤掉)。

rejectfilter 的补操作。它保留使断言函数返回 "falsy" 的元素,排除使断言函数返回 "truthy" 的元素。

js
reject(isEven, [1, 2, 3, 4]) //=> [1, 3]

find

find 将断言函数作用于数组中的每个元素,并返回第一个使断言函数返回真值的元素。

js
find(isEven, [1, 2, 3, 4]) //=> 2

reduce

reduce 比之前遇到的其他函数要复杂一些。了解它是值得的,但如果刚开始不太好理解,不要被它挡住。你可以在理解它之前继续学习其他知识。

reduce 接受一个二元函数(reducing function)、一个初始值和待处理的数组。

归约函数的第一个参数称为 "accumulator" (累加值),第二个参数取自数组中的元素;返回值为一个新的 "accumulator"。

先来看一个示例,然后看看会发生什么。

js
const add = (accum, value) => accum + value
 
 reduce(add, 5, [1, 2, 3, 4]) //=> 15
  1. reduce 首先将初始值 5 和 数组中的首个元素 1 传入归约函数 addadd 返回一个新的累加值:5 + 1 = 6
  2. reduce 再次调用 add,这次使用新的累加值 6 和 数组中的下一个元素 2 作为参数,add 返回 8
  3. reduce 再次使用 8 和 数组中的下个元素 3 来调用 add,输出 11
  4. reduce 最后一次调用 add,使用 11 和 数组中的最后一个元素 4 ,输出 15
  5. reduce 将最终累加值 15 作为结果返回。

结论

从这些集合迭代函数开始,需要逐渐习惯将函数传入其他函数的编程方式。你可能在其他语言中用过,但没有意识到正在做函数式编程。

下一节

本系列的下一篇文章,函数组合 将演示怎样以新的、有趣的方式对函数进行组合。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Immutability-and-Arrays.html b/guide/Thinking-in-Ramda-Immutability-and-Arrays.html index b8fcafa..b727dc8 100644 --- a/guide/Thinking-in-Ramda-Immutability-and-Arrays.html +++ b/guide/Thinking-in-Ramda-Immutability-and-Arrays.html @@ -13,7 +13,7 @@ - + @@ -58,7 +58,7 @@ update(2, multiply(10, nth(2, numbers)), numbers) // => [10, 20, 300, 40, 50, 60]

为了简化这个常见的用例, Ramda 提供了 adjust,其工作方式类似于操作对象的 evolve。与 evolve 不同的是, adjust 只能作用于数组的单个元素。

js
const numbers = [10, 20, 30, 40, 50, 60]
  
 adjust(multiply(10), 2, numbers)

注意,与 update 相比,adjust 将前两个参数的位置交换了一下。这可能会引起困扰,但当进行部分应用时,这样做还是很有道理的。你可能会先提供一个调整函数,比如 adjust(multiply(10)) ,然后再决定要调整的索引和数组。

结论

我们现在有了以声明式和不变式操作对象和数组的一系列方法。这允许我们在不改变已有数据的情况下,从较小的、函数式的构建模块来构建程序,通过对函数进行组合来实现我们想要的功能。

下一节

我们学习了读取、更新和转换对象属性和数组元素的方法。Ramda 提供了更通用的进行这些操作的工具:lens(透镜)。Lenses 向我们演示了它们的工作原理和方式。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Immutability-and-Objects.html b/guide/Thinking-in-Ramda-Immutability-and-Objects.html index 780f2bc..f7d377f 100644 --- a/guide/Thinking-in-Ramda-Immutability-and-Objects.html +++ b/guide/Thinking-in-Ramda-Immutability-and-Objects.html @@ -13,7 +13,7 @@ - + @@ -42,7 +42,7 @@ const defaultOptions = { value: 42, local: true } const finalOptions = merge(defaultOptions, options) }

merge 返回一个包含两个对象的所有属性和值的新对象。如果两个对象具有相同的属性,则采用第二个对象参数的属性值。

在单独使用 merge 时,采用第二个参数的属性值作为最终值是非常有用的;但在 pipeline 中可能没什么用。在 pipeline 中,通常会对一个对象进行一系列转换,其中一个转换是合并一些新的属性值到对象中。这种情况,可能需要第一个参数中的属性值作为最终值。

如果只是在 pipeline 中简单地使用 merge(newValues),可能不会得到你想要的结果。

对于这种情况,我通常会定义一个辅助函数 reverseMergeconst reverseMerge = flip(merge)。回想一下,flip 会翻转函数前两个参数的位置。

merge 执行的是浅合并。如果被合并的对象存在属性值为对象的属性,子对象并不会继续嵌套合并。如果想递归地进行 "深合并",可以使用 Ramda 的 mergeDeep 系列函数。(译者注:作者在写这篇文章时,Ramda 还没有 mergeDeep 系列函数,mergeDeep 系列函数是在 v0.24.0 中加入的)

注意,merge 只接受两个参数。如果想要将多个对象合并为一个对象,可以使用 mergeAll,它接受一个需要被合并对象的数组作为参数。

结论

本文展示了 Ramda 中一系列很好的以声明式和数据不变方式处理对象的方法。我们现在可以对对象进行增、删、改、查,而不会改变原有的对象。并且也可以在组合函数时使用这些方法来做这些事情。

下一节

现在可以以 Immutable 的方式处理对象,那么数组呢?数据不变性和数组 将演示对数组的处理。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Lenses.html b/guide/Thinking-in-Ramda-Lenses.html index d90d426..4b232fa 100644 --- a/guide/Thinking-in-Ramda-Lenses.html +++ b/guide/Thinking-in-Ramda-Lenses.html @@ -13,7 +13,7 @@ - + @@ -50,7 +50,7 @@ // twitter: '@randycoulman' // } // }

注意,setover 会按指定的方式对被透镜关注的属性进行修改,并返回整个新的对象。

结论

如果想从复杂数据结构的操作中抽象出简单、通用的方法,透镜可以提供很多帮助。我们只需暴露透镜;而不需要暴露整个数据结构、或者为每个可访问属性都提供 "setter"、"getter" 和 变换方法。

下一节

我们现在已经了解了许多 Ramda 提供的方法,已经足以应对大部分编程需要。总结 将回顾整个系列的内容,并会提到一些可能需要自己进一步探索的其他主题。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Partial-Application.html b/guide/Thinking-in-Ramda-Partial-Application.html index 20349c9..2ed9535 100644 --- a/guide/Thinking-in-Ramda-Partial-Application.html +++ b/guide/Thinking-in-Ramda-Partial-Application.html @@ -13,7 +13,7 @@ - + @@ -86,7 +86,7 @@ map(book => book.title) )(books) )

结论

本文可能是这个系列中讲解最深的一篇。部分应用和柯里化可能需要花一些时间和精力来熟悉和掌握。但一旦学会,他们会以一种强大的方式将数据处理变得更加函数式。

它们引导你通过创建包含许多小而简单代码块的 "pipeline" 的方式,来构建数据处理程序。

下一节

为了以函数式的方式编写代码,我们需要用 "声明式" 的思维代替 "命令式" 思维。要做到这点,需要找到一种函数式的方式来表示命令式的结构。声明式编程 将会讨论这些想法。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Pointfree-Style.html b/guide/Thinking-in-Ramda-Pointfree-Style.html index b572660..8f2b854 100644 --- a/guide/Thinking-in-Ramda-Pointfree-Style.html +++ b/guide/Thinking-in-Ramda-Pointfree-Style.html @@ -13,7 +13,7 @@ - + @@ -43,7 +43,7 @@ const isEligibleToVote = person => isOver18(person) && isCitizen(person)

先从 isCitizen 开始。它接受一个 person, 然后将两个函数作用于该 person,将结果使用 || 组合起来。正如在 第二节 中学到的,可以使用 either 将两个函数组合成一个新函数,然后将该组合函数作用于该 person

js
const isCitizen = person => either(wasBornInCountry, wasNaturalized)(person)

可以使用 bothisEligibleToVote 做类似的处理。

js
const isEligibleToVote = person => both(isOver18, isCitizen)(person)

现在我们已经完成了这些重构,可以看到,这两个函数都遵循上面提到的模式:person 出现了两次,一次作为函数参数;一次放到最后,将组合函数作用其上。现在可以将它们重构为 pointfree 风格的代码:

js
const isCitizen = either(wasBornInCountry, wasNaturalized)
 const isEligibleToVote = both(isOver18, isCitizen)

为什么要这么做?

Pointfree 风格需要一定的时间才能习惯。可能并不需要所有的地方都没有参数。有时候知道某些 Ramda 函数需要多少参数,也是很重要的。

但是,一旦习惯了这种方式,它将变得非常强大:可以以非常有趣的方式将很多小的 pointfree 函数组合起来。

Pointfree 风格的优点是什么呢?人们可能会认为,这只不过是为了让函数式编程赢得 "优点徽章" 的学术活动而已(实际上并没有什么用处)。然而,我认为还是有一些优点的,即使需要花一些时间来习惯这种方式也是值得的:

结论

Pointfree 风格也被成为 tacit 式编程(隐含式编程),可以使代码更清晰、更易于理解。通过代码重构将所有的转换组合成单一函数,我们最终会得到可以在更多地方使用的更小的构建块(函数)。

下一节

在当前示例中,我们尚未将所有代码都重构为 pointfree 的风格。还有一些代码是命令式的。大部分这种代码是处理对象和数组的。

我们需要找到声明式的方式来处理对象和数组。Immutability (不变性) 怎么样?我们如何以 "不变" (immutable) 的方式来操作对象和数组呢?

本系列的下一节,数据不变性和对象 将讨论如何以函数式和 immutable 的方式来处理对象。紧随其后的章节:数据不变性和数组 对数组也是相同的处理方式。

- + \ No newline at end of file diff --git a/guide/Thinking-in-Ramda-Wrap-Up.html b/guide/Thinking-in-Ramda-Wrap-Up.html index ca6aa55..dec73c5 100644 --- a/guide/Thinking-in-Ramda-Wrap-Up.html +++ b/guide/Thinking-in-Ramda-Wrap-Up.html @@ -13,13 +13,13 @@ - +
Skip to content

Thinking in Ramda: 概要总结

译者注:本文翻译自 Randy Coulman 的 《Thinking in Ramda: Wrap-Up》,转载请与原作者本人联系。下面开始正文。


本文是函数式编程系列文章:Thinking in Ramda 的总结篇。

在过去的八篇文章中,我们一直在讨论 Ramda JavsScipt 库,它提供了一系列以函数式、声明式和数据不变性方式工作的函数。

在这个系列中,我们了解了蕴含在 Ramda API 背后的一些指导原则:

  • 数据放在最后:几乎所有的函数都将数据参数作为最后一个参数。

  • 柯里化:Ramda 几乎所有的函数都是自动柯里化的。也即,可以使用函数必需参数的子集来调用函数,这会返回一个接受剩余参数的新函数。当所有参数都传入后,原始函数才被调用。

这两个原则使我们能编写出非常清晰的函数式代码,可以将基本的构建模块组合成更强大的操作。

总结

作为参考,一下是本系列文章的简单概要。

  • 入门:介绍了函数、纯函数和数据不变性思想。作为入门,展示了一些集合迭代函数,如:mapfilterreduce 等。

  • 函数组合:演示了可以使用工具(如 botheitherpipecompose)以多种方式组合函数。

  • 部分应用(Partial Application):演示了一种非常有用的函数延时调用方式:可以先向函数传入部分参数,以后根据需要将其余参数传入。借助 partialcurry 可以实现部分应用。我们还学习了 flip 和占位符(__)。

  • 声明式编程:介绍了命令式和函数式编程之间的区别。学习了如何使用 Ramda 的声明式函数代替算术、比较、逻辑和条件运算符。

  • 无参数风格编程(Pointfree Style):介绍了 pointfree 风格的思想,也被称为 "tatic" 式编程。在 pointfree 式编程时,实际上不会看到正在操作的数据参数,数据被隐含在函数中了。程序是由许多较小的、简单的构建模块组合而成。只有在最后才将组合后的函数应用于实际的数据上。

  • 数据不变性和对象:该节让我们回到了声明式编程的思想,展示了读取、更新、删除和转换对象属性所需的工具。

  • 数据不变性和数组:继续上一节的主题,展示了数据不变性在数组中的应用。

  • 透镜(Lenses):引入了透镜的概念,该结构允许我们把重点聚焦在较大的数据结构的一小部分上。借助 viewsetover 函数,可以对较大数据结构的小部分被关注数据进行读取、更新和变换操作。

后续

该系列文章并未覆盖到 Ramda 所有部分。特别是,我们没有讨论处理字符串的函数,也没有讨论一些更高阶的概念,如 transducers

要了解更多 Ramda 的作用,我建议仔细阅读 官方文档,那里有大量的信息。所有的函数都按照它们处理数据的类型进行了分类,尽管有一些重叠。比如,有几个处理数组的函数可以用于处理字符串,map 可以作用于数组和对象两种类型。

如果你对更高级的函数式主题感兴趣,可以参考一下资料:

Released under the MIT License.

- + \ No newline at end of file diff --git a/guide/Why-Curry-Helps.html b/guide/Why-Curry-Helps.html index e397249..e8cf115 100644 --- a/guide/Why-Curry-Helps.html +++ b/guide/Why-Curry-Helps.html @@ -13,7 +13,7 @@ - + @@ -50,7 +50,7 @@ .then(JSON.parse) .then(get('posts')) .then(map(get('title')))

这具有很强的逻辑性、表达力;如果不使用柯里化函数,我们几乎不可能轻易的将其实现。

总结

curry 赋予你一种强大的表达能力。

我建议你下载下来,玩一会儿。如果你已经熟悉了这个概念,我觉得你可以直接找到合适的 API。如果没有的话,建议你和你的同事一起研究一下吧。

- + \ No newline at end of file diff --git a/guide/Why-Ramda.html b/guide/Why-Ramda.html index 689f0ae..8fec873 100644 --- a/guide/Why-Ramda.html +++ b/guide/Why-Ramda.html @@ -13,7 +13,7 @@ - + @@ -83,7 +83,7 @@ var topDataAllUsers = R.compose(R.mapObj(gloss), activeByUser); var byUser = R.use(R.filter).over(R.propEq("username"));

如果想使用它,可以像下面这样:

js
var results = topData(byUser('Scott', tasks));

拜托,我只是想要我的数据!

"好的",你说,"也许这很酷,但现在我真的只是想要我的数据,我不想要不知猴年马月才能返回给我数据的函数。我还能用 Ramda 吗?"

当然可以。

让我们回到第一个函数:

js
var incomplete = R.filter(R.where({complete: false}));

如何才能变成会返回数据的东西呢?非常简单:

js
var incompleteTasks = R.filter(R.where({complete: false}), tasks);

所有其他主要的函数也是这样:只需要在调用的最后面添加一个 tasks 参数,即可返回数据。

刚刚发生了什么?

这是 Ramda 的另一个重要特性。Ramda 所有主要的函数都是自动柯里化的。这意味着,如果你不提供给函数需要的所有参数,不想立即调用函数,我们会返会一个接受剩余参数的新函数。所以,filter 的定义既包含数组,也包含了过滤数组元素的 "predicate" 函数(判断函数)。在初始版本中,我们没有提供数组值,所以 filter 会返回一个新的接受数组为参数的函数。在第二个版本中,我们传入了数组,并与 "predicate" 函数一起来计算结果。

Ramda 函数的自动柯里化和 "函数优先,数据最后" 的 API 设计理念一起,使得 Ramda 能够非常简单地进行这种风格的函数式组合。

但 Ramda 中柯里化的实现细节是另一篇文章的事情(更新:该文章已经发布了:Favoring Curry)。同时,Hugh Jackson 的这篇文章也绝对值得一读:为什么柯里化有帮助

但是,这些东西真能工作吗?

这是我们一直讨论的代码的 JSFiddle 示例:

这段优雅的代码清楚的表明了使用 Ramda 的好处。

使用 Ramda

可以参考 Ramda 非常不错的文档

Ramda 代码本身非常有用,上面提到的技术也非常有帮助。你可以从 Github 仓库 获取代码,或 通过 npm 安装 Ramda

在 Node 中使用:

js
npm install ramda
 var R = require('ramda')

在浏览器中使用,只需包含下列代码:

js
<script src="path/to/yourCopyOf/ramda.js"></script>

或者

js
<script src="path/to/yourCopyOf/ramda.min.js"></script>

我们会尽快将其放到 CDN 上。

如果你有任何建议,欢迎随时跟我们联系

- + \ No newline at end of file diff --git a/guide/ch1.html b/guide/ch1.html index 105196e..fdf1768 100644 --- a/guide/ch1.html +++ b/guide/ch1.html @@ -13,7 +13,7 @@ - + @@ -71,7 +71,7 @@ // 再应用分配律 multiply(flock_b, add(flock_a, flock_a));

漂亮!除了调用的函数,一点多余的代码都不需要写。当然这里我们定义 addmultiply 是为了代码完整性,实际上并不必要——在调用之前它们肯定已经在某个类库里定义好了。

你可能在想“你也太偷换概念了吧,居然举一个这么数学的例子”,或者“真实世界的应用程序比这复杂太多,不能这么简单地推理”。我之所以选择这样一个例子,是因为大多数人都知道加法和乘法,所以很容易就能理解数学可以如何为我们所用。

不要感到绝望,本书后面还会穿插一些范畴学(category theory)、集合论(set theory)以及 lambda 运算的知识,教你写更加复杂的代码,而且一点也不输本章这个海鸥程序的简洁性和准确性。你也不需要成为一个数学家,本书要教给你的编程范式实践起来就像是使用一个普通的框架或者 api 一样。

你也许会惊讶,我们可以像上例那样遵循函数式的范式去书写完整的、日常的应用程序,有着优异性能的程序,简洁且易推理的程序,以及不用每次都重新造轮子的程序。如果你是罪犯,那违法对你来说是好事;但在本书中,我们希望能够承认并遵守数学之法。

我们希望去践行每一部分都能完美接合的理论,希望能以一种通用的、可组合的组件来表示我们的特定问题,然后利用这些组件的特性来解决这些问题。相比命令式(稍后本书将会介绍命令式的精确定义,暂时我们还是先把重点放在函数式上)编程的那种“某某去做某事”的方式,函数式编程将会有更多的约束,不过你会震惊于这种强约束、数学性的“框架”所带来的回报。

我们已经看到函数式的点点星光了,但在真正开始我们的旅程之前,我们要先掌握一些具体的概念。

第 2 章:一等公民的函数

- + \ No newline at end of file diff --git a/guide/ch10.html b/guide/ch10.html index 36b48e8..8cc196b 100644 --- a/guide/ch10.html +++ b/guide/ch10.html @@ -13,7 +13,7 @@ - + @@ -191,7 +191,7 @@ }, 300); }); } - + \ No newline at end of file diff --git a/guide/ch11.html b/guide/ch11.html index a9676f9..fe9f02a 100644 --- a/guide/ch11.html +++ b/guide/ch11.html @@ -13,7 +13,7 @@ - + @@ -105,7 +105,7 @@ // listToStr :: [Char] -> String const listToStr = undefined - + \ No newline at end of file diff --git a/guide/ch12.html b/guide/ch12.html index 008ee65..98f5b2c 100644 --- a/guide/ch12.html +++ b/guide/ch12.html @@ -13,7 +13,7 @@ - + @@ -116,7 +116,7 @@ player.name ? Either.of(player) : left('must have name');

使用 traversable 和 validate 函数,更新 startGame (和它的类型签名),使得只有在所有玩家是有效时才开始游戏。

js
// startGame :: [Player] -> [Either Error String]
 const startGame = compose(map(map(always('game started!'))), map(validate));

最终,我们考虑一些文件系统相关的帮助函数:

js
// readfile :: String -> String -> Task Error String
 // readdir :: String -> Task Error [String]
- + \ No newline at end of file diff --git a/guide/ch13.html b/guide/ch13.html index b7fad42..cc8dda4 100644 --- a/guide/ch13.html +++ b/guide/ch13.html @@ -13,7 +13,7 @@ - + @@ -138,7 +138,7 @@ map(([f, x]) => f(x)), concat, )

总结

看,一切都是相互联系,或者只是暂时没有互相联系。这个深刻的认识让 Monoid 成为上到大型应用架构下到小块数据的建模工具。当你在应用中遇到累加或者 combination 时,多尝试通过 Monoid 来思考,一旦你做到这点,再拓展到更多的应用,你会惊喜于 Monoid 能建模的东西之多。

Exercises

- + \ No newline at end of file diff --git a/guide/ch2.html b/guide/ch2.html index 249faac..0765012 100644 --- a/guide/ch2.html +++ b/guide/ch2.html @@ -13,7 +13,7 @@ - + @@ -60,7 +60,7 @@ // 好一点点 fs.readFile('freaky_friday.txt', Db.save.bind(Db));

把 Db 绑定(bind)到它自己身上以后,你就可以随心所欲地调用它的原型链式垃圾代码了。this 就像一块脏尿布,我尽可能地避免使用它,因为在函数式编程中根本用不到它。然而,在使用其他的类库时,你却不得不向这个疯狂的世界低头。

也有人反驳说 this 能提高执行速度。如果你是这种对速度吹毛求疵的人,那你还是合上这本书吧。要是没法退货退款,也许你可以去换一本更入门的书来读。

至此,我们才准备好继续后面的章节。

第 3 章: 纯函数的好处

- + \ No newline at end of file diff --git a/guide/ch3.html b/guide/ch3.html index 8fa5210..608cbe9 100644 --- a/guide/ch3.html +++ b/guide/ch3.html @@ -13,7 +13,7 @@ - + @@ -152,7 +152,7 @@ };

如果再内联 decrementHP,我们会发现这种情况下,punch 变成了一个让 hp 的值减 1 的调用:

js
var punch = function(player, target) {
   return target.set("hp", target.hp-1);
 };

总之,等式推导带来的分析代码的能力对重构和理解代码非常重要。事实上,我们重构海鸥程序使用的正是这项技术:利用加和乘的特性。对这些技术的使用将会贯穿本书,真的。

并行代码

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

并行代码在服务端 js 环境以及使用了 web worker 的浏览器那里是非常容易实现的,因为它们使用了线程(thread)。不过出于对非纯函数复杂度的考虑,当前主流观点还是避免使用这种并行。

总结

我们已经了解什么是纯函数了,也看到作为函数式程序员的我们,为何深信纯函数是不同凡响的。从这开始,我们将尽力以纯函数式的方式书写所有的函数。为此我们将需要一些额外的工具来达成目标,同时也尽量把非纯函数从纯函数代码中分离。

如果手头没有一些工具,那么纯函数程序写起来就有点费力。我们不得不玩杂耍似的通过到处传递参数来操作数据,而且还被禁止使用状态,更别说“作用”了。没有人愿意这样自虐。所以让我们来学习一个叫 curry 的新工具。

第 4 章: 柯里化(curry)

- + \ No newline at end of file diff --git a/guide/ch4.html b/guide/ch4.html index 52a74ed..3e89a8e 100644 --- a/guide/ch4.html +++ b/guide/ch4.html @@ -13,7 +13,7 @@ - + @@ -137,7 +137,7 @@ // ============ // 借助 `slice` 定义一个 `take` curry 函数,该函数调用后可以取出字符串的前 n 个字符。 var take = undefined; - + \ No newline at end of file diff --git a/guide/ch5.html b/guide/ch5.html index e7a5e94..c8e79a4 100644 --- a/guide/ch5.html +++ b/guide/ch5.html @@ -13,7 +13,7 @@ - + @@ -174,7 +174,7 @@ var fastest = _.last(sorted); return fastest.name + ' is the fastest'; }; - + \ No newline at end of file diff --git a/guide/ch6.html b/guide/ch6.html index 2955aa6..794be2c 100644 --- a/guide/ch6.html +++ b/guide/ch6.html @@ -13,7 +13,7 @@ - + @@ -144,7 +144,7 @@ var mediaToImg = _.compose(img, mediaUrl); var images = _.compose(_.map(mediaToImg), _.prop('items'));

总结

我们已经见识到如何在一个小而不失真实的应用中运用新技能了,也已经使用过函数式这个“数学框架”来推导和重构代码了。但是异常处理以及代码分支呢?如何让整个应用都是函数式的,而不仅仅是把破坏性的函数放到命名空间下?如何让应用更安全更富有表现力?这些都是本书第 2 部分将要解决的问题。

第 7 章: Hindley-Milner 类型签名

- + \ No newline at end of file diff --git a/guide/ch7.html b/guide/ch7.html index ce279d2..fc1bf1a 100644 --- a/guide/ch7.html +++ b/guide/ch7.html @@ -13,7 +13,7 @@ - + @@ -73,7 +73,7 @@ // filter :: (a -> Bool) -> [a] -> [a] compose(map(f), filter(compose(p, f))) == compose(filter(p), map(f));

不用写一行代码你也能理解这些定理,它们直接来自于类型本身。第一个例子中,等式左边说的是,先获取数组的头部(译者注:即第一个元素),然后对它调用函数 f;等式右边说的是,先对数组中的每一个元素调用 f,然后再取其返回结果的头部。这两个表达式的作用是相等的,但是前者要快得多。

你可能会想,这不是常识么。但根据我的调查,计算机是没有常识的。实际上,计算机必须要有一种形式化方法来自动进行类似的代码优化。数学提供了这种方法,能够形式化直观的感觉,这无疑对死板的计算机逻辑非常有用。

第二个例子 filter 也是一样。等式左边是说,先组合 fp 检查哪些元素要过滤掉,然后再通过 map 实际调用 f(别忘了 filter 是不会改变数组中元素的,这就保证了 a 将保持不变);等式右边是说,先用 map 调用 f,然后再根据 p 过滤元素。这两者也是相等的。

以上只是两个例子,但它们传达的定理却是普适的,可以应用到所有的多态性类型签名上。在 JavaScript 中,你可以借助一些工具来声明重写规则,也可以直接使用 compose 函数来定义重写规则。总之,这么做的好处是显而易见且唾手可得的,可能性则是无限的。

类型约束

最后要注意的一点是,签名也可以把类型约束为一个特定的接口(interface)。

js
// sort :: Ord a => [a] -> [a]

胖箭头左边表明的是这样一个事实:a 一定是个 Ord 对象。也就是说,a 必须要实现 Ord 接口。Ord 到底是什么?它是从哪来的?在一门强类型语言中,它可能就是一个自定义的接口,能够让不同的值排序。通过这种方式,我们不仅能够获取关于 a 的更多信息,了解 sort 函数具体要干什么,而且还能限制函数的作用范围。我们把这种接口声明叫做类型约束(type constraints)。

js
// assertEqual :: (Eq a, Show a) => a -> a -> Assertion

这个例子中有两个约束:EqShow。它们保证了我们可以检查不同的 a 是否相等,并在有不相等的情况下打印出其中的差异。

我们将会在后面的章节中看到更多类型约束的例子,其含义也会更加清晰。

总结

Hindley-Milner 类型签名在函数式编程中无处不在,它们简单易读,写起来也不复杂。但仅仅凭签名就能理解整个程序还是有一定难度的,要想精通这个技能就更需要花点时间了。从这开始,我们将给每一行代码都加上类型签名。

第 8 章: 特百惠

- + \ No newline at end of file diff --git a/guide/ch8.html b/guide/ch8.html index 3220182..a3c07f9 100644 --- a/guide/ch8.html +++ b/guide/ch8.html @@ -13,7 +13,7 @@ - + @@ -473,7 +473,7 @@ } var ex8 = undefined - + \ No newline at end of file diff --git a/guide/ch9.html b/guide/ch9.html index 47b4fa5..b5dd8c1 100644 --- a/guide/ch9.html +++ b/guide/ch9.html @@ -13,7 +13,7 @@ - + @@ -308,7 +308,7 @@ // ex4 :: Email -> Either String (IO String) var ex4 = undefined; - + \ No newline at end of file diff --git a/guide/code/part1_exercises/README.html b/guide/code/part1_exercises/README.html index 3c3add2..9e78738 100644 --- a/guide/code/part1_exercises/README.html +++ b/guide/code/part1_exercises/README.html @@ -13,14 +13,14 @@ - +
- + \ No newline at end of file diff --git a/guide/code/part2_exercises/README.html b/guide/code/part2_exercises/README.html index b999a1c..e8fb93d 100644 --- a/guide/code/part2_exercises/README.html +++ b/guide/code/part2_exercises/README.html @@ -13,14 +13,14 @@ - +
- + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json index b2dca1f..c0aee23 100644 --- a/hashmap.json +++ b/hashmap.json @@ -1 +1 @@ -{"guide_ch2.md":"Bg06iC56","guide_thinking-in-ramda-lenses.md":"ChADnMO1","guide_thinking-in-ramda-immutability-and-arrays.md":"C7kI_L3f","guide_readme.md":"1XFwN-aA","guide_introducing-ramda.md":"WyVWaTR7","readme.md":"C6fsobry","guide_ch10.md":"_h1UC9iD","guide_ch3.md":"BXNoU9FZ","guide_thinking-in-ramda-partial-application.md":"LTgpREKb","guide_thinking-in-ramda-combining-functions.md":"Ca3wlZFq","guide_ch7.md":"BIdQTJxx","guide_ch12.md":"Va0hWDYa","guide_ch5.md":"fYd1dShy","guide_thinking-in-ramda-pointfree-style.md":"Ccq6YosK","guide_thinking-in-ramda-wrap-up.md":"Dh6gHguE","guide_code_part1_exercises_readme.md":"ttEVVHmg","guide_code_part2_exercises_readme.md":"BwgftO5r","guide_ch8.md":"C-2i1vI1","guide_thinking-in-ramda-immutability-and-objects.md":"CAns9AmC","guide_ch6.md":"DDYNLJwy","guide_ch1.md":"DX9XMxnd","guide_why-ramda.md":"DO1Mgvz2","guide_ch11.md":"BmzeWLXK","guide_ch9.md":"J45MrHsq","guide_thinking-in-ramda-declarative-programming.md":"DdWX3Gtr","index.md":"C8lrHU00","guide_ch4.md":"CQRQiEwQ","guide_ch13.md":"4Kq6qljd","guide_favoring-curry.md":"DX2Hk3dJ","guide_why-curry-helps.md":"CxR0uhwc","guide_thinking-in-ramda-getting-started.md":"DrgYNWZU"} +{"guide_thinking-in-ramda-partial-application.md":"LTgpREKb","guide_thinking-in-ramda-lenses.md":"ChADnMO1","guide_thinking-in-ramda-wrap-up.md":"Dh6gHguE","guide_ch2.md":"Bg06iC56","index.md":"C8lrHU00","guide_thinking-in-ramda-declarative-programming.md":"DdWX3Gtr","guide_ch1.md":"DX9XMxnd","guide_why-ramda.md":"DO1Mgvz2","guide_ch9.md":"J45MrHsq","guide_thinking-in-ramda-pointfree-style.md":"Ccq6YosK","guide_favoring-curry.md":"DX2Hk3dJ","guide_ch4.md":"CQRQiEwQ","guide_ch12.md":"Va0hWDYa","guide_thinking-in-ramda-combining-functions.md":"Ca3wlZFq","guide_thinking-in-ramda-immutability-and-arrays.md":"C7kI_L3f","readme.md":"C6fsobry","guide_why-curry-helps.md":"CxR0uhwc","guide_ch3.md":"BXNoU9FZ","guide_ch11.md":"BmzeWLXK","guide_readme.md":"1XFwN-aA","guide_introducing-ramda.md":"WyVWaTR7","guide_thinking-in-ramda-getting-started.md":"DrgYNWZU","guide_ch7.md":"BIdQTJxx","guide_ch8.md":"C-2i1vI1","guide_code_part1_exercises_readme.md":"ttEVVHmg","guide_ch10.md":"_h1UC9iD","guide_thinking-in-ramda-immutability-and-objects.md":"CAns9AmC","guide_ch13.md":"4Kq6qljd","guide_code_part2_exercises_readme.md":"BwgftO5r","guide_ch5.md":"fYd1dShy","guide_ch6.md":"DDYNLJwy"} diff --git a/index.html b/index.html index b0e4572..4b825c4 100644 --- a/index.html +++ b/index.html @@ -13,13 +13,13 @@ - +
Skip to content

函数式编程指南

Functional Programming

强大、简洁、优雅

Released under the MIT License.

- + \ No newline at end of file