From e6f00d73d961ec294b4c4f4d8c030fd0bcc490c9 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 4 Jul 2019 20:23:41 +0800 Subject: [PATCH 01/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67210b5d..17d5dd88 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Functional-Light JavaScript +# 多彩迷幻的 JavaScript [![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC%20BY--NC--ND%204.0-blue.svg)](http://creativecommons.org/licenses/by-nc-nd/4.0/) From 6fba13397ba353dc53c7b0a37e469ed3679b80c7 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 4 Jul 2019 20:27:48 +0800 Subject: [PATCH 02/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 17d5dd88..93f7cee9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Book Cover -This book is a balanced, pragmatic look at FP in JavaScript. The first edition is now complete. Read here online **for free**, or: +这是一本比较相对中稳,务实的去看待JS的函数式编程基础的书籍。第一版现在已完成。可免费在线阅读, or:

Buy on Leanpub Buy on Manning Buy on Amazon From 5c42e941cc072d63e34a7b9f1d4076de55dfc90b Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 4 Jul 2019 21:24:53 +0800 Subject: [PATCH 03/63] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 93f7cee9..0c02098a 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,17 @@ Book Cover -这是一本比较相对中稳,务实的去看待JS的函数式编程基础的书籍。第一版现在已完成。可免费在线阅读, or: +这是一本比较相对中稳,务实的去看待JS的函数式编程基础的书籍。第一版现在已完成。可免费在线阅读, 如果喜欢书籍可从以下渠道购买:

Buy on Leanpub Buy on Manning Buy on Amazon

-"Functional-Light JavaScript" explores the core principles of functional programming (FP) as they are applied to JavaScript. But what makes this book different is that we approach these principles without drowning in all the heavy terminology. We look at a subset of FP foundational concepts that I call "Functional-Light Programming" (FLP) and apply it to JavaScript. +本书主要探讨了应用于javascript的JS的函数式编程的核心原则。但让这本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为“多彩迷幻的 JavaScript”的函数式编程基础,并将它应用到javascript中去。 -**Note:** Despite the word "Light" in the title, I do not consider or recommend this book as a "beginner", "easy", or "intro" book on the topic. This book is rigorous and full of gritty detail; it expects a solid foundation of JS knowledge before diving in. "Light" means limited in scope; instead of being more broad, this book goes much deeper into each topic than you typically find in other FP-JavaScript books. +**注意:** 尽管书名中含有“光”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“灯”的意思是范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 -Let's face it: unless you're already a member of the FP cool kids club (I'm not!), a statement like, "a monad is just a monoid in the category of endofunctors", just doesn't mean anything useful to us. +让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中酷孩子俱乐部的成员(可惜我不是!)。 That's not to say the terms are meaning*less* or that FPrs are bad for using them. Once you graduate from Functional-Light, you'll maybe/hopefully want to study FP more formally, and you'll certainly have plenty of exposure to what they mean and why. From 22bbc79d7b11767c5b46cbd0c66ab42e772f09a5 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 4 Jul 2019 21:25:39 +0800 Subject: [PATCH 04/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c02098a..3e018e3f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Buy on Leanpub Buy on Manning Buy on Amazon

-本书主要探讨了应用于javascript的JS的函数式编程的核心原则。但让这本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为“多彩迷幻的 JavaScript”的函数式编程基础,并将它应用到javascript中去。 +本书主要探讨了应用于javascript的函数式编程的核心原则。但本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为“多彩迷幻的 JavaScript”的函数式编程基础,并将它应用到javascript中去。 **注意:** 尽管书名中含有“光”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“灯”的意思是范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 From 70d588e93dfef3414e23e8de34c5cefc3953efd9 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 4 Jul 2019 21:31:42 +0800 Subject: [PATCH 05/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e018e3f..f905e3d8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 多彩迷幻的 JavaScript +# JavaScript中的轻量函数式编程 [![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC%20BY--NC--ND%204.0-blue.svg)](http://creativecommons.org/licenses/by-nc-nd/4.0/) From de0c9f789107f7529070b82cd8912e0f64b0f924 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 14:18:27 +0800 Subject: [PATCH 06/63] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f905e3d8..a7be15cb 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ 本书主要探讨了应用于javascript的函数式编程的核心原则。但本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为“多彩迷幻的 JavaScript”的函数式编程基础,并将它应用到javascript中去。 -**注意:** 尽管书名中含有“光”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“灯”的意思是范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 +**注意:** 尽管书名中含有“轻”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“灯”的意思是范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 -让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中酷孩子俱乐部的成员(可惜我不是!)。 +让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中佼佼者(可惜我不是!)。 That's not to say the terms are meaning*less* or that FPrs are bad for using them. Once you graduate from Functional-Light, you'll maybe/hopefully want to study FP more formally, and you'll certainly have plenty of exposure to what they mean and why. From 94c76913ebe206a241de835f952b57c1752a113c Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 14:52:57 +0800 Subject: [PATCH 07/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7be15cb..e0e41ce0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ 让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中佼佼者(可惜我不是!)。 -That's not to say the terms are meaning*less* or that FPrs are bad for using them. Once you graduate from Functional-Light, you'll maybe/hopefully want to study FP more formally, and you'll certainly have plenty of exposure to what they mean and why. +上面这并不是说这些术语没有意义,或者说函数式程序设计的理念不适用于他们。一旦你掌握了JavaScript中的轻量函数式编程,你可能会希望能更正式地学习函数式的程序设计(FP),你肯定会接触到它们的含义和其中的原因。 But I want you to be able to apply some of the fundamentals of FP to your JavaScript *now*, because I believe it will help you write better, more *reason*able code. From d155040301de681e8a3c3681c061426e02f00bf3 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 14:55:01 +0800 Subject: [PATCH 08/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0e41ce0..5d3cddef 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ 上面这并不是说这些术语没有意义,或者说函数式程序设计的理念不适用于他们。一旦你掌握了JavaScript中的轻量函数式编程,你可能会希望能更正式地学习函数式的程序设计(FP),你肯定会接触到它们的含义和其中的原因。 -But I want you to be able to apply some of the fundamentals of FP to your JavaScript *now*, because I believe it will help you write better, more *reason*able code. +但我希望您现在能够将函数式程序设计(fp)的一些基本原理应用到您的javascript中,因为我相信它将帮助您编写更好、更合理的代码。 **To read more about the motivations and perspective behind this book, check out the [Preface](manuscript/preface.md).** From 886d315c549948a907a08f8837b7204c8b5c6ea1 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 14:56:27 +0800 Subject: [PATCH 09/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d3cddef..f387e613 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ 但我希望您现在能够将函数式程序设计(fp)的一些基本原理应用到您的javascript中,因为我相信它将帮助您编写更好、更合理的代码。 -**To read more about the motivations and perspective behind this book, check out the [Preface](manuscript/preface.md).** +**要更多地了解这本书背后的动机和观点,请看[序言](manuscript/preface.md)。 ** ## Book From 03337cc9bdd648fd593401b90d6b21ed6dd3011b Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 14:57:29 +0800 Subject: [PATCH 10/63] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f387e613..2d08e8dc 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ **要更多地了解这本书背后的动机和观点,请看[序言](manuscript/preface.md)。 ** -## Book +## 书籍 -[Table of Contents](manuscript/README.md/#table-of-contents) +[目录](manuscript/README.md/#table-of-contents) * [Foreword](manuscript/foreword.md/#foreword) (by [Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) * [Preface](manuscript/preface.md/#preface) From ef882ae95cd976901d2fda247a636e0f0a983209 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 14:59:21 +0800 Subject: [PATCH 11/63] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d08e8dc..9c60d705 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ [目录](manuscript/README.md/#table-of-contents) -* [Foreword](manuscript/foreword.md/#foreword) (by [Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) -* [Preface](manuscript/preface.md/#preface) +* [序言](manuscript/foreword.md/#foreword) ([Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) +* [前言](manuscript/preface.md/#preface) * [Chapter 1: Why Functional Programming?](manuscript/ch1.md/#chapter-1-why-functional-programming) * [Chapter 2: The Nature Of Functions](manuscript/ch2.md/#chapter-2-the-nature-of-functions) * [Chapter 3: Managing Function Inputs](manuscript/ch3.md/#chapter-3-managing-function-inputs) From 7cc3e301b13055e9eb8472e7de448c4a96cb2144 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 15:00:44 +0800 Subject: [PATCH 12/63] Update README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9c60d705..321d3777 100644 --- a/README.md +++ b/README.md @@ -28,20 +28,20 @@ * [序言](manuscript/foreword.md/#foreword) ([Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) * [前言](manuscript/preface.md/#preface) -* [Chapter 1: Why Functional Programming?](manuscript/ch1.md/#chapter-1-why-functional-programming) -* [Chapter 2: The Nature Of Functions](manuscript/ch2.md/#chapter-2-the-nature-of-functions) -* [Chapter 3: Managing Function Inputs](manuscript/ch3.md/#chapter-3-managing-function-inputs) -* [Chapter 4: Composing Functions](manuscript/ch4.md/#chapter-4-composing-functions) -* [Chapter 5: Reducing Side Effects](manuscript/ch5.md/#chapter-5-reducing-side-effects) -* [Chapter 6: Value Immutability](manuscript/ch6.md/#chapter-6-value-immutability) -* [Chapter 7: Closure vs Object](manuscript/ch7.md/#chapter-7-closure-vs-object) -* [Chapter 8: Recursion](manuscript/ch8.md/#chapter-8-recursion) -* [Chapter 9: List Operations](manuscript/ch9.md/#chapter-9-list-operations) -* [Chapter 10: Functional Async](manuscript/ch10.md/#chapter-10-functional-async) -* [Chapter 11: Putting It All Together](manuscript/ch11.md/#chapter-11-putting-it-all-together) -* [Appendix A: Transducing](manuscript/apA.md/#appendix-a-transducing) -* [Appendix B: The Humble Monad](manuscript/apB.md/#appendix-b-the-humble-monad) -* [Appendix C: FP Libraries](manuscript/apC.md/#appendix-c-fp-libraries) +* [章节 1: Why Functional Programming?](manuscript/ch1.md/#chapter-1-why-functional-programming) +* [章节 2: The Nature Of Functions](manuscript/ch2.md/#chapter-2-the-nature-of-functions) +* [章节 3: Managing Function Inputs](manuscript/ch3.md/#chapter-3-managing-function-inputs) +* [章节 4: Composing Functions](manuscript/ch4.md/#chapter-4-composing-functions) +* [章节 5: Reducing Side Effects](manuscript/ch5.md/#chapter-5-reducing-side-effects) +* [章节 6: Value Immutability](manuscript/ch6.md/#chapter-6-value-immutability) +* [章节 7: Closure vs Object](manuscript/ch7.md/#chapter-7-closure-vs-object) +* [章节 8: Recursion](manuscript/ch8.md/#chapter-8-recursion) +* [章节 9: List Operations](manuscript/ch9.md/#chapter-9-list-operations) +* [章节 10: Functional Async](manuscript/ch10.md/#chapter-10-functional-async) +* [章节 11: Putting It All Together](manuscript/ch11.md/#chapter-11-putting-it-all-together) +* [附言 A: Transducing](manuscript/apA.md/#appendix-a-transducing) +* [附言 B: The Humble Monad](manuscript/apB.md/#appendix-b-the-humble-monad) +* [附言 C: FP Libraries](manuscript/apC.md/#appendix-c-fp-libraries) ## Publishing From 110515b76f321cd5421eba307901ab82af134e81 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 15:47:32 +0800 Subject: [PATCH 13/63] Update README.md --- README.md | 57 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 321d3777..379c012d 100644 --- a/README.md +++ b/README.md @@ -28,50 +28,53 @@ * [序言](manuscript/foreword.md/#foreword) ([Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) * [前言](manuscript/preface.md/#preface) -* [章节 1: Why Functional Programming?](manuscript/ch1.md/#chapter-1-why-functional-programming) -* [章节 2: The Nature Of Functions](manuscript/ch2.md/#chapter-2-the-nature-of-functions) -* [章节 3: Managing Function Inputs](manuscript/ch3.md/#chapter-3-managing-function-inputs) -* [章节 4: Composing Functions](manuscript/ch4.md/#chapter-4-composing-functions) -* [章节 5: Reducing Side Effects](manuscript/ch5.md/#chapter-5-reducing-side-effects) -* [章节 6: Value Immutability](manuscript/ch6.md/#chapter-6-value-immutability) -* [章节 7: Closure vs Object](manuscript/ch7.md/#chapter-7-closure-vs-object) -* [章节 8: Recursion](manuscript/ch8.md/#chapter-8-recursion) -* [章节 9: List Operations](manuscript/ch9.md/#chapter-9-list-operations) -* [章节 10: Functional Async](manuscript/ch10.md/#chapter-10-functional-async) -* [章节 11: Putting It All Together](manuscript/ch11.md/#chapter-11-putting-it-all-together) -* [附言 A: Transducing](manuscript/apA.md/#appendix-a-transducing) -* [附言 B: The Humble Monad](manuscript/apB.md/#appendix-b-the-humble-monad) -* [附言 C: FP Libraries](manuscript/apC.md/#appendix-c-fp-libraries) - -## Publishing - -This book has been published and is now available for purchase (in both ebook and print formats) from these sources: +* [章节 1: 为什么要函数式编程?](manuscript/ch1.md/#chapter-1-why-functional-programming) +* [章节 2: 函数的性质](manuscript/ch2.md/#chapter-2-the-nature-of-functions) +* [章节 3: 管理函数输入](manuscript/ch3.md/#chapter-3-managing-function-inputs) +* [章节 4: 组合函数](manuscript/ch4.md/#chapter-4-composing-functions) +* [章节 5: 减少副作用影响](manuscript/ch5.md/#chapter-5-reducing-side-effects) +* [章节 6: 值的不变性质](manuscript/ch6.md/#chapter-6-value-immutability) +* [章节 7: 闭合与对象](manuscript/ch7.md/#chapter-7-closure-vs-object) +* [章节 8: 递归](manuscript/ch8.md/#chapter-8-recursion) +* [章节 9: 列表的操作](manuscript/ch9.md/#chapter-9-list-operations) +* [章节 10: 函数的异步](manuscript/ch10.md/#chapter-10-functional-async) +* [章节 11: 汇总](manuscript/ch11.md/#chapter-11-putting-it-all-together) +* [附言 A: 转换](manuscript/apA.md/#appendix-a-transducing) +* [附言 B: 卑微的单元](manuscript/apB.md/#appendix-b-the-humble-monad) +* [附言 C: 函数式编程的库](manuscript/apC.md/#appendix-c-fp-libraries) + +## 出版发行 + +这本书已经出版,现在可以从这些来源购买(电子书和印刷格式):

Buy on Leanpub Buy on Manning Buy on Amazon

-If you'd like additionally to contribute financially towards the effort (or any of my other OSS work) aside from purchasing the book, I do have a [patreon](https://www.patreon.com/getify) that I would always appreciate your generosity towards. +如果你想为购买这本书以外(或我的任何其他综合性的工作)提供额外的资金支持,我确实有一个[资助点](https://www.patreon.com/getify),我将永远感谢你的慷慨。 [![patreon.png](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/getify) -## In-person Training +## 面对面培训 -The content for this book derives heavily from a training workshop I teach professionally (in both public and private-corporate workshop format) of the same name. +这本书的内容很多都是来源于我所教专业一个培训讲习班,而且我所教的专业在公共和私营企业讲习班中都是在用这样的内容名字。 -If you like this content and would like to contact me regarding conducting training on this, or other various JS/HTML5/Node.js topics, please reach out to me through email: getify @ gmail -## Online Video Training +如果您喜欢此内容,并想与我联系进行此方面的培训,或其他各种js/html5/node.js主题训练,请通过 email: getify @ gmail -I also have several JS training courses available in on-demand video format. I [teach courses](https://FrontendMasters.com/teachers/kyle-simpson) through [Frontend Masters](https://FrontendMasters.com), like my [Functional-Light JavaScript v2](https://frontendmasters.com/courses/functional-javascript-v2/) workshop. Some of my courses are also available on [PluralSight](https://www.pluralsight.com/search?q=kyle%20simpson&categories=all). +## 在线视频培训 -## Contributions +我还提供了几个按需视频格式的JS培训课程. 我通过网站[FrontendMasters](https://FrontendMasters.com)进行[授课](https://FrontendMasters.com/teachers/kyle-simpson)的,比如我的轻量函数式编程 第二版的研讨会。你也能在[PluralSight](https://www.pluralsight.com/search?q=kyle%20simpson&categories=all)找到我的其他一些课程。 -Any contributions you make to this effort **are of course greatly appreciated**. +## 贡献 + +你为这项工作所做的任何贡献当然都会受到**莫大感激** 。 + +但**请**在提交项目前仔细阅读[投稿指南](CONTRIBUTING.md)。 But **PLEASE** read the [Contributions Guidelines](CONTRIBUTING.md) carefully before submitting a PR. -## License & Copyright +## 许可证和版权 The materials herein are all (c) 2016-2018 Kyle Simpson. From e446c4e0077b4ca8d287abfc06b2ef1df604c951 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 5 Jul 2019 16:09:40 +0800 Subject: [PATCH 14/63] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 379c012d..53506f63 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Buy on Leanpub Buy on Manning Buy on Amazon

-本书主要探讨了应用于javascript的函数式编程的核心原则。但本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为“多彩迷幻的 JavaScript”的函数式编程基础,并将它应用到javascript中去。 +本书主要探讨了应用于javascript的函数式编程的核心原则。但本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为函数式编程基础,并将它应用到javascript中去。 -**注意:** 尽管书名中含有“轻”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“灯”的意思是范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 +**注意:** 尽管书名中含有“轻”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“轻”可以理解为范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中佼佼者(可惜我不是!)。 From 5570432711ca7b5cb3eec1edeed2637a3f21b153 Mon Sep 17 00:00:00 2001 From: mon Date: Fri, 5 Jul 2019 23:26:35 +0800 Subject: [PATCH 15/63] Update apC.md --- manuscript/apC.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index 4eba3eba..a1005a67 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -1,23 +1,22 @@ -# Functional-Light JavaScript -# Appendix C: FP Libraries +# JavaScript中的轻量函数式编程 +# 附录C: 关于FP的库 +当你把这本书从头读到尾,请花点时间回看一下[Chapter 1](ch1.md). 这是一段很长的旅程. 我希望你已经学到了很多,并且把函数式的精髓深入到你的编程中去。 -If you've been reading this book from start to finish, take a minute to stop and look back how far you've come since [Chapter 1](ch1.md). It's been quite a journey. I hope you've learned a lot and gained insight into thinking functionally for your own programs. +我想把这本书合上来告诉你一些常用/流行的FP库的快速入门。接下来要介绍并不是一份详尽的文档,而是一个快速使你从轻量函数式进入更广泛的FP的入门介绍。 -I want to close this book leaving you with some quick pointers of working with common/popular FP libraries. This is not an exhaustive documentation on each, but a quick glance at the things you should be aware of as you venture beyond "Functional-Light" into broader FP. - -Wherever possible, I recommend you *not* reinvent any wheels. If you find an FP library that suits your needs, use it. Only use the ad hoc helper utilities from this book -- or invent ones of your own! -- if you can't find a suitable library method for your circumstance. +如果可能的话,我建议你 “不要” 再发明任何轮子。如果你找到一个符合你需要的FP库,就用它吧。-- 如果你实在找不到适合自己库,那么也使用本书中的示例程序 -- 或者你也可以自己发明一个。 ## Stuff to Investigate -Let's expand the [list of FP libraries to be aware of, from Chapter 1](ch1.md/#libraries). We won't cover all of these (as there's a lot of overlap), but here are the ones that should probably be on your radar screen: +让我们展开第1章 [list of FP libraries to be aware of, from Chapter 1](ch1.md/#libraries),我们不可能涵盖所有这些内容(可能有很多相似),但以下应该是你要关注的库: -* [Ramda](http://ramdajs.com): General FP Utilities -* [Sanctuary](https://github.com/sanctuary-js/sanctuary): Ramda Companion for FP Types -* [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide): General FP Utilities -* [functional.js](http://functionaljs.com/): General FP Utilities -* [Immutable](https://github.com/facebook/immutable-js): Immutable Data Structures -* [Mori](https://github.com/swannodette/mori): (ClojureScript Inspired) Immutable Data Structures -* [Seamless-Immutable](https://github.com/rtfeldman/seamless-immutable): Immutable Data Helpers +* [Ramda](http://ramdajs.com): 通用的FP工具库 +* [Sanctuary](https://github.com/sanctuary-js/sanctuary): 类似Ramda的FP工具库 +* [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide): lodash的FP工具库 +* [functional.js](http://functionaljs.com/): 通用的FP工具库 +* [Immutable](https://github.com/facebook/immutable-js): facebook官方的,一个关于不可变数据工具库 +* [Mori](https://github.com/swannodette/mori): 基于ClojureScript不可变数据工具库 +* [Seamless-Immutable](https://github.com/rtfeldman/seamless-immutable): 关于不可变数据的辅助函数 * [transducers-js](https://github.com/cognitect-labs/transducers-js): Transducers * [monet.js](https://github.com/monet/monet.js): Monadic Types From a17f4857cbcba51125ee917da6f76ab0214e2bd6 Mon Sep 17 00:00:00 2001 From: mon Date: Sat, 6 Jul 2019 12:27:21 +0800 Subject: [PATCH 16/63] Update apC.md --- manuscript/apC.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index a1005a67..d737afc2 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -1,38 +1,38 @@ -# JavaScript中的轻量函数式编程 -# 附录C: 关于FP的库 -当你把这本书从头读到尾,请花点时间回看一下[Chapter 1](ch1.md). 这是一段很长的旅程. 我希望你已经学到了很多,并且把函数式的精髓深入到你的编程中去。 +# JavaScript轻量级函数式编程 +# 附录C: 关于FP的库 +当你把这本书从头读到尾,请花点时间回看一下[Chapter 1](ch1.md). 这是一段很长的旅程. 我希望你已经学到了很多,并且把函数式的精髓深入到你的编程中去。 -我想把这本书合上来告诉你一些常用/流行的FP库的快速入门。接下来要介绍并不是一份详尽的文档,而是一个快速使你从轻量函数式进入更广泛的FP的入门介绍。 +我想把这本书合上来告诉你一些常用/流行的FP库的快速入门。接下来要介绍并不是一份详尽的文档,而是一个快速使你从轻量函数式进入更广泛的FP的入门介绍。 -如果可能的话,我建议你 “不要” 再发明任何轮子。如果你找到一个符合你需要的FP库,就用它吧。-- 如果你实在找不到适合自己库,那么也使用本书中的示例程序 -- 或者你也可以自己发明一个。 +如果可能的话,我建议你 “不要” 再发明任何轮子。如果你找到一个符合你需要的FP库,就用它吧。-- 如果你实在找不到适合自己库,那么也使用本书中的示例程序 -- 或者你也可以自己发明一个。 -## Stuff to Investigate +## 需关注的工具库(注:我的理解) -让我们展开第1章 [list of FP libraries to be aware of, from Chapter 1](ch1.md/#libraries),我们不可能涵盖所有这些内容(可能有很多相似),但以下应该是你要关注的库: +让我们展开第1章 [list of FP libraries to be aware of, from Chapter 1](ch1.md/#libraries),我们不可能涵盖所有这些内容(可能有很多相似),但以下应该是你要关注的库: * [Ramda](http://ramdajs.com): 通用的FP工具库 * [Sanctuary](https://github.com/sanctuary-js/sanctuary): 类似Ramda的FP工具库 * [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide): lodash的FP工具库 * [functional.js](http://functionaljs.com/): 通用的FP工具库 -* [Immutable](https://github.com/facebook/immutable-js): facebook官方的,一个关于不可变数据工具库 -* [Mori](https://github.com/swannodette/mori): 基于ClojureScript不可变数据工具库 +* [Immutable](https://github.com/facebook/immutable-js): facebook官方的,一个关于不可变数据的库 +* [Mori](https://github.com/swannodette/mori): 基于ClojureScript不可变数据的库 * [Seamless-Immutable](https://github.com/rtfeldman/seamless-immutable): 关于不可变数据的辅助函数 * [transducers-js](https://github.com/cognitect-labs/transducers-js): Transducers * [monet.js](https://github.com/monet/monet.js): Monadic Types -There are dozens of other fine libraries not on this list. Just because it's not on my list here doesn't mean it's not good, nor is this list a particular endorsement. It's just a quick glance at the landscape of FP-in-JavaScript. A much longer list of FP resources can be [found here](https://github.com/stoeffel/awesome-fp-js). +还有几十个其他优秀的库不在此列表中。仅仅因为它不在我的名单上并不意味着它不好,这里只是简单地浏览一下JavaScript中的FP。你可以[在这里](https://github.com/stoeffel/awesome-fp-js)找到更多关于FP的编程资源。 -One resource that's extremely important to the FP world -- it's not a library but more an encyclopedia! -- is [Fantasy Land](https://github.com/fantasyland/fantasy-land) (aka FL). +函数式编程世界中十分重要的学习资源,[Fantasy Land](https://github.com/fantasyland/fantasy-land) (aka FL) ,与其说它是一个库,更像是一本百科全书。 -This is definitely not light reading for the faint of heart. It's a complete detailed roadmap of all of FP as it's interpreted in JavaScript. FL has become a de facto standard for JavaScript FP libraries to adhere to, to ensure maximum interoperability. +Fantasy Land(FL) 不仅仅是一份为初学者准备的轻量级读物,更是一个完整而详细的 JavaScript 函数式编程路线图。为了确保最大的通用性,FL 已经成为 JavaScript 函数式编程库遵循的业内标准。 -Fantasy Land is pretty much the exact opposite of "Functional-Light". It's the full-on no-holds-barred approach to FP in JavaScript. That said, as you venture beyond this book, it's likely that FL will be on that road for you. I'd recommend you bookmark it, and go back to it after you've had at least six months of real-world practice with this book's concepts. +Fantasy Land (FL)与“轻量函数式编程”的概念几乎完全相反,它是 JavaScript 函数式编程世界中全面的毫无保留的一种诠释 。也就是说,当你的能力超越本书时,FL 将会成为你接下来前进的方向。我建议你将其保存在收藏夹,并在你使用本书的概念进行至少 6 个月的实战练习之后再回来。 -## Ramda (0.23.0) +## Ramda (0.23.0) (注:目前最新版本是0.26.1) -From the [Ramda documentation](http://ramdajs.com/): +来自 [Ramda documentation](http://ramdajs.com/): -> Ramda functions are automatically curried. +> Ramda函数是自动被柯里化的. > > The parameters to Ramda functions are arranged to make it convenient for currying. The data to be operated on is generally supplied last. From b3d9a159e7473097666b261f67522d01312f7ce5 Mon Sep 17 00:00:00 2001 From: mon Date: Sat, 6 Jul 2019 12:28:09 +0800 Subject: [PATCH 17/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53506f63..d07659f7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JavaScript中的轻量函数式编程 +# JavaScript轻量级函数式编程 [![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC%20BY--NC--ND%204.0-blue.svg)](http://creativecommons.org/licenses/by-nc-nd/4.0/) From 63c46b9ad35e43b7d59bc7dbc56152cc9ae697c8 Mon Sep 17 00:00:00 2001 From: mon Date: Sat, 6 Jul 2019 12:29:26 +0800 Subject: [PATCH 18/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d07659f7..54e34291 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ 让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中佼佼者(可惜我不是!)。 -上面这并不是说这些术语没有意义,或者说函数式程序设计的理念不适用于他们。一旦你掌握了JavaScript中的轻量函数式编程,你可能会希望能更正式地学习函数式的程序设计(FP),你肯定会接触到它们的含义和其中的原因。 +上面这并不是说这些术语没有意义,或者说函数式程序设计的理念不适用于他们。一旦你掌握了JavaScript轻量级函数式编程,你可能会希望能更正式地学习函数式的程序设计(FP),你肯定会接触到它们的含义和其中的原因。 但我希望您现在能够将函数式程序设计(fp)的一些基本原理应用到您的javascript中,因为我相信它将帮助您编写更好、更合理的代码。 From 6820cd9f0e333d77ac24d51d8b2c880fb8760880 Mon Sep 17 00:00:00 2001 From: mon Date: Sat, 6 Jul 2019 23:38:34 +0800 Subject: [PATCH 19/63] Update apC.md --- manuscript/apC.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index d737afc2..a6499593 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -34,11 +34,11 @@ Fantasy Land (FL)与“轻量函数式编程”的概念几乎完全相反 > Ramda函数是自动被柯里化的. > -> The parameters to Ramda functions are arranged to make it convenient for currying. The data to be operated on is generally supplied last. +> Ramda函数的参数进行了优化,使其便于柯里化。要操作的数据通常是最后提供的。 -I find that design decision to be one of Ramda's strengths. It's also important to note that Ramda's form of currying (as with most libraries, it seems) is the ["loose currying" we talked about in Chapter 3](ch3.md/#user-content-loosecurry). +我发现合理设计是Ramda的优势之一。还需要注意的是,Ramda的柯里化形式(似乎与大多数库一样)是 [我们第3章讨论的“松散柯里化”](ch3.md/#user-content-loosecurry)。 -The [final example of Chapter 3](ch3.md/#user-content-finalshortlong) -- recall defining a point-free `printIf(..)` utility -- can be done with Ramda like this: +回想一下,[在第3章最后的示例](ch3.md/#user-content-finalshortlong),我们定义一个无点函数`printif(..) ` -- 在Ramda中可以这样定义: ```js function output(msg) { @@ -63,23 +63,23 @@ printIf( isLongEnough, msg1 ); printIf( isLongEnough, msg2 ); // Hello World ``` -A few differences to point out compared to [Chapter 3's approach](ch3.md/#user-content-finalshortlong): +与第3章实现有点不同的是 [Chapter 3's approach](ch3.md/#user-content-finalshortlong): -* We use `R.complement(..)` instead of `not(..)` to create a negating function `isLongEnough(..)` around `isShortEnough(..)`. +* 我们使用 `R.complement(..)` 代替 `not(..)` 基于`isShortEnough(..)` 创建一个相反的否定函数 `isLongEnough(..)` 。 -* We use `R.flip(..)` instead of `reverseArgs(..)`. It's important to note that `R.flip(..)` only swaps the first two arguments, whereas `reverseArgs(..)` reverses all of them. In this case, `flip(..)` is more convenient for us, so we don't need to do `partialRight(..)` or any of that kind of juggling. +* 我们使用 `R.flip(..)` 代替 `reverseArgs(..)`。需要注意的是,`R.flip(..)`只交换前两个参数,而`recseArgs(..)`则反转所有参数。在这种情况下,`flip(..)`对我们来说更方便,所以我们不需要使用`ParalRight(..)`或其他的方法。 -* `R.partial(..)` takes all of its subsequent arguments (beyond the function) as a single array. +* `R.partial(..)`将其所有后续参数(函数以外)作为单个数组传入。 -* Because Ramda is using loose currying, we don't need to use `R.uncurryN(..)` to get a `printIf(..)` that takes both its arguments. If we did, it would look like `R.uncurryN( 2, .. )` wrapped around the `R.partial(..)` call; but that's not necessary. +* 由于Ramda 使用松散柯里化,因此我们不需要使用 `R.uncurryN(..)` 来取得一个所有参数的 `printIf(..)` 方法。如果我们这样做了,它看起来就像用 `R.uncurryN( 2,.. )` 包装了 `R.partial(..)` 来调用,这看似是没必要的。 -Ramda is a very popular and powerful library. It's a really good place to start if you're practicing adding FP to your code base. +Ramda 是一个非常受欢迎和强大的函数库。如果您正在尝试将FP添加到您的代码库中,那么这是一个非常好的开始。 ## Lodash/fp (4.17.4) -Lodash is one of the most popular libraries in the entire JS ecosystem. They publish an "FP-friendly" version of their API as ["lodash/fp"](https://github.com/lodash/lodash/wiki/FP-Guide). +Lodash 是整个JS生态系统中最受欢迎的函数库。Lodash团队也发布了一个 "FP-friendly" API版本 -- ["lodash/fp"](https://github.com/lodash/lodash/wiki/FP-Guide)。 -In [Chapter 9, we looked at composing standalone list operations](ch9.md/#composing-standalone-utilities) (`map(..)`, `filter(..)`, and `reduce(..)`). Here's how we could do it with "lodash/fp": +在 [第9章,我们探讨了合并独立列表操作](ch9.md/#composing-standalone-utilities) (`map(..)`, `filter(..)`, 和 `reduce(..)`)。在"lodash/fp"中,我们可以这么做 : ```js var sum = (x,y) => x + y; @@ -94,7 +94,7 @@ fp.compose( [ ( [1,2,3,4,5] ); // 18 ``` -Instead of the more familiar `_.` namespace prefix, "lodash/fp" defines its methods with `fp.` as the namespace prefix. I find that a helpful distinguisher, and also generally more easy on my eyes than `_.` anyway! +与我们熟悉的 `_.` 命名空间前缀不同,“lodash/fp”以 `fp.` 作为命名空间前缀定义其方法。我觉得这是个很有帮助的区别,而且比 `_.` 更容易理解! Notice that `fp.compose(..)` (also known as `_.flowRight(..)` in lodash proper) takes an array of functions instead of individual arguments. From d5121c5f91ab1588489112f86d60ec26e386b696 Mon Sep 17 00:00:00 2001 From: mon Date: Sun, 7 Jul 2019 10:29:59 +0800 Subject: [PATCH 20/63] Update apC.md --- manuscript/apC.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index a6499593..7910b560 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -22,7 +22,7 @@ 还有几十个其他优秀的库不在此列表中。仅仅因为它不在我的名单上并不意味着它不好,这里只是简单地浏览一下JavaScript中的FP。你可以[在这里](https://github.com/stoeffel/awesome-fp-js)找到更多关于FP的编程资源。 -函数式编程世界中十分重要的学习资源,[Fantasy Land](https://github.com/fantasyland/fantasy-land) (aka FL) ,与其说它是一个库,更像是一本百科全书。 +函数式编程世界中十分重要的学习资源,[Fantasy Land](https://github.com/fantasyland/fantasy-land) (简称 FL) ,与其说它是一个库,更像是一本百科全书。 Fantasy Land(FL) 不仅仅是一份为初学者准备的轻量级读物,更是一个完整而详细的 JavaScript 函数式编程路线图。为了确保最大的通用性,FL 已经成为 JavaScript 函数式编程库遵循的业内标准。 @@ -34,7 +34,7 @@ Fantasy Land (FL)与“轻量函数式编程”的概念几乎完全相反 > Ramda函数是自动被柯里化的. > -> Ramda函数的参数进行了优化,使其便于柯里化。要操作的数据通常是最后提供的。 +> Ramda函数的参数进行了优化,使其便于柯里化。要操作的数据通常跟在最后。 我发现合理设计是Ramda的优势之一。还需要注意的是,Ramda的柯里化形式(似乎与大多数库一样)是 [我们第3章讨论的“松散柯里化”](ch3.md/#user-content-loosecurry)。 @@ -65,7 +65,7 @@ printIf( isLongEnough, msg2 ); // Hello World 与第3章实现有点不同的是 [Chapter 3's approach](ch3.md/#user-content-finalshortlong): -* 我们使用 `R.complement(..)` 代替 `not(..)` 基于`isShortEnough(..)` 创建一个相反的否定函数 `isLongEnough(..)` 。 +* 我们使用 `R.complement(..)` 代替 `not(..)` 基于`isShortEnough(..)` 创建一个相反函数 `isLongEnough(..)` 。 * 我们使用 `R.flip(..)` 代替 `reverseArgs(..)`。需要注意的是,`R.flip(..)`只交换前两个参数,而`recseArgs(..)`则反转所有参数。在这种情况下,`flip(..)`对我们来说更方便,所以我们不需要使用`ParalRight(..)`或其他的方法。 From 0a71633ded890ddace219645459d204c5313a3b4 Mon Sep 17 00:00:00 2001 From: Siming Date: Sun, 7 Jul 2019 23:18:01 +0800 Subject: [PATCH 21/63] =?UTF-8?q?Update=20and=20rename=20ch1.md=20to=20?= =?UTF-8?q?=E7=AB=A0=E8=8A=821.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\347\253\240\350\212\2021.md" | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) rename manuscript/ch1.md => "manuscript/\347\253\240\350\212\2021.md" (71%) diff --git a/manuscript/ch1.md "b/manuscript/\347\253\240\350\212\2021.md" similarity index 71% rename from manuscript/ch1.md rename to "manuscript/\347\253\240\350\212\2021.md" index a1d517c3..0332b70a 100644 --- a/manuscript/ch1.md +++ "b/manuscript/\347\253\240\350\212\2021.md" @@ -1,21 +1,21 @@ -# Functional-Light JavaScript -# Chapter 1: Why Functional Programming? -> Functional programmer: (noun) One who names variables "x", names functions "f", and names code patterns "zygohistomorphic prepromorphism" +# 章节1:为什么要函数式编程? + +> 函数编程人员:(名词)将变量命名为“x”,将函数命名为“f”,并将代码模式命名为“zygohistomorphic prepromorphism”。 > > James Iry @jamesiry 5/13/15 > > https://twitter.com/jamesiry/status/598547781515485184 -Functional Programming (FP) is not a new concept by any means. It's been around almost the entire history of programming. However, and I'm not sure it's fair to say, but... it sure hasn't seemed like as mainstream of a concept in the overall developer world until perhaps the last few years. I think FP has more been the realm of academics. +函数式编程(FP)并不是一个新概念。但它几乎贯穿了整个编程历史。不过,我不确定这样说是否合理,但是…直到最近几年,函数式编程才成为整个开发界的主流观念。所以我觉得函数式编程更像是学者的领域。 -That's all changing, though. A groundswell of interest is growing around FP, not just at the languages level but even in libraries and frameworks. You very well might be reading this text because you've finally realized FP is something you can't ignore any longer. Or maybe you're like me and you've tried to learn FP many times before but struggled to wade through all the terms or mathematical notation. +然而一切都在改变。不仅仅是在语言层面,甚至是在库和框架方面对函数式编程的兴趣正在与日俱增。你很可能读到了这篇文章,因为你最后也会意识到函数式编程是一个你不能再忽视的东西。或者你像我一样,你以前学过很多次函数式编程,但是很难理解其中所有的术语或数学符号。 -This first chapter's purpose is to answer questions like "Why should I use FP style with my code?" and "How does Functional-Light JavaScript compare to what others say about FP?" After we've laid that groundwork, throughout the rest of the book we'll uncover, piece by piece, the techniques and patterns for writing JS in Functional-Light style. +第一章的目的是回答诸如“为什么要在代码中使用函数式编程的风格”之类的问题。“JavaScript轻量级函数式编程“与其他人对函数式编程的看法相比如何?”在我们做好基础准备之后,通过在本书的其余部分中,我们将一块一块地揭示以轻量级风格编写JS的技术和模式。 -## At a Glance +## 快照 -Let's briefly illustrate the notion of "Functional-Light JavaScript" with a before-and-after snapshot of code. Consider: +让我们用代码的前后快照简要说明“JavaScript轻量级函数式编程”的概念。思考下列代码: ```js var numbers = [4,10,0,27,42,17,15,-6,58]; @@ -48,7 +48,7 @@ function outputMsg() { } ``` -Now consider a very different style that accomplishes exactly the same outcome: +现在考虑一种完全不同的风格,它可以实现相同的结果: ```js var sumOnlyFavorites = FP.compose( [ @@ -72,45 +72,45 @@ function sum(x,y) { return x + y; } function constructMsg(v) { return `The magic number is: ${v}`; } ``` -Once you understand FP and Functional-Light, this is likely how you'd *read* and mentally process that second snippet: +一旦你理解了函数式编程和轻量函数,第二个代码片段很可能是你阅读并脑中想着处理的方式: -> We're first creating a function called `sumOnlyFavorites(..)` that's a combination of three other functions. We combine two filters, one checking if a value is greater-than-or-equal to 10 and one for less-than-or-equal to 20. Then we include the `sum(..)` reducer in the transducer composition. The resulting `sumOnlyFavorites(..)` function is a reducer that checks if a value passes both filters, and if so, adds the value to an accumulator value. +> 我们首先创建一个函数 `sumOnlyFavorites(..)` 这是其他三个函数的组合。 我们结合了两个过滤器, 一个检查值是否大于或等于10,一个检查值是否小于或等于20. 然后我们使用 `sum(..)` 减少数据传输. 结果函数 `sumOnlyFavorites(..)` 作为缩减作用,用于检查一个值是否通过两个过滤器,如果通过,则将该值添加到累加器值中。 > -> Then we make another function called `printMagicNumber(..)` which first reduces a list of numbers using that `sumOnlyFavorites(..)` reducer we just defined, resulting in a sum of only numbers that passed the *favorite* checks. Then `printMagicNumber(..)` pipes that final sum into `constructMsg(..)`, which creates a string value that finally goes into `console.log(..)`. +> 然后我们使用定义好的函数 `sumOnlyFavorites(..)` 它可以首先减少一个数字列表,然后使用另一个函数 `printMagicNumber(..)` 打印产生通过“sumOnlyFavorites”计算出数字的总和. 函数 `printMagicNumber(..)` 把最后的总数再输送到 `constructMsg(..)`, 进入 `console.log(..)`打印创建一个字符串值结果. -All those moving pieces *speak* to an FP developer in ways that likely seem highly unfamiliar to you right now. This book will help you *speak* that same kind of reasoning so that it's as readable to you as any other code, if not more so! +所有这些处理函数与函数式编程的开发人员的”对话“就好像当前相当不熟悉函数式编程的你一样,这本书帮助你像跟其他你熟悉的代码一样跟这种方式进行”对话“ -A few other quick remarks about this code comparison: +关于此代码比较的其他一些简短说明: -* It's likely that for many readers, the former snippet feels closer to comfortable/readable/maintainable than the latter snippet. It's entirely OK if that's the case. You're in exactly the right spot. I'm confident that if you stick it out through the whole book, and practice everything we talk about, that second snippet will eventually become a lot more natural, maybe even preferable! +* 对于许多读者来说,前一个片段比后一个片段更接近舒适/可读/可维护性一些。如果是这样思考的话,也完全可以。你也在一个正确的位置下想的。我相信,如果你在整本书中坚持下去,并实践我们所谈论的一切,第二个片段最终会变得更加自然,甚至更可取! -* You might have done the task significantly or entirely different from either snippet presented. That's OK, too. This book won't be prescriptive in dictating that you should do something a specific way. The goal is to illustrate the pros/cons of various patterns and enable you to make those decisions. By the end of this book, how you would approach the task may fall a little closer to the second snippet than it does right now. +* 您可能已经完成了这项任务,可能这与所提供的任何一个代码片段都有显著的或完全的不同。也没关系。这本书并没有特定性的说你应该以一种特定的方式做某件事时。目的是说明各种模式的优缺点,并使您能够做出这些决定。在本书的最后,您将如何处理这个任务可能会比现在更接近第二个片段。 -* It's also possible that you're already a seasoned FP developer who's scanning through the start of this book to see if it has anything useful for you to read. That second snippet certainly has some bits that are quite familiar. But I'm also betting that you thought, "Hmmm, I wouldn't have done it *that* way..." a couple of times. That's OK, and entirely reasonable. +* 也有可能你已经是一个经验丰富的函数式编程开发人员,正在浏览这本书的开头,看看它是否有任何有用的东西供你阅读。第二个片段肯定有一些非常熟悉的片段。但我敢打赌你会想,“嗯,我不会那样做的……”好几次。没关系,完全合理 - This is not a traditional, canonical FP book. We'll at times seem quite heretical in our approaches. We're seeking to strike a pragmatic balance between the clear undeniable benefits of FP, and the need to ship workable, maintainable JS without having to tackle a daunting mountain of math/notation/terminology. This is not *your* FP, it's "Functional-Light JavaScript". + 这不是一本传统的、规范的函数式编程书。我们的方法有时会显得很离经叛道。我们正在寻求在函数式编程明显的不可否认的好处与需要运送可操作、可维护的JS之间达成一个务实的平衡,而不必处理数学/符号/术语这座令人望而生畏的大山。这不是你独有的函数式编程,它是“js的轻量函数式编程”。 -Whatever your reasons for reading this book, welcome! +无论你出于何目的翻阅本书,欢迎加入我们! -## Confidence +## 信心 -I have a very simple premise that sort of underlies everything I do as a teacher of software development (in JavaScript): code that you cannot trust is code that you do not understand. The reverse is true also: code that you don't understand is code you can't trust. Furthermore, if you cannot trust or understand your code, then you can't have any confidence whatsoever that the code you write is suitable to the task. You run the program and basically just cross your fingers. +我有一个非常简单的前提,那就是作为一名软件开发教师,我所做的每一件事情的基础(在javascript中):您不能信任的代码是您不理解的代码。反过来也是正确的:你不理解的代码是你不能信任的代码。此外,如果您不能信任或理解您的代码,那么您就不能对您编写的代码是否适合该任务。你运行程序,基本上就是交叉手指,祈祷没有问题发生了。 -What do I mean by trust? I mean that you can verify, by reading and reasoning, not just executing, that you understand what a piece of code *will* do; you aren't just relying on what it *should* do. More often than is perhaps prudent, we tend to rely on running test suites to verify our programs' correctness. I don't mean to suggest tests are bad. But I do think we should aspire to be able to understand our code well enough that we know the test suite will pass before it runs. +我所说的信任是什么意思?我的意思是,你可以通过阅读和推理,而不仅仅是执行来验证你理解一段代码将要做什么;而不是依赖它应该做什么的层面上。通常情况下,我们倾向于依靠运行测试来验证程序的正确性。我不是说测试不好。但是我认为我们应该渴望能够充分理解我们的代码,这样我们就知道测试在运行之前会通过。. -The techniques that form the foundation of FP are designed from the mindset of having far more confidence over our programs just by reading them. Someone who understands FP, and who's disciplined enough to diligently use it throughout their programs, will write code that they **and others** can read and verify that the program will do what they want. +仅仅通过阅读代码就能让他们对我们的程序有更多信心,形成函数式编程的技术是以这样的心态设计的。理解函数式编程的人,并且有足够的自我约束在他们的程序中频繁地使用它,他们将编写代码,他们和其他人可以阅读并验证程序如他们想的一样运行。 -Confidence is also increased when we use techniques that avoid or minimize likely sources of bugs. That's perhaps one of the biggest selling points of FP: FP programs often have fewer bugs, and the bugs that do exist are usually in more obvious places, so they're easier to find and fix. FP code tends to be more bug-resistant -- certainly not bug-proof, though. +当我们使用避免或最小化可能的错误源的技术时,信心也会大大增强。这可能是函数式编程最大卖点之一:函数式编程的程序通常比较少的错误,而且存在的错误通常在一些更明显的地方,因此更容易找到和修复。函数式编程代码趋向于更具防bug性——当然不只是为防代码错误的。 -As you journey through this book, you will begin to develop more confidence in the code you write, because you will use patterns and practices that are already well proven; and you'll avoid the most common causes of program bugs! +在阅读本书的过程中,您将开始对编写的代码培养更多的信心,因为您将使用已经很好证明的模式和实践;并且您将避免最常见的程序错误! -## Communication +## 沟通 -Why is Functional Programming important? To answer that, we need to take a bigger step back and talk about why programming itself is important. +为什么函数式编程很重要?为了回答这个问题,我们需要后退一大步,讨论为什么编程本身很重要。 -It may surprise you to hear this, but I don't believe that code is primarily a set of instructions for the computer. Actually, I think the fact that code instructs the computer is almost a happy accident. +听到这个可能会让你吃惊,但我不认为代码只是计算机的一组指令。实际上,我认为代码指示计算机这几乎是一个愉快的意外。 -I believe very deeply that the vastly more important role of code is as a means of communication with other human beings. +我非常深刻地相信,代码的更重要的作用是作为与其他人交流的一种手段。 You probably know by experience that an awful lot of your time spent "coding" is actually spent reading existing code. Very few of us are so privileged as to spend all or most of our time simply banging out all new code and never dealing with code that others (or our past selves) wrote. From a48d3a14c8a1a29e08c2fbffb9fe45745b4e416e Mon Sep 17 00:00:00 2001 From: mon Date: Mon, 8 Jul 2019 11:12:58 +0800 Subject: [PATCH 22/63] Update apC.md --- manuscript/apC.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index 7910b560..c5729a56 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -96,15 +96,15 @@ fp.compose( [ 与我们熟悉的 `_.` 命名空间前缀不同,“lodash/fp”以 `fp.` 作为命名空间前缀定义其方法。我觉得这是个很有帮助的区别,而且比 `_.` 更容易理解! -Notice that `fp.compose(..)` (also known as `_.flowRight(..)` in lodash proper) takes an array of functions instead of individual arguments. +需要注意的是,`fp.Composed(..)` (在lodash中称为`_.flowRight(..)`)接受一个函数数组,而不是单个函数参数。 -You cannot beat the stability, widespread community support, and performance of lodash. It's a solid bet for your FP explorations. +lodash的稳定性、广泛的社区支持和优秀性能是你探索FP的后盾。 ## Mori (0.3.2) -In [Chapter 6](ch6.md), we already briefly glanced at the Immutable.js library, probably the most well-known for immutable data structures. +在[第6章](ch6.md),我们已经简要地看过 Immutable.js 库,它可能是最著名的不可变数据结构库。 -Let's instead look at another popular library: [Mori](https://github.com/swannodette/mori). Mori is designed with a different (ostensibly more FP-like) take on API: it uses standalone functions instead of methods directly on the values. +让我们看看另一个流行的库:[Mori](https://github.com/swannodette/mori) 。Mori在设计时采用了一套不同的API(表面上看更像FP):它使用独立的函数,而不是直接对值做操作。 ```js var state = mori.vector( 1, 2, 3, 4 ); @@ -126,21 +126,21 @@ mori.get( newState, 42 ); // "meaning of life" mori.toJs( newState ).slice( 1, 3 ); // [2,3] ``` -Some interesting things to point out about Mori for this example: +在这个例子中,Mori有一些有趣的地方需要指出: -* We're using a `vector` instead of a `list` (as one might assume), mostly because the documentation says it behaves more like we expect JS arrays to be. +* 我们使用 `vector` 代替 `list` ,主要是因为文档说它的行为更像JS的数组。 -* We cannot just randomly set a position past the end of the vector like we can with JS arrays; that throws an exception. So we have to first "grow" the vector using `mori.into(..)` with an array of the appropriate size of extra slots we want. Once we have a vector with 43 slots (4 + 39), we can set the final slot (position `42`) to the `"meaning of life"` value using the `mori.assoc(..)` method. +* 我们不能像操作JS数组那样在末尾随机设置值,这会引发异常报错。因此,我们必须首先使用 `morio .into(..)` 传入一个合适长度的数组来扩展 vector 的长度。当有一个43个插槽(注:索引值)(4 + 39)的vector,我们就可以使用 `mori.assoc(..)`方法将最后的索引位置 `42` 设置为`"meaning of life"`这个值。 -* The intermediate step of creating a larger vector with `mori.into(..)` and then creating another from it with `mori.assoc(..)` might sound inefficient. But the beauty of immutable data structures is that no cloning is going on here. Each time a "change" is made, the new data structure is just tracking the difference from the previous state. +* 使用 `mori.into(..)` 创建一个更大的vector,然后使用 `mori.assoc(..)` 在此基础上创建另一个vector,这样的操作可能听起来效率不高。但是不可变数据结构的美妙之处在于数据没有克隆。每次进行“更改”时,新的数据结构只是跟踪与以前状态的差异。 -Mori is heavily inspired by ClojureScript. Its API will be very familiar if you have experience (or currently work in!) that language. Since I don't have that experience, I find the method names a little strange to get used to. +Mori深受ClojureScript的启发。 如果你有ClojureScript语言经验(或目前正在使用!),那么应该对它的API非常熟悉。由于我没有这种经验,我觉得方法名有点奇怪,不习惯。 -But I really like the standalone function design instead of methods on values. Mori also has some functions that automatically return regular JS arrays, which is a nice convenience. +我真心喜欢这种独立的调用函数设计,而不是基于值的调用方法。Mori还有一些自动返回常规JS数组的函数,用起来都很方便。 -## Bonus: FPO +## 干货: FPO -In [Chapter 2, we introduced a pattern](ch2.md/#named-arguments) for dealing with arguments called "named arguments", which in JS means using an object at the call-site to map properties to destructured function parameters: +在 [第2章中,我们介绍了一种模式](ch2.md/#named-arguments),用于处理称为“命名参数”的参数,在JS(注:ES6)中,使用对象将属性映射到析构函数的参数: ```js function foo( {x,y} = {} ) { @@ -152,7 +152,7 @@ foo( { } ); // undefined 3 ``` -Then in [Chapter 3, we talked about extending](ch3.md/#order-matters) our ideas of currying and partial application to work with named arguments, like this: +接着在[Chapter 3, 我们探讨了更多](ch3.md/#order-matters) 关于析构函数的柯里化和偏函数应用,像下面这样: ```js function foo({ x, y, z } = {}) { @@ -164,7 +164,7 @@ var f1 = curryProps( foo, 3 ); f1( {y: 2} )( {x: 1} )( {z: 3} ); ``` -One major benefit of this style is being able to pass arguments (even with currying or partial application!) in any order without needing to do `reverseArgs(..)`-style juggling of parameters. Another is being able to omit an optional argument by simply not specifying it, instead of passing an ugly placeholder. +这种风格的一个好处是能够以任何顺序传参(即使是柯里化和偏函数应用!),而不必像`reverseArgs(..)`式的传参。 另一个好处是能够省略一个可选参数,只不需传一个丑陋的占位符。 In my journey learning FP, I've regularly been frustrated by both of those irritations of functions with traditional positional arguments; thus I've really appreciated the named arguments style for addressing those concerns. From 80fcfd2fc9f5798dc05405fad26c293705bf6a76 Mon Sep 17 00:00:00 2001 From: mon Date: Mon, 8 Jul 2019 14:45:46 +0800 Subject: [PATCH 23/63] Update apC.md --- manuscript/apC.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index c5729a56..96d06874 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -166,13 +166,13 @@ f1( {y: 2} )( {x: 1} )( {z: 3} ); 这种风格的一个好处是能够以任何顺序传参(即使是柯里化和偏函数应用!),而不必像`reverseArgs(..)`式的传参。 另一个好处是能够省略一个可选参数,只不需传一个丑陋的占位符。 -In my journey learning FP, I've regularly been frustrated by both of those irritations of functions with traditional positional arguments; thus I've really appreciated the named arguments style for addressing those concerns. +在我学习FP的过程中,我经常被固定位置传参感到沮丧,因此,我非常欣赏用命名参数风格(注:传入一个对象)解决以往的问题。 -One day, I was musing about with this style of FP coding, and wondered what it would be like if a whole FP library had all its API methods exposed in this style. I started experimenting, showed those experiments to a few FP folks, and got some positive feedback. +有一天,我在思考FP编码风格,我想如果整个FP库都以这种风格公开其所有API方法,那会是什么样子。我开始做尝试,不断把这些尝试展示给一些人看,并得到了一些积极的反馈。 -From those experiments, eventually the [FPO](https://github.com/getify/fpo) (pronounced "eff-poh") library was born; FPO stands for FP-with-Objects, in case you were wondering. +经过努力,[FPO](https://github.com/getify/fpo) (发音为“ef-poh”) 库最终诞生了。FPO意思是FP-with-Objects。 -From the documentation: +官方文档: ```js // Ramda's `reduce(..)` @@ -189,9 +189,9 @@ FPO.reduce({ }); // 19 ``` -With traditional library implementations of `reduce(..)` (like Ramda), the initial value parameter is in the middle, and not optional. FPO's `reduce(..)` method can take the arguments in any order, and you can omit the optional initial value if desired. +例如传统FP库(Ramda)的`reduce(..)`方法,初始参数的位置是固定不可变的。而FPO的`reduce(..)`方法可以按任意顺序传参,如果需要,甚至可以省略初始值。 -As with most other FP libraries, FPO's API methods are automatically loose-curried, so you can not only provide arguments in any order, but specialize the function by providing its arguments over multiple calls: +与大多数其他FP库一样,FPO的API方法自动松柯里化,因此您不仅可以按任何顺序传参,还可以通过多次调用赋值方法来传入参数: ```js var f = FPO.reduce({ arr: [3,7,9] }); @@ -201,7 +201,7 @@ var f = FPO.reduce({ arr: [3,7,9] }); f({ fn: ({acc,v}) => acc + v }); // 19 ``` -Lastly, all of FPO's API methods are also exposed using the traditional positional arguments style -- you'll find they're all very similar to Ramda and other libraries -- under the `FPO.std.*` namespace: +最后,在`FPO.std.*`命名文件下,你会发现它们与Ramda和其他库非常相似,所有FPO的API方法也可以使用传统固定位置参数操作: ```js FPO.std.reduce( @@ -211,11 +211,11 @@ FPO.std.reduce( ); // 19 ``` -If FPO's named argument form of FP appeals to you, perhaps check out the library and see what you think. It has a full test suite and most of the major FP functionality you'd expect, including everything we covered in this text to get you up and going with Functional-Light JavaScript! +如果FPO的命名参数写法对你有吸引力,你可以查看源码了解更多。 它拥有完整的测试套件和大多数你期待的FP方法,包括本文中介绍的所有内容,以帮助您更好的学习(使用)轻量级函数式编程! -## Bonus #2: fasy +## 干货 #2: fasy -FP iterations (`map(..)`, `filter(..)`, etc.) are almost always modeled as synchronous operations, meaning we eagerly run through all the steps of the iteration immediately. As a matter of fact, other FP patterns like composition and even transducing are also iterations, and are also modeled exactly this way. +FP迭代方法(`map(..)`,`filter(..)`等)几乎总是被看作同步操作,意味着立即执行所有步骤。 事实上,其他FP模式,如合成甚至转换也是迭代,并且也以这种方式执行。 But what happens if one or more of the steps in an iteration needs to complete asynchronously? You might jump to thinking that Observables (see [Chapter 10](ch10.md/#observables)) is the natural answer, but they're not what we need. @@ -302,10 +302,10 @@ Again, there will inevitably be cases where concurrent or serial asynchrony will Along with Observables, **fasy** will help you extend more FP patterns and principles to your asynchronous operations. -## Summary +## 总结 -JavaScript is not particularly designed as an FP language. However, it does have enough of the basics (like function values, closures, etc.) for us to make it FP-friendly. And the libraries we've examined here will help you do that. +JavaScript并不是专门为FP语言而设计的。 但是它具备设计FP的该有的核心(如函数值,闭包等)。 就像上面我们探讨过的优秀的库。 -Armed with the concepts from this book, you're ready to start tackling real-world code. Find a good, comfortable FP library and jump in. Practice, practice, practice! +通过学习本书中的知识,你可以开始开始coding了。 找一个好的,适合的FP库并使用之。练习,练习,练习!(注:多写才是王道) -So... that's it. I've shared what I have for you, for now. I hereby officially certify you as a "Functional-Light JavaScript" programmer! It's time to close out this "chapter" of our story of learning FP together. But my learning journey still continues; I hope yours does, too! +看到这里,我已经分享了我所有了解的。 我认为你完全可以称之为“轻量级函数式编程”程序员! 现在是时候结束我们共同学习FP的“篇章”了。 但我的学习之旅仍在继续,我也希望你的确如此! From 7eef49c64f8505304e08cb83764ac79eec533747 Mon Sep 17 00:00:00 2001 From: mon Date: Mon, 8 Jul 2019 14:49:45 +0800 Subject: [PATCH 24/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54e34291..19347588 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ * [章节 4: 组合函数](manuscript/ch4.md/#chapter-4-composing-functions) * [章节 5: 减少副作用影响](manuscript/ch5.md/#chapter-5-reducing-side-effects) * [章节 6: 值的不变性质](manuscript/ch6.md/#chapter-6-value-immutability) -* [章节 7: 闭合与对象](manuscript/ch7.md/#chapter-7-closure-vs-object) +* [章节 7: 闭包与对象](manuscript/ch7.md/#chapter-7-closure-vs-object) * [章节 8: 递归](manuscript/ch8.md/#chapter-8-recursion) * [章节 9: 列表的操作](manuscript/ch9.md/#chapter-9-list-operations) * [章节 10: 函数的异步](manuscript/ch10.md/#chapter-10-functional-async) From 3520009e44d7fc0ba34470291bb6c1230f8b2470 Mon Sep 17 00:00:00 2001 From: mon Date: Mon, 8 Jul 2019 15:50:11 +0800 Subject: [PATCH 25/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19347588..dc2675d7 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ * [序言](manuscript/foreword.md/#foreword) ([Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) * [前言](manuscript/preface.md/#preface) * [章节 1: 为什么要函数式编程?](manuscript/ch1.md/#chapter-1-why-functional-programming) -* [章节 2: 函数的性质](manuscript/ch2.md/#chapter-2-the-nature-of-functions) +* [章节 2: 函数的本质](manuscript/ch2.md/#chapter-2-the-nature-of-functions) * [章节 3: 管理函数输入](manuscript/ch3.md/#chapter-3-managing-function-inputs) * [章节 4: 组合函数](manuscript/ch4.md/#chapter-4-composing-functions) * [章节 5: 减少副作用影响](manuscript/ch5.md/#chapter-5-reducing-side-effects) From b82da5c68ca769da838cd9a977b7b44406381e4b Mon Sep 17 00:00:00 2001 From: mon Date: Mon, 8 Jul 2019 17:27:38 +0800 Subject: [PATCH 26/63] Update apC.md --- manuscript/apC.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index 96d06874..e6a10deb 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -216,12 +216,12 @@ FPO.std.reduce( ## 干货 #2: fasy FP迭代方法(`map(..)`,`filter(..)`等)几乎总是被看作同步操作,意味着立即执行所有步骤。 事实上,其他FP模式,如合成甚至转换也是迭代,并且也以这种方式执行。 + +但是如果迭代中的一个或多个步骤需要异步完成,会发生什么?自然你会想到观察者模式(见[第10章](ch10.md/#observables)),但目前不是我们所要的。 -But what happens if one or more of the steps in an iteration needs to complete asynchronously? You might jump to thinking that Observables (see [Chapter 10](ch10.md/#observables)) is the natural answer, but they're not what we need. +让我快速说明一下。 -Let me quickly illustrate. - -Imagine you have a list of URLs that represent images you want to load into a web page. The fetching of the images is asynchronous, obviously. So, this isn't going to work quite like you'd hope: +想象一下,您有一个URL列表,表示您要加载到网页中的图像。 显然,提取图像是异步的。显然,这并不像你希望的那样顺序加载: ```js var imageURLs = [ @@ -233,9 +233,9 @@ var imageURLs = [ var images = imageURLs.map( fetchImage ); ``` -The `images` array won't contain the images. Depending on the behavior of `fetchImage(..)`, it probably returns a promise for the image object once it finishes downloading. So `images` would now be a list of promises. +`images` 数组的内容基于`fetchImage(..)`方法执行,一旦它下载完成将返回一个promise的对象。 -Of course, you could then use `Promise.all(..)` to wait for all those promises to resolve, and then unwrap an array of the image object results at its completion: +当然,你也可以使用`Promise.all(..)` 来等待所有的图片下载完成返回一个resolve对象,通过`then()`方法返回一个接收图片对象的函数: ```js Promise.all( images ) @@ -244,7 +244,7 @@ Promise.all( images ) }); ``` -Unfortunately, this "trick" only works if you're going to do all the asynchronous steps concurrently (rather than serially, one after the other), and only if the operation is a `map(..)` call as shown. If you want serial asynchrony, or you want to, for example, do a `filter(..)` concurrently, this won't quite work; it's possible, but it's messier. +不幸的是,这个“技巧”只有在你要同时执行所有异步步骤(而不是串行,一个接一个)时才有效,并且只有当操作是一个`map(..)`调用时才有效。 如果你想要串行异步操作,或者你想同事使用`filter(..)`方法,这将可能返回错乱结果。 And some operations naturally require serial asynchrony, like for example an asynchronous `reduce(..)`, which clearly needs to work left-to-right one at a time; those steps can't be run concurrently and have that operation make any sense. From 9104e003c0ad93156a59313f700f91a2b14bb883 Mon Sep 17 00:00:00 2001 From: mon Date: Mon, 8 Jul 2019 20:08:46 +0800 Subject: [PATCH 27/63] Update ch7.md --- manuscript/ch7.md | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/manuscript/ch7.md b/manuscript/ch7.md index f6ac57c0..1a1265d7 100644 --- a/manuscript/ch7.md +++ b/manuscript/ch7.md @@ -1,37 +1,5 @@ -# Functional-Light JavaScript -# Chapter 7: Closure vs. Object - -A number of years ago, Anton van Straaten crafted what has become a rather famous and oft-cited [koan](https://www.merriam-webster.com/dictionary/koan) to illustrate and provoke an important tension between closure and objects: - -> The venerable master Qc Na was walking with his student, Anton. Hoping to -prompt the master into a discussion, Anton said "Master, I have heard that -objects are a very good thing - is this true?" Qc Na looked pityingly at -his student and replied, "Foolish pupil - objects are merely a poor man's -closures." -> -> Chastised, Anton took his leave from his master and returned to his cell, -intent on studying closures. He carefully read the entire "Lambda: The -Ultimate..." series of papers and its cousins, and implemented a small -Scheme interpreter with a closure-based object system. He learned much, and -looked forward to informing his master of his progress. -> -> On his next walk with Qc Na, Anton attempted to impress his master by -saying "Master, I have diligently studied the matter, and now understand -that objects are truly a poor man's closures." Qc Na responded by hitting -Anton with his stick, saying "When will you learn? Closures are a poor man's -object." At that moment, Anton became enlightened. -> -> -- Anton van Straaten 6/4/2003 -> -> http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html - -The original posting, while brief, has more context to the origin and motivations, and I strongly suggest you read that post to properly set your mindset for approaching this chapter. - -Many people I've observed read this koan smirk at its clever wit but then move on without it changing much about their thinking. However, the purpose of a koan (from the Zen Buddhist perspective) is to prod the reader into wrestling with the contradictory truths therein. So, go back and read it again. Now read it again. - -Which is it? Is a closure a poor man's object, or is an object a poor man's closure? Or neither? Or both? Is merely the take-away that closures and objects are in some way equivalent? - -And what does any of this have to do with functional programming? Pull up a chair and ponder for a while. This chapter will be an interesting detour, an excursion if you will. +# JavaScript轻量级函数式编程 +# 第7章: 闭包和对象 ## The Same Page From 123d8ddd85882345a94a3ea25791905459a3ae2c Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 9 Jul 2019 09:26:34 +0800 Subject: [PATCH 28/63] =?UTF-8?q?Rename=20=E7=AB=A0=E8=8A=821.md=20to=20ch?= =?UTF-8?q?1.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "manuscript/\347\253\240\350\212\2021.md" => manuscript/ch1.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "manuscript/\347\253\240\350\212\2021.md" => manuscript/ch1.md (100%) diff --git "a/manuscript/\347\253\240\350\212\2021.md" b/manuscript/ch1.md similarity index 100% rename from "manuscript/\347\253\240\350\212\2021.md" rename to manuscript/ch1.md From 35672c4a0891cf0afb0ec158174da7e90a2a5fe5 Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 9 Jul 2019 17:33:39 +0800 Subject: [PATCH 29/63] Update ch1.md --- manuscript/ch1.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/manuscript/ch1.md b/manuscript/ch1.md index 0332b70a..bcdbb6bc 100644 --- a/manuscript/ch1.md +++ b/manuscript/ch1.md @@ -112,15 +112,17 @@ function constructMsg(v) { return `The magic number is: ${v}`; } 我非常深刻地相信,代码的更重要的作用是作为与其他人交流的一种手段。 -You probably know by experience that an awful lot of your time spent "coding" is actually spent reading existing code. Very few of us are so privileged as to spend all or most of our time simply banging out all new code and never dealing with code that others (or our past selves) wrote. +你可能从经验中知道,你花在“编码”上的大量时间实际上是花在阅读现有代码上。我们中很少有人享有这样的特权:把全部或大部分时间都花在简单地敲出所有新代码上,从不处理别人(或我们过去的自己)写的代码上。 -It's widely estimated that developers spend 70% of code maintenance time on reading to understand it. That is eye-opening. 70%. No wonder the global average for a programmer's lines of code written per day is about 10. We spend up to 7 hours of our day just reading the code to figure out where those 10 lines should go! +据广泛估计,开发人员将70%的代码维护时间花在阅读上以理解它。这真让人大开眼界。居然达到了70%。难怪程序员每天编写的代码行数的平均值大约是10行。我们每天花7个小时来阅读代码,去理解这10行怎么运行! -We need to focus a lot more on the readability of our code. And by the way, readability is not just about fewer characters. Readability is actually most impacted by familiarity.1 +我们需要更加关注代码的可读性。还得提一下,可读性不仅仅是字符数的减少,可读性实际上最受熟悉度的影响。 +1 -If we are going to spend our time concerned with making code that will be more readable and understandable, FP is central in that effort. The principles of FP are well established, deeply studied and vetted, and provably verifiable. Taking the time to learn and employ these FP principles will ultimately lead to more readily and recognizably familiar code for you and others. The increase in code familiarity, and the expediency of that recognition, will improve code readability. +如果我们要花时间来写更易于阅读和理解的代码,那么函数式编程就是这项工作的核心。函数式编程的原则是建立良好的,深入研究和审查,并可证实的。花时间学习和使用这些函数式编程原则最终将为您和其他人带来更容易识别的熟悉代码。代码熟悉度的提高以及识别的便利性将提高代码的可读性。 -For example, once you learn what `map(..)` does, you'll be able to almost instantly spot and understand it when you see it in any program. But every time you see a `for` loop, you're going to have to read the whole loop to understand it. The syntax of the `for` loop may be familiar, but the substance of what it's doing is not; that has to be *read*, every time. + +例如,一旦您学习了“map(…)”的功能,当您在任何程序中看到它时,您几乎可以立即发现并理解它。 但是每次你看到一个“for”循环,你就必须阅读整个循环才能理解它。“for”循环的语法可能是熟悉的,但实际上它所做的并不是;你每次都必须*读*才能理解。 By having more code that's recognizable at a glance, and thus spending less time figuring out what the code is doing, our focus is freed up to think about the higher levels of program logic; this is the important stuff that most needs our attention anyway. From 04b3a860d94dd36a57312137fb8d728d9a0667cd Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 9 Jul 2019 17:36:30 +0800 Subject: [PATCH 30/63] Update ch1.md --- manuscript/ch1.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/manuscript/ch1.md b/manuscript/ch1.md index 0332b70a..bcdbb6bc 100644 --- a/manuscript/ch1.md +++ b/manuscript/ch1.md @@ -112,15 +112,17 @@ function constructMsg(v) { return `The magic number is: ${v}`; } 我非常深刻地相信,代码的更重要的作用是作为与其他人交流的一种手段。 -You probably know by experience that an awful lot of your time spent "coding" is actually spent reading existing code. Very few of us are so privileged as to spend all or most of our time simply banging out all new code and never dealing with code that others (or our past selves) wrote. +你可能从经验中知道,你花在“编码”上的大量时间实际上是花在阅读现有代码上。我们中很少有人享有这样的特权:把全部或大部分时间都花在简单地敲出所有新代码上,从不处理别人(或我们过去的自己)写的代码上。 -It's widely estimated that developers spend 70% of code maintenance time on reading to understand it. That is eye-opening. 70%. No wonder the global average for a programmer's lines of code written per day is about 10. We spend up to 7 hours of our day just reading the code to figure out where those 10 lines should go! +据广泛估计,开发人员将70%的代码维护时间花在阅读上以理解它。这真让人大开眼界。居然达到了70%。难怪程序员每天编写的代码行数的平均值大约是10行。我们每天花7个小时来阅读代码,去理解这10行怎么运行! -We need to focus a lot more on the readability of our code. And by the way, readability is not just about fewer characters. Readability is actually most impacted by familiarity.1 +我们需要更加关注代码的可读性。还得提一下,可读性不仅仅是字符数的减少,可读性实际上最受熟悉度的影响。 +1 -If we are going to spend our time concerned with making code that will be more readable and understandable, FP is central in that effort. The principles of FP are well established, deeply studied and vetted, and provably verifiable. Taking the time to learn and employ these FP principles will ultimately lead to more readily and recognizably familiar code for you and others. The increase in code familiarity, and the expediency of that recognition, will improve code readability. +如果我们要花时间来写更易于阅读和理解的代码,那么函数式编程就是这项工作的核心。函数式编程的原则是建立良好的,深入研究和审查,并可证实的。花时间学习和使用这些函数式编程原则最终将为您和其他人带来更容易识别的熟悉代码。代码熟悉度的提高以及识别的便利性将提高代码的可读性。 -For example, once you learn what `map(..)` does, you'll be able to almost instantly spot and understand it when you see it in any program. But every time you see a `for` loop, you're going to have to read the whole loop to understand it. The syntax of the `for` loop may be familiar, but the substance of what it's doing is not; that has to be *read*, every time. + +例如,一旦您学习了“map(…)”的功能,当您在任何程序中看到它时,您几乎可以立即发现并理解它。 但是每次你看到一个“for”循环,你就必须阅读整个循环才能理解它。“for”循环的语法可能是熟悉的,但实际上它所做的并不是;你每次都必须*读*才能理解。 By having more code that's recognizable at a glance, and thus spending less time figuring out what the code is doing, our focus is freed up to think about the higher levels of program logic; this is the important stuff that most needs our attention anyway. From a86a366dcff724b840e10f505f9282cc4336513d Mon Sep 17 00:00:00 2001 From: mon Date: Wed, 10 Jul 2019 11:28:17 +0800 Subject: [PATCH 31/63] Update ch7.md --- manuscript/ch7.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/manuscript/ch7.md b/manuscript/ch7.md index 1a1265d7..b166460a 100644 --- a/manuscript/ch7.md +++ b/manuscript/ch7.md @@ -1,11 +1,11 @@ # JavaScript轻量级函数式编程 # 第7章: 闭包和对象 -## The Same Page +## 达成共识 -First, let's make sure we're all on the same page when we refer to closures and objects. We're obviously in the context of how JavaScript deals with these two mechanisms, and specifically talking about simple function closure (see [Chapter 2, "Keeping Scope"](ch2.md/#keeping-scope)) and simple objects (collections of key-value pairs). +首先,确保当我们提到闭包和对象时,我们都达成了共识。 我们探讨JavaScript如何处理这两种机制的上下文,特别是普通的函数闭包(参见[第2章“保持作用域”](ch2.md/#keeping-scope))和普通对象(键值对集合) )。 -For the record, here's an illustration of a simple function closure: +普通函数闭包例子: ```js function outer() { @@ -22,7 +22,7 @@ var three = outer(); three(); // 3 ``` -And an illustration of a simple object: +普通对象例子: ```js var obj = { @@ -37,24 +37,24 @@ function three(outer) { three( obj ); // 3 ``` -Many people conjure lots of extra things when you mention "closure", such as the asynchronous callbacks or even the module pattern with encapsulation and information hiding. Similarly, "object" brings to mind classes, `this`, prototypes, and a whole slew of other utilities and patterns. +当谈论到“闭包”时,许多人会想到许多额外的部分,例如异步回调,甚至包含封装和信息隐藏的模块模式。 类似地,“对象”带来了类,“this”,原型以及大量其他方法和模式。 -As we go along, we'll carefully address the parts of this external context that matter, but for now, try to just stick to the simplest interpretations of "closure" and "object" as illustrated here; it'll make our exploration less confusing. +随着我们学习深入,我们将探讨这些重要的额外的部分,但是现在,我们先记住这里所说的“闭包”和“对象”的最简单的解释,这样可以使我们保持清晰。 -## Look Alike +## 相似 -It may not be obvious how closures and objects are related. So let's explore their similarities first. +闭包和对象的关系可能并不明显,我们首先探讨它们的相似之处。 -To frame this discussion, let me just briefly assert two things: +基于这个讨论,我先简单地确定两件事: -1. A programming language without closures can simulate them with objects instead. -2. A programming language without objects can simulate them with closures instead. +没有闭包的编程语言可以用对象模拟闭包。 +没有对象的编程语言可以用闭包来模拟对象。 -In other words, we can think of closures and objects as two different representations of a thing. +换句话说,我们可以将闭包和对象视为事物的两种不同表示。 -### State +### 状态 -Consider this code from before: +思考以下代码: ```js function outer() { @@ -72,9 +72,9 @@ var obj = { }; ``` -Both the scope closed over by `inner()` and the object `obj` contain two elements of state: `one` with value `1` and `two` with value `2`. Syntactically and mechanically, these representations of state are different. But conceptually, they're actually quite similar. +`inner()`函数和对象`obj`作用域都包含两个状态元素:`one`的值为1,`two`的值为2。在语法和机制上,这些状态的表示是不同的。但从概念上讲,它们非常相似。 -As a matter of fact, it's fairly straightforward to represent an object as a closure, or a closure as an object. Go ahead, try it yourself: +事实上,将对象表示为闭包或将闭包表示为对象是相当简单的。来吧,尝试一下: ```js var point = { @@ -84,7 +84,7 @@ var point = { }; ``` -Did you come up with something like? +你想到什么了吗? ```js function outer() { @@ -100,9 +100,9 @@ function outer() { var point = outer(); ``` -**Note:** The `inner()` function creates and returns a new array (aka, an object!) each time it's called. That's because JS doesn't afford us any capability to `return` multiple values without encapsulating them in an object. That's not technically a violation of our object-as-closure task, because it's just an implementation detail of exposing/transporting values; the state tracking itself is still object-free. With ES6+ array destructuring, we can declaratively ignore this temporary intermediate array on the other side: `var [x,y,z] = point()`. From a developer ergonomics perspective, the values are stored individually and tracked via closure instead of objects. +**注意:** 每次`inner()`函数调用并返回一个新的数组(简称, 对象!) 。原因是JS没提供在不封装多个值的情况下“返回”多个值的功能。这在技术上并不违反对象即闭包这个问题,因为它只是暴露/传递值的实现细节;状态跟踪本身仍然是基于对象的。使用ES6+数组析构,我们可以声明性地忽略临时中间数组:`var [x,y,z] = point()`。从开发人员的角度来看,这些值是单独存储的,并通过闭包(而非对象)跟踪。 -What if we have nested objects? +如果有嵌套对象怎么办? ```js var person = { @@ -115,7 +115,7 @@ var person = { }; ``` -We could represent that same kind of state with nested closures: +同样的,可以使用嵌套闭包实现: ```js function outer() { From c584530361d0008f32019736b0b50b1efb35ba5d Mon Sep 17 00:00:00 2001 From: Siming Date: Wed, 10 Jul 2019 17:20:03 +0800 Subject: [PATCH 32/63] Update ch1.md --- manuscript/ch1.md | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/manuscript/ch1.md b/manuscript/ch1.md index bcdbb6bc..d79e5c09 100644 --- a/manuscript/ch1.md +++ b/manuscript/ch1.md @@ -124,54 +124,55 @@ function constructMsg(v) { return `The magic number is: ${v}`; } 例如,一旦您学习了“map(…)”的功能,当您在任何程序中看到它时,您几乎可以立即发现并理解它。 但是每次你看到一个“for”循环,你就必须阅读整个循环才能理解它。“for”循环的语法可能是熟悉的,但实际上它所做的并不是;你每次都必须*读*才能理解。 +通过拥有看一眼就能识别的代码的能力,从而减少时间去了解代码在做什么,我们的注意力被释放出来,去思考更高层次的程序逻辑;那些都是最需要我们关注的重要内容。 By having more code that's recognizable at a glance, and thus spending less time figuring out what the code is doing, our focus is freed up to think about the higher levels of program logic; this is the important stuff that most needs our attention anyway. -FP (at least, without all the terminology weighing it down) is one of the most effective tools for crafting readable code. *That* is why it's so important. +函数式编程 (至少,没有所有的术语来衡量它) 是制作可读代码最有效的工具之一。 这也是它如此重要的原因。 -## Readability +## 可读性 -Readability is not a binary characteristic. It's a largely subjective human factor describing our relationship to code. And it will naturally vary over time as our skills and understanding evolve. I have experienced effects similar to the following figure, and anecdotally many others I've talked to have as well. +可读性不是一个二进制特性。这在很大程度上是描述我们与代码关系的主观因素。随着时间的推移,我们的技能和理解自然会发生变化。我曾经历过类似下图的效果,而且我也曾与许多人聊过关于这些有趣的事。

-You may just find yourself experiencing similar effects as you work through the book. But take heart; if you stick this out, the curve comes back up! +你可能会发现,当你读这本书的时候,你也会有类似的感受。但振作起来;如果你坚持下去,曲线就会上升! -*Imperative* describes the code most of us probably already write naturally; it's focused on precisely instructing the computer *how* to do something. Declarative code -- the kind we'll be learning to write, which adheres to FP principles -- is code that's more focused on describing the *what* outcome. +命令式代码描述我们大多数人已经自然编写的代码;它专注于精确指导计算机“如何”做某事。而我们将学习编写的声明式代码,它遵循函数式编程原则是更专注于描述结果输出的代码。 -Let's revisit the two code snippets presented earlier in this chapter. +让我们回顾本章前面介绍的两个代码片段。 -The first snippet is imperative, focused almost entirely on *how* to do the tasks; it's littered with `if` statements, `for` loops, temporary variables, reassignments, value mutations, function calls with side effects, and implicit data flow between functions. You certainly *can* trace through its logic to see how the numbers flow and change to the end state, but it's not at all clear or straightforward. +第一个片段是命令式的,几乎完全集中于“如何”完成任务;它充斥着“if”语句、“for”循环、临时变量、重新分配、值突变、带有副作用的函数调用以及函数之间的隐式数据流。当然,你“可以”通过它的逻辑来查看数字是如何流动和更改到最终状态的,但它一点也不清楚或直接。 -The second snippet is more declarative; it does away with most of those aforementioned imperative techniques. Notice there's no explicit conditionals, loops, side effects, reassignments, or mutations; instead, it employs well-known (to the FP world, anyway!) and trustable patterns like filtering, reduction, transducing, and composition. The focus shifts from low-level *how* to higher level *what* outcomes. +第二个片段更具声明性一些;它消除了前面提到的大多数命令式技术。注意没有显式的条件、循环、副作用、重新分配或突变;相反,它使用我们所说的函数式编程和可信的模式,如过滤、还原、转换和组合。注重从低级别的“如何”转移到高级的“结果”。 -Instead of messing with an `if` statement to test a number, we delegate that to a well-known FP utility like `gte(..)` (greater-than-or-equal-to), and then focus on the more important task of combining that filter with another filter and a summation function. +我们没有使用“if”语句来测试一个数字,而是将其委托给一个函数式编程里的实用程序,如“gte(..)”(大于或等于)去操作,然后将重点放在更重要的任务上,即将该过滤器与另一个过滤器和求和函数组合起来,得到我们想要的结果。 -Moreover, the flow of data through the second program is explicit: +此外,通过第二个程序的数据流是明确的: -1. A list of numbers goes into `printMagicNumber(..)`. -2. One at a time those numbers are processed by `sumOnlyFavorites(..)`, resulting in a single number total of only our favorite kinds of numbers. -3. That total is converted to a message string with `constructMsg(..)`. -4. The message string is printed to the console with `console.log(..)`. +1. 一系列数字经过函数 `printMagicNumber(..)`. +2. 这些数字由“sumOnlyFavorites(..)”依次次处理,最后结果得到一个数字,其中得出一个我们最喜欢的数字 +3. 这个总数被“constructMsg(..)”函数转换为一个带有的消息字符串。 +4. 消息字符串通过`console.log(..)`打印出来. -You may still feel this approach is convoluted, and that the imperative snippet was easier to understand. You're much more accustomed to it; familiarity has a profound influence on our judgments of readability. By the end of this book, though, you will have internalized the benefits of the second snippet's declarative approach, and that familiarity will spring its readability to life. +从上面可以看到命令式代码片段更容易理解,但您可能仍然觉得这种方法很复杂,你的习惯与熟悉度对可读性的判断有深刻的影响。不过,在本书的最后,您将会潜移默化的了解到第二个代码片段的声明性方法的优点,并且熟悉性将使其可读性更强。 -I know asking you to believe that at this point is a leap of faith. +我知道让你相信这一点是一种信仰的飞跃。 -It takes a lot more effort, and sometimes more code, to improve its readability as I'm suggesting, and to minimize or eliminate many of the mistakes that lead to bugs. Quite honestly, when I started writing this book, I could never have written (or even fully understood!) that second snippet. As I'm now further along on my journey of learning, it's more natural and comfortable. +正如我所建议的,要提高它的可读性,并最小化或消除导致bug的许多错误,需要付出更多的努力,有时还需要编写更多的代码。坦白地说,当我开始写这本书的时候,我可能还完全写不出(甚至完全理解)第二段。当我更深入学习的时候,一切都变得自然与舒适。 -If you're hoping that FP refactoring, like a magic silver bullet, will quickly transform your code to be more graceful, elegant, clever, resilient, and concise -- that it will come easy in the short term -- unfortunately that's just not a realistic expectation. +如果您希望使用函数式编程重构,那这就像一个神奇的银弹,能够快速地将您的代码转换为更优雅、更优雅、更聪明、更有弹性、更简洁的代码(短期内很容易实现),万幸的是,这是一个现实的期望不难实现。 -FP is a very different way of thinking about how code should be structured, to make the flow of data much more obvious and to help your reader follow your thinking. It will take time. This effort is eminently worthwhile, but it can be an arduous journey. +函数式编程是一种非常不同的方式来考虑代码应该如何构造,从而使数据流更加明显,并帮助读者跟随您的想法。这需要时间。这一努力非常值得,但可能是一段艰苦的旅程。 -It still often takes me multiple attempts at refactoring a snippet of imperative code into more declarative FP, before I end up with something that's clear enough for me to understand later. I've found converting to FP is a slow iterative process rather than a quick binary flip from one paradigm to another. +通常,我仍然需要多次尝试将命令式代码片段重构为更具声明性的函数式编程模式的代码,然后才能得到一些足够清晰的代码,以便以后能够理解。我发现转换到函数式编程模式是一个缓慢的迭代过程,不像从一个范例到另一个范例的二进制转换那样快速。 -I also apply the "teach it later" test to every piece of code I write. After I've written a piece of code, I leave it alone for a few hours or days, then come back and try to read it with fresh eyes, and pretend as if I need to teach or explain it to someone else. Usually, it's jumbled and confusing the first few passes, so I tweak it and repeat! +我还将“以后再教”测试应用到我编写的每段代码中。在我写完一段代码后,我会把它放在一边几个小时或几天,然后回来,试着用新的眼光来读它,假装我需要教别人或向别人解释它。通常,这样给别人解释的时候相当混乱,而且要不断的调整那些代码! -I'm not trying to dampen your spirits. I really want you to hack through these weeds. I am glad I did it. I can finally start to see the curve bending upward toward improved readability. The effort has been worth it. It will be for you, too. +我不是想让你扫兴。我真希望你能解开疑惑。我很高兴我做到了。我最终可以看到上面图像解释的曲线向上弯曲的状态,改造代码以提高可读性。这些努力是值得的。 -## Perspective +## 客观判断 Most other FP texts seem to take a top-down approach, but we're going to go the opposite direction: working from the ground up, we'll uncover the basic foundational principles that I believe formal FPers would admit are the scaffolding for everything they do. But for the most part we'll stay arm's length away from most of the intimidating terminology or mathematical notation that can so easily frustrate learners. From 5946b21dc4bfd8abc5d4e7db79e0e7c01f317da8 Mon Sep 17 00:00:00 2001 From: mon Date: Wed, 17 Jul 2019 12:16:23 +0800 Subject: [PATCH 33/63] Update ch7.md --- manuscript/ch7.md | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/manuscript/ch7.md b/manuscript/ch7.md index b166460a..9370b60a 100644 --- a/manuscript/ch7.md +++ b/manuscript/ch7.md @@ -100,7 +100,7 @@ function outer() { var point = outer(); ``` -**注意:** 每次`inner()`函数调用并返回一个新的数组(简称, 对象!) 。原因是JS没提供在不封装多个值的情况下“返回”多个值的功能。这在技术上并不违反对象即闭包这个问题,因为它只是暴露/传递值的实现细节;状态跟踪本身仍然是基于对象的。使用ES6+数组析构,我们可以声明性地忽略临时中间数组:`var [x,y,z] = point()`。从开发人员的角度来看,这些值是单独存储的,并通过闭包(而非对象)跟踪。 +**注意:** 每次`inner()` 函数调用并返回一个新的数组(简称, 对象!) 。原因是JS没提供在不封装多个值的情况下“返回”多个值的功能。这在技术上并不违反对象即闭包这个问题,因为它只是暴露/传递值的实现细节;状态跟踪本身仍然是基于对象的。使用ES6+数组析构,我们可以声明性地忽略临时中间数组:`var [x,y,z] = point()`。从开发人员的角度来看,这些值是单独存储的,并通过闭包(而非对象)跟踪。 如果有嵌套对象怎么办? @@ -138,7 +138,7 @@ function outer() { var person = outer(); ``` -Let's practice going the other direction, from closure to object: +让我们试试从闭包转为对象: ```js function point(x1,y1) { @@ -155,7 +155,7 @@ var pointDistance = point( 1, 1 ); pointDistance( 4, 5 ); // 5 ``` -`distFromPoint(..)` is closed over `x1` and `y1`, but we could instead explicitly pass those values as an object: +`distFromPoint(..)`封装了`x1`和`y1`, 但我们可以显式地传入值作为对象: ```js function pointDistance(point,x2,y2) { @@ -173,13 +173,13 @@ pointDistance( // 5 ``` -The `point` object state explicitly passed in replaces the closure that implicitly held that state. +显式的传入`point`对象替换隐式的闭包状态。 -### Behavior, Too! +### 行为也如此! -It's not just that objects and closures represent ways to express collections of state, but also that they can include behavior via functions/methods. Bundling data with its behavior has a fancy name: encapsulation. +对象和闭包不仅表示表示状态集合的方法,而且还包含函数/方法。将数据与其行为捆绑在一起有一个奇特的名称:封装。 -Consider: +试想一下: ```js function person(name,age) { @@ -196,9 +196,9 @@ var birthdayBoy = person( "Kyle", 36 ); birthdayBoy(); // Happy 37th Birthday, Kyle! ``` -The inner function `happyBirthday()` has closure over `name` and `age` so that the functionality therein is kept with the state. +内部函数`happyBirthday()` 通过闭包引入`name` 和 `age` 使其功能与状态保持一致。(注:保持了变量引用和不销毁) -We can achieve that same capability with a `this` binding to an object: +我们可以通过将`this`绑定到对象来实现相同的能力: ```js var birthdayBoy = { @@ -216,11 +216,11 @@ birthdayBoy.happyBirthday(); // Happy 37th Birthday, Kyle! ``` -We're still expressing the encapsulation of state data with the `happyBirthday()` function, but with an object instead of a closure. And we don't have to explicitly pass in an object to a function (as with earlier examples); JavaScript's `this` binding easily creates an implicit binding. +我们仍然用`happyBirthday()`函数来表示状态数据的封装,但是使用对象而不是闭包。 而且我们不必将对象显式传递给函数(与前面的示例一样); JavaScript的`this`绑定很容易创建一个隐式绑定。 -Another way to analyze this relationship: a closure associates a single function with a set of state, whereas an object holding the same state can have any number of functions to operate on that state. +另一方面分析此关系:闭包将单个函数与一组状态相关联,而对象可以保持相同状态,而持有相同状态的对象可以有任意数量的函数对该状态进行操作。 -As a matter of fact, you could even expose multiple methods with a single closure as the interface. Consider a traditional object with two methods: +事实上,你甚至可以使用单个闭包作为接口来暴露多个方法。 思考以下使用两种方法的传统对象: ```js var person = { @@ -238,7 +238,7 @@ person.first() + " " + person.last(); // Kyle Simpson ``` -Just using closure without objects, we could represent this program as: +只使用闭包,可以这么做: ```js function createPerson(firstName,lastName) { @@ -272,13 +272,13 @@ person( "first" ) + " " + person( "last" ); // Kyle Simpson ``` -While these programs look and feel a bit different ergonomically, they're actually just different implementation variations of the same program behavior. +这些程序在人机工程学上看起来有点不同,事实上只是相同程序行为的不同实现而已。 -### (Im)mutability +### (不)可变数据 -Many people will initially think that closures and objects behave differently with respect to mutability; closures protect from external mutation while objects do not. But, it turns out, both forms have identical mutation behavior. +许多人最初会认为闭包和对象在可变性方面表现不同,闭包能保护免受外部改变(影响),而对象不会。但事实证明,两种形式都有相同的可变行为。 -That's because what we care about, as discussed in [Chapter 6](ch6.md), is **value** mutability, and this is a characteristic of the value itself, regardless of where or how it's assigned: +正如我们在 [第6章](ch6.md)所讨论的,需要关心的,**值**的可变性,这是值本身的一个特征,无论它在何处或如何赋值: ```js function outer() { @@ -296,9 +296,9 @@ var xyPublic = { }; ``` -The value stored in the `x` lexical variable inside `outer()` is immutable -- remember, primitives like `2` are by definition immutable. But the value referenced by `y`, an array, is definitely mutable. The exact same goes for the `x` and `y` properties on `xyPublic`. +`outer()`的内部变量`x`(基础类型)值是不可变的 - 记住,像`2`这样的基本类型都是不可变的。 但是,数组`y`(引用类型)引用的值是可变的。 `xyPublic`里的`x`和`y`属性也是这个道理。 -We can reinforce the point that objects and closures have no bearing on mutability by pointing out that `y` is itself an array, and thus we need to break this example down further: +我们可以通过指出`y`本身就是一个数组来强调对象和闭包对可变性没有影响,因此我们需要进一步分解这个例子: ```js function outer() { @@ -326,15 +326,15 @@ var xyPublic = { }; ``` -If you think about it as "turtles (aka, objects) all the way down", at the lowest level, all state data is primitives, and all primitives are value-immutable. +如果您将其视为“turtles (aka, objects) all the way down”(注:不太理解作者用意),那么在最低级别上,所有状态数据都是基础类型,并且所有基础类型都是不可变值。 -Whether you represent this state with nested objects, or with nested closures, the values being held are all immutable. +无论使用嵌套对象表示此状态,还是使用嵌套闭包表示此状态,值都是不可变的。 -### Isomorphic +### 同构 -The term "isomorphic" gets thrown around a lot in JavaScript these days, and it's usually used to refer to code that can be used/shared in both the server and the browser. I wrote a blog post a while back that calls bogus on that usage of this word "isomorphic", which actually has an explicit and important meaning that's being clouded. +“同构”这个术语在JavaScript中经常出现,通常用于指可以在服务器和浏览器中使用/共享的代码。不久前,我写了一篇博客文章,对“同构”这个词的用法进行了抨击,实际上它有一个明确而重要的含义,但却被用错地方。 -Here's some selections from a part of that post: +以下列举这篇文章的部分节选: > What does isomorphic mean? Well, we could talk about it in mathematical terms, or sociology, or biology. The general notion of isomorphism is that you have two things which are similar in structure but not the same. > From 967877443b58fcc4f7228bede16993527938dd06 Mon Sep 17 00:00:00 2001 From: mon Date: Wed, 17 Jul 2019 16:18:48 +0800 Subject: [PATCH 34/63] Update ch7.md --- manuscript/ch7.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/manuscript/ch7.md b/manuscript/ch7.md index 9370b60a..303ff263 100644 --- a/manuscript/ch7.md +++ b/manuscript/ch7.md @@ -3,7 +3,7 @@ ## 达成共识 -首先,确保当我们提到闭包和对象时,我们都达成了共识。 我们探讨JavaScript如何处理这两种机制的上下文,特别是普通的函数闭包(参见[第2章“保持作用域”](ch2.md/#keeping-scope))和普通对象(键值对集合) )。 +首先,确保当我们提到闭包和对象时,我们都达成了共识。 我们探讨JavaScript如何处理这两种机制的上下文,特别是普通的函数闭包(参见[第2章“保持作用域”](ch2.md/#keeping-scope))和普通对象(键值对集合) )。 普通函数闭包例子: @@ -72,7 +72,7 @@ var obj = { }; ``` -`inner()`函数和对象`obj`作用域都包含两个状态元素:`one`的值为1,`two`的值为2。在语法和机制上,这些状态的表示是不同的。但从概念上讲,它们非常相似。 +`inner()`函数和对象`obj`作用域都包含两个状态元素:`one`的值为`1`,`two`的值为`2`。在语法和机制上,这些状态的表示是不同的。但从概念上讲,它们非常相似。 事实上,将对象表示为闭包或将闭包表示为对象是相当简单的。来吧,尝试一下: @@ -336,29 +336,29 @@ var xyPublic = { 以下列举这篇文章的部分节选: -> What does isomorphic mean? Well, we could talk about it in mathematical terms, or sociology, or biology. The general notion of isomorphism is that you have two things which are similar in structure but not the same. -> -> In all those usages, isomorphism is differentiated from equality in this way: two values are equal if they’re exactly identical in all ways, but they are isomorphic if they are represented differently but still have a 1-to-1, bi-directional mapping relationship. -> -> In other words, two things A and B would be isomorphic if you could map (convert) from A to B and then go back to A with the inverse mapping. +> 同构是什么意思呢? 我们可以用数学术语,社会学或生物学来解释,同构概念是两个结构相似但不相同的东西。 -Recall in [Chapter 2, "Brief Math Review"](ch2.md/#brief-math-review), we discussed the mathematical definition of a function as being a mapping between inputs and outputs. We pointed out this is technically called a morphism. An isomorphism is a special case of bijective (aka, 2-way) morphism that requires not only that the mapping must be able to go in either direction, but also that the behavior is identical in either form. +> 在所有这些用法中,同构和相等的区别在这里:如果两个值在所有方面完全相同,则它们是相等的,但如果它们以不同的方式表示但仍具有1对1的双向性,则它们是同构的 映射关系。 -But instead of thinking about numbers, let's relate isomorphism to code. Again quoting my blog post: +> 换句话说,如果你可以从A映射(转换)到B然后用逆映射返回到A,那么A和B可以称为是同构的。 -> [W]hat would isomorphic JS be if there were such a thing? Well, it could be that you have one set of JS code that is converted to another set of JS code, and that (importantly) you could convert from the latter back to the former if you wanted. +回顾一下[第2章](ch2.md/#brief-math-review),我们讨论了函数的数学定义,即输入和输出之间的映射,这在学术上被称为态射。 同构是一种双射(又称双向)态射的特殊情况,它不仅要求映射必须能够在任一方向上进行,而且要求行为在任何一种形式中都是相同的。 -As we asserted earlier with our examples of closures-as-objects and objects-as-closures, these representative alternations go either way. In this respect, they are isomorphisms to each other. +但是,我们不要考虑数学术语,而是将同构与代码联系起来。 再次引用我的博文: -Put simply, closures and objects are isomorphic representations of state (and its associated functionality). +> 如果真有这种东西,同构JS会是什么?它可能是一组JS代码被转换成另一组JS代码,而且(重要的是)如果有需要,可以将后者转换回前者。 -The next time you hear someone say "X is isomorphic to Y", what they mean is, "X and Y can be converted from either one to the other in either direction, and not lose information." +正如我们在前面的 “闭包作为对象” 和 “对象作为闭包” 示例中所说的那样,它们的表达可以任意替换。因此,它们彼此是同构的。 -### Under the Hood +简单地说,闭包和对象是状态(及其相关功能)的同构表示。 -So, we can think of objects as an isomorphic representation of closures from the perspective of code we could write. But we can also observe that a closure system could actually be implemented -- and likely is -- with objects! +下次当你听到有人说 “X与Y是同构” 时,他们的意思是“X和Y可以从任何一个方向转换到另一个方向,而不会丢失特性。” -Think about it this way: in the following code, how is JS keeping track of the `x` variable for `inner()` to keep referencing, well after `outer()` has already run? +### 深入内部结构 + +因此,从编写代码的角度来看可以将对象看作闭包的同构表示。但我们也观察到,闭包系统实际上可以用对象实现——而且很可能是这样的! + +考虑以下代码:JS如何追踪`x`变量,以便` inner()`函数在`outer()`函数运行之后仍然保持它的引用? ```js function outer() { From dfdeee344c1e6ac06b4803a856ddf3d9c32a6a2f Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 12 Aug 2019 17:07:47 +0800 Subject: [PATCH 35/63] Update ch1.md --- manuscript/ch1.md | 50 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/manuscript/ch1.md b/manuscript/ch1.md index d79e5c09..b5b1c45b 100644 --- a/manuscript/ch1.md +++ b/manuscript/ch1.md @@ -174,49 +174,47 @@ By having more code that's recognizable at a glance, and thus spending less time ## 客观判断 -Most other FP texts seem to take a top-down approach, but we're going to go the opposite direction: working from the ground up, we'll uncover the basic foundational principles that I believe formal FPers would admit are the scaffolding for everything they do. But for the most part we'll stay arm's length away from most of the intimidating terminology or mathematical notation that can so easily frustrate learners. +大多数FP文本似乎都采取了自上而下的方法,但我们会以相反的方向:从头开始,我们将揭示基本的基本原则,我相信正式的FP使用者会承认这是他们所做一切的脚手架。但在很大程度上,我们会与大多数吓人的术语或数学符号保持一定距离,因为这些术语或数学符号很容易让学习者感到沮丧。 -I believe it's less important what you call something and more important that you understand what it is and how it works. That's not to say there's no importance to shared terminology -- it undoubtedly eases communication among seasoned professionals. But for the learner, I've found it can be distracting. +我相信你所说的东西不那么重要,更重要的是你要了解它是什么以及它是如何工作的。这并不是说共享术语不重要——毫无疑问,它简化了经验丰富的专业人员之间的交流。但对于学习者来说,我发现这会分散注意力。 -So this book will try to focus more on the base concepts and less on the fancy fluff. That's not to say there won't be terminology; there definitely will be. But don't get too wrapped up in the sophisticated words. Wherever necessary, look beyond them to the ideas. +因此,这本书将会更多地集中在基本概念,而不是花里胡哨的说些无意义的事情上。这并不是说没有术语,而是肯定会有术语。但不要过于沉溺于复杂的语言中。在必要的时候,超越他们去考虑这些想法。 -I call the less formal practice herein "Functional-Light Programming" because I think where the formalism of true FP suffers is that it can be quite overwhelming if you're not already accustomed to formal thought. I'm not just guessing; this is my own personal story. Even after teaching FP and writing this book, I can still say that the formalism of terms and notation in FP is very, very difficult for me to process. I've tried, and tried, and I can't seem to get through much of it. +我把这些不太正式的实践称为“轻量编程”,我认为真正的FP的形式主义所受害的地方是,如果你还不习惯于正式的思想,它可能是非常压倒性的。我不只是简单猜测,从我自己的故事中可以说明。即使在教过FP和写过这本书之后,我仍然可以说,FP中的术语和符号的形式主义对我来说是非常难以处理的。我试过又试,但还是觉得难以处理。 -I know many FPers who believe that the formalism itself helps learning. But I think there's clearly a cliff where that only becomes true once you reach a certain comfort with the formalism. If you happen to already have a math background or even some flavors of CS experience, this may come more naturally to you. But some of us don't, and no matter how hard we try, the formalism keeps getting in the way. +我知道许多人相信形式主义的方式有助于学习。但我认为很明显这是一个误解,只有当你对形式主义有了一定的了解后,才会认识到。如果你恰好已经有了数学背景,甚至是一些CS经验,这对你来说可能更自然。但是我们中的一些人没有,不管我们多么努力,形式主义总是阻碍我们。 -So this book introduces the concepts that I believe FP is built on, but comes at it by giving you a boost from below to climb up the cliff wall, rather than condescendingly shouting down at you from the top, prodding you to just figure out how to climb as you go. +所以这本书介绍了我相信函数编程是建立在其基础上的概念,但它是通过从下面给你一个动力来爬上悬崖峭壁,而不是屈尊地从顶部冲你喊叫,督促你去弄清楚怎么爬。 -## How to Find Balance +## 如何找到平衡 -If you've been around programming for very long, chances are you've heard the phrase "YAGNI" before: "You Ain't Gonna Need It". This principle primarily comes from extreme programming, and stresses the high risk and cost of building a feature before it's needed. +如果你已经在编程方面工作很长时间了,很可能你以前听过“YAGNI”这个缩写:“You Ain’t Gonna Need It”(“你不需要它”)。这个原则主要来自于极端编程,强调在需要之前构建一个特性的高风险和成本。. -Sometimes we guess we'll need a feature in the future, build it now believing it'll be easier to do as we build other stuff, then realize we guessed wrong and the feature wasn't needed, or needed to be quite different. Other times we guess right, but build a feature too early, and suck up time from the features that are genuinely needed now; we incur an opportunity cost in diluting our energy. +“YAGNI”挑战让我们要记住:即使在某种情况下这是违反直觉的,我们通常也应该推迟建造一些东西,直到目前需要它为止。我们倾向于夸大我们对未来重构的估计,即在需要时稍后添加重构的成本。很可能,以后做起来并不像我们想象的那么困难。 -YAGNI challenges us to remember: even if it's counterintuitive in a situation, we often should postpone building something until it's presently needed. We tend to exaggerate our mental estimates of the future refactoring cost of adding it later when it is needed. Odds are, it won't be as hard to do later as we might assume. +当它应用于函数式编程时,我会给出这样的警告:在本文中会讨论许多有趣的模式,但只因为您发现一些令人兴奋的模式可以应用,在您的代码的应用这些模式可能未必合适。 -As it applies to functional programming, I would offer this admonition: there will be plenty of interesting and compelling patterns discussed in this text, but just because you find some pattern exciting to apply, it may not necessarily be appropriate to do so in a given part of your code. +这就是我不同于许多正式的函数编程人员的地方:仅仅是因为您*可以*将函数编程模式应用于某个东西,但并不意味着您就*应该*将函数编程模式应用于到你的代码上。此外,有很多方法可以分割一个问题,即使您可能已经学习了一种更为复杂的方法,这种方法对维护和可扩展性来说更具“未来证明”,但在这一点上,更简单的函数编程模式可能就足够了。 -This is where I will differ from many formal FPers: just because you *can* apply FP to something doesn't mean you *should* apply FP to it. Moreover, there are many ways to slice a problem, and even though you may have learned a more sophisticated approach that is more "future-proof" to maintenance and extensibility, a simpler FP pattern might be more than sufficient in that spot. +一般来说,我建议您在编写代码时寻求平衡,并在掌握了一些技巧后,在应用函数编程概念时保持保守。在决定某个特定的模式或抽象的时候,考虑是否有助于该部分代码更具可读性,或者它只是引入了尚不保证的巧妙的复杂性时,默认了“YAGNI”原则。 -Generally, I'd recommend seeking balance in what you code, and to be conservative in your application of FP concepts as you get the hang of things. Default to the YAGNI principle in deciding if a certain pattern or abstraction will help that part of the code be more readable or if it's just introducing clever sophistication that isn't (yet) warranted. - -> Reminder, any extensibility point that’s never used isn’t just wasted effort, it’s likely to also get in your way as well +> 提醒一下,任何从未使用过的扩展点不仅仅是浪费精力,还可能妨碍您的工作。 > > Jeremy D. Miller @jeremydmiller 2/20/15 > > https://twitter.com/jeremydmiller/status/568797862441586688 -Remember, every single line of code you write has a reader cost associated with it. That reader may be another team member, or even your future self. Neither of those readers will be impressed with overly clever, unnecessary sophistication just to show off your FP prowess. +记住,您编写的每一行代码都会有相应的阅读成本。看这代码的人可能是团队的成员,甚至是你未来的自己。谁都不会对看起来老练的代码印象深刻,代码只是为了炫耀你的函数编程能力罢了。 -The best code is the code that is most readable in the future because it strikes exactly the right balance between what it can/should be (idealism) and what it must be (pragmatism). +最好的代码是未来可读性最高的代码,因为它在理想与实用之间达到了正确的平衡。 -## Resources +## 资源 -I have drawn on a great many different resources to be able to compose this text. I believe you, too, may benefit from them, so I wanted to take a moment to point them out. +我利用了大量不同的资源来撰写这篇文章。我相信你也会从中受益,下面花点时间简单介绍一下。 -### Books +### 相关书籍 -Some FP/JavaScript books that you should definitely read: +一些你不可错过的函数编程/JS书籍: * [Professor Frisby's Mostly Adequate Guide to Functional Programming](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch1.html) by [Brian Lonsdorf](https://twitter.com/drboolean) * [JavaScript Allongé](https://leanpub.com/javascriptallongesix) by [Reg Braithwaite](https://twitter.com/raganwald) @@ -251,15 +249,15 @@ Here are a few popular FP libraries for JavaScript that are a great place to sta [Appendix C takes a deeper look at these libraries](apC.md/#stuff-to-investigate) and others. -## Summary +## 总结 -You may have a variety of reasons for starting to read this book, and different expectations of what you'll get out of it. This chapter has explained why I want you to read the book and what I want you to get out of the journey. It also helps you articulate to others (like your fellow developers) why they should come on the journey with you! +你可能有各种各样的理由开始读这本书,以及对你能从中得到什么的不同期望。这一章解释了为什么我要你读这本书,以及我要你从中得到什么。它还可以帮助您向其他人(如您的开发伙伴)清楚地表达为什么他们应该与您一起读这本书! -Functional programming is about writing code that is based on proven principles so we can gain a level of confidence and trust over the code we write and read. We shouldn't be content to write code that we anxiously *hope* works, and then abruptly breathe a sigh of relief when the test suite passes. We should *know* what it will do before we run it, and we should be absolutely confident that we've communicated all these ideas in our code for the benefit of other readers (including our future selves). +函数式编程是关于编写基于经验证的原则的代码,这样我们就可以对编写和读取的代码有自信。我们不应该满足于编写揣测的工作代码,然后测试通过后松了一口气的状态。在运行它之前,我们应该*知道*它将做什么,并且我们应该确信我们已经在代码中为其他读者(包括我们未来的自己)传达了所有这些想法。 -This is the heart of Functional-Light JavaScript. The goal is to learn to effectively communicate with our code but not have to suffocate under mountains of notation or terminology to get there. +这是该书的核心。我们的目标是学会有效地与我们的代码通信,但不必为了达到目的而困惑在符号或术语的复杂中。 -The journey to learning functional programming starts with deeply understanding the nature of what a function is. That's what we tackle in the next chapter. +学习函数编程从深入了解函数的本质开始。这就是我们在下一章要讨论的问题。 ---- From cd68a56a5e8424b775b39f8214fea34235a71401 Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 12 Aug 2019 17:14:10 +0800 Subject: [PATCH 36/63] Update ch1.md --- manuscript/ch1.md | 64 ++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/manuscript/ch1.md b/manuscript/ch1.md index d79e5c09..e7f83b62 100644 --- a/manuscript/ch1.md +++ b/manuscript/ch1.md @@ -174,57 +174,55 @@ By having more code that's recognizable at a glance, and thus spending less time ## 客观判断 -Most other FP texts seem to take a top-down approach, but we're going to go the opposite direction: working from the ground up, we'll uncover the basic foundational principles that I believe formal FPers would admit are the scaffolding for everything they do. But for the most part we'll stay arm's length away from most of the intimidating terminology or mathematical notation that can so easily frustrate learners. +大多数FP文本似乎都采取了自上而下的方法,但我们会以相反的方向:从头开始,我们将揭示基本的基本原则,我相信正式的FP使用者会承认这是他们所做一切的脚手架。但在很大程度上,我们会与大多数吓人的术语或数学符号保持一定距离,因为这些术语或数学符号很容易让学习者感到沮丧。 -I believe it's less important what you call something and more important that you understand what it is and how it works. That's not to say there's no importance to shared terminology -- it undoubtedly eases communication among seasoned professionals. But for the learner, I've found it can be distracting. +我相信你所说的东西不那么重要,更重要的是你要了解它是什么以及它是如何工作的。这并不是说共享术语不重要——毫无疑问,它简化了经验丰富的专业人员之间的交流。但对于学习者来说,我发现这会分散注意力。 -So this book will try to focus more on the base concepts and less on the fancy fluff. That's not to say there won't be terminology; there definitely will be. But don't get too wrapped up in the sophisticated words. Wherever necessary, look beyond them to the ideas. +因此,这本书将会更多地集中在基本概念,而不是花里胡哨的说些无意义的事情上。这并不是说没有术语,而是肯定会有术语。但不要过于沉溺于复杂的语言中。在必要的时候,超越他们去考虑这些想法。 -I call the less formal practice herein "Functional-Light Programming" because I think where the formalism of true FP suffers is that it can be quite overwhelming if you're not already accustomed to formal thought. I'm not just guessing; this is my own personal story. Even after teaching FP and writing this book, I can still say that the formalism of terms and notation in FP is very, very difficult for me to process. I've tried, and tried, and I can't seem to get through much of it. +我把这些不太正式的实践称为“轻量编程”,我认为真正的FP的形式主义所受害的地方是,如果你还不习惯于正式的思想,它可能是非常压倒性的。我不只是简单猜测,从我自己的故事中可以说明。即使在教过FP和写过这本书之后,我仍然可以说,FP中的术语和符号的形式主义对我来说是非常难以处理的。我试过又试,但还是觉得难以处理。 -I know many FPers who believe that the formalism itself helps learning. But I think there's clearly a cliff where that only becomes true once you reach a certain comfort with the formalism. If you happen to already have a math background or even some flavors of CS experience, this may come more naturally to you. But some of us don't, and no matter how hard we try, the formalism keeps getting in the way. +我知道许多人相信形式主义的方式有助于学习。但我认为很明显这是一个误解,只有当你对形式主义有了一定的了解后,才会认识到。如果你恰好已经有了数学背景,甚至是一些CS经验,这对你来说可能更自然。但是我们中的一些人没有,不管我们多么努力,形式主义总是阻碍我们。 -So this book introduces the concepts that I believe FP is built on, but comes at it by giving you a boost from below to climb up the cliff wall, rather than condescendingly shouting down at you from the top, prodding you to just figure out how to climb as you go. +所以这本书介绍了我相信函数编程是建立在其基础上的概念,但它是通过从下面给你一个动力来爬上悬崖峭壁,而不是屈尊地从顶部冲你喊叫,督促你去弄清楚怎么爬。 -## How to Find Balance +## 如何找到平衡 -If you've been around programming for very long, chances are you've heard the phrase "YAGNI" before: "You Ain't Gonna Need It". This principle primarily comes from extreme programming, and stresses the high risk and cost of building a feature before it's needed. +如果你已经在编程方面工作很长时间了,很可能你以前听过“YAGNI”这个缩写:“You Ain’t Gonna Need It”(“你不需要它”)。这个原则主要来自于极端编程,强调在需要之前构建一个特性的高风险和成本。. -Sometimes we guess we'll need a feature in the future, build it now believing it'll be easier to do as we build other stuff, then realize we guessed wrong and the feature wasn't needed, or needed to be quite different. Other times we guess right, but build a feature too early, and suck up time from the features that are genuinely needed now; we incur an opportunity cost in diluting our energy. +“YAGNI”挑战让我们要记住:即使在某种情况下这是违反直觉的,我们通常也应该推迟建造一些东西,直到目前需要它为止。我们倾向于夸大我们对未来重构的估计,即在需要时稍后添加重构的成本。很可能,以后做起来并不像我们想象的那么困难。 -YAGNI challenges us to remember: even if it's counterintuitive in a situation, we often should postpone building something until it's presently needed. We tend to exaggerate our mental estimates of the future refactoring cost of adding it later when it is needed. Odds are, it won't be as hard to do later as we might assume. +当它应用于函数式编程时,我会给出这样的警告:在本文中会讨论许多有趣的模式,但只因为您发现一些令人兴奋的模式可以应用,在您的代码的应用这些模式可能未必合适。 -As it applies to functional programming, I would offer this admonition: there will be plenty of interesting and compelling patterns discussed in this text, but just because you find some pattern exciting to apply, it may not necessarily be appropriate to do so in a given part of your code. +这就是我不同于许多正式的函数编程人员的地方:仅仅是因为您*可以*将函数编程模式应用于某个东西,但并不意味着您就*应该*将函数编程模式应用于到你的代码上。此外,有很多方法可以分割一个问题,即使您可能已经学习了一种更为复杂的方法,这种方法对维护和可扩展性来说更具“未来证明”,但在这一点上,更简单的函数编程模式可能就足够了。 -This is where I will differ from many formal FPers: just because you *can* apply FP to something doesn't mean you *should* apply FP to it. Moreover, there are many ways to slice a problem, and even though you may have learned a more sophisticated approach that is more "future-proof" to maintenance and extensibility, a simpler FP pattern might be more than sufficient in that spot. +一般来说,我建议您在编写代码时寻求平衡,并在掌握了一些技巧后,在应用函数编程概念时保持保守。在决定某个特定的模式或抽象的时候,考虑是否有助于该部分代码更具可读性,或者它只是引入了尚不保证的巧妙的复杂性时,默认了“YAGNI”原则。 -Generally, I'd recommend seeking balance in what you code, and to be conservative in your application of FP concepts as you get the hang of things. Default to the YAGNI principle in deciding if a certain pattern or abstraction will help that part of the code be more readable or if it's just introducing clever sophistication that isn't (yet) warranted. - -> Reminder, any extensibility point that’s never used isn’t just wasted effort, it’s likely to also get in your way as well +> 提醒一下,任何从未使用过的扩展点不仅仅是浪费精力,还可能妨碍您的工作。 > > Jeremy D. Miller @jeremydmiller 2/20/15 > > https://twitter.com/jeremydmiller/status/568797862441586688 -Remember, every single line of code you write has a reader cost associated with it. That reader may be another team member, or even your future self. Neither of those readers will be impressed with overly clever, unnecessary sophistication just to show off your FP prowess. +记住,您编写的每一行代码都会有相应的阅读成本。看这代码的人可能是团队的成员,甚至是你未来的自己。谁都不会对看起来老练的代码印象深刻,代码只是为了炫耀你的函数编程能力罢了。 -The best code is the code that is most readable in the future because it strikes exactly the right balance between what it can/should be (idealism) and what it must be (pragmatism). +最好的代码是未来可读性最高的代码,因为它在理想与实用之间达到了正确的平衡。 -## Resources +## 资源 -I have drawn on a great many different resources to be able to compose this text. I believe you, too, may benefit from them, so I wanted to take a moment to point them out. +我利用了大量不同的资源来撰写这篇文章。我相信你也会从中受益,下面花点时间简单介绍一下。 -### Books +### 相关书籍 -Some FP/JavaScript books that you should definitely read: +一些你不可错过的函数编程/JS书籍: * [Professor Frisby's Mostly Adequate Guide to Functional Programming](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch1.html) by [Brian Lonsdorf](https://twitter.com/drboolean) * [JavaScript Allongé](https://leanpub.com/javascriptallongesix) by [Reg Braithwaite](https://twitter.com/raganwald) * [Functional JavaScript](http://shop.oreilly.com/product/0636920028857.do) by [Michael Fogus](https://twitter.com/fogus) -### Blogs/sites +### 博客/网站 -Some other authors and content you should check out: +一些作者和内容: * [Fun Fun Function Videos](https://www.youtube.com/watch?v=BMUiFMZr7vk) by [Mattias P Johansson](https://twitter.com/mpjme) * [Awesome FP JS](https://github.com/stoeffel/awesome-fp-js) @@ -236,13 +234,13 @@ Some other authors and content you should check out: * [Functional Programming Jargon](https://github.com/hemanth/functional-programming-jargon#functional-programming-jargon) * [Functional Programming Exercises](https://github.com/InceptionCode/Functional-Programming-Exercises) -### Libraries +### 资源库 -The code snippets in this book largely do not rely on libraries. Each operation that we discover, we'll derive how to implement it in standalone, plain ol' JavaScript. However, as you begin to build more of your real code with FP, you'll soon want a library to provide optimized and highly reliable versions of these commonly accepted utilities. +这本书中的代码片段在很大程度上不依赖于库。我们发现的每一个操作,都将派生出如何在独立的纯javascript中实现它。但是,当您开始使用函数式编程构建更多的实际代码时,您很快就会希望库能够提供这些常见的实用程序的优化和高度可靠的版本。 -By the way, you need to check the documentation for the library functions you use to ensure you know how they work. There will be a lot of similarities in many of them to the code we build on in this text, but there will undoubtedly be some differences, even between popular libraries. +顺便说一下,您需要检查文档中所使用的库函数,以确保您知道它们是如何工作的。它们中的许多与我们在本文中构建的代码有很多相似之处,但毫无疑问,即使在流行的库之间,也会存在一些差异。 -Here are a few popular FP libraries for JavaScript that are a great place to start your exploration with: +下面是几个流行的JavaScript FP库,是您开始探索的好地方: * [Ramda](http://ramdajs.com) * [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide) @@ -251,16 +249,14 @@ Here are a few popular FP libraries for JavaScript that are a great place to sta [Appendix C takes a deeper look at these libraries](apC.md/#stuff-to-investigate) and others. -## Summary +## 总结 -You may have a variety of reasons for starting to read this book, and different expectations of what you'll get out of it. This chapter has explained why I want you to read the book and what I want you to get out of the journey. It also helps you articulate to others (like your fellow developers) why they should come on the journey with you! +你可能有各种各样的理由开始读这本书,以及对你能从中得到什么的不同期望。这一章解释了为什么我要你读这本书,以及我要你从中得到什么。它还可以帮助您向其他人(如您的开发伙伴)清楚地表达为什么他们应该与您一起读这本书! -Functional programming is about writing code that is based on proven principles so we can gain a level of confidence and trust over the code we write and read. We shouldn't be content to write code that we anxiously *hope* works, and then abruptly breathe a sigh of relief when the test suite passes. We should *know* what it will do before we run it, and we should be absolutely confident that we've communicated all these ideas in our code for the benefit of other readers (including our future selves). +函数式编程是关于编写基于经验证的原则的代码,这样我们就可以对编写和读取的代码有自信。我们不应该满足于编写揣测的工作代码,然后测试通过后松了一口气的状态。在运行它之前,我们应该*知道*它将做什么,并且我们应该确信我们已经在代码中为其他读者(包括我们未来的自己)传达了所有这些想法。 -This is the heart of Functional-Light JavaScript. The goal is to learn to effectively communicate with our code but not have to suffocate under mountains of notation or terminology to get there. +这是该书的核心。我们的目标是学会有效地与我们的代码通信,但不必为了达到目的而困惑在符号或术语的复杂中。 -The journey to learning functional programming starts with deeply understanding the nature of what a function is. That's what we tackle in the next chapter. +学习函数编程从深入了解函数的本质开始。这就是我们在下一章要讨论的问题。 ---- - -1Buse, Raymond P. L., and Westley R. Weimer. “Learning a Metric for Code Readability.” IEEE Transactions on Software Engineering, IEEE Press, July 2010, dl.acm.org/citation.cfm?id=1850615. From b30004c434af73a163a39c5a53bfdfe68ca610e4 Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 12 Aug 2019 17:17:39 +0800 Subject: [PATCH 37/63] Update ch1.md --- manuscript/ch1.md | 66 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/manuscript/ch1.md b/manuscript/ch1.md index d79e5c09..d340c992 100644 --- a/manuscript/ch1.md +++ b/manuscript/ch1.md @@ -174,57 +174,55 @@ By having more code that's recognizable at a glance, and thus spending less time ## 客观判断 -Most other FP texts seem to take a top-down approach, but we're going to go the opposite direction: working from the ground up, we'll uncover the basic foundational principles that I believe formal FPers would admit are the scaffolding for everything they do. But for the most part we'll stay arm's length away from most of the intimidating terminology or mathematical notation that can so easily frustrate learners. +大多数FP文本似乎都采取了自上而下的方法,但我们会以相反的方向:从头开始,我们将揭示基本的基本原则,我相信正式的FP使用者会承认这是他们所做一切的脚手架。但在很大程度上,我们会与大多数吓人的术语或数学符号保持一定距离,因为这些术语或数学符号很容易让学习者感到沮丧。 -I believe it's less important what you call something and more important that you understand what it is and how it works. That's not to say there's no importance to shared terminology -- it undoubtedly eases communication among seasoned professionals. But for the learner, I've found it can be distracting. +我相信你所说的东西不那么重要,更重要的是你要了解它是什么以及它是如何工作的。这并不是说共享术语不重要——毫无疑问,它简化了经验丰富的专业人员之间的交流。但对于学习者来说,我发现这会分散注意力。 -So this book will try to focus more on the base concepts and less on the fancy fluff. That's not to say there won't be terminology; there definitely will be. But don't get too wrapped up in the sophisticated words. Wherever necessary, look beyond them to the ideas. +因此,这本书将会更多地集中在基本概念,而不是花里胡哨的说些无意义的事情上。这并不是说没有术语,而是肯定会有术语。但不要过于沉溺于复杂的语言中。在必要的时候,超越他们去考虑这些想法。 -I call the less formal practice herein "Functional-Light Programming" because I think where the formalism of true FP suffers is that it can be quite overwhelming if you're not already accustomed to formal thought. I'm not just guessing; this is my own personal story. Even after teaching FP and writing this book, I can still say that the formalism of terms and notation in FP is very, very difficult for me to process. I've tried, and tried, and I can't seem to get through much of it. +我把这些不太正式的实践称为“轻量编程”,我认为真正的FP的形式主义所受害的地方是,如果你还不习惯于正式的思想,它可能是非常压倒性的。我不只是简单猜测,从我自己的故事中可以说明。即使在教过FP和写过这本书之后,我仍然可以说,FP中的术语和符号的形式主义对我来说是非常难以处理的。我试过又试,但还是觉得难以处理。 -I know many FPers who believe that the formalism itself helps learning. But I think there's clearly a cliff where that only becomes true once you reach a certain comfort with the formalism. If you happen to already have a math background or even some flavors of CS experience, this may come more naturally to you. But some of us don't, and no matter how hard we try, the formalism keeps getting in the way. +我知道许多人相信形式主义的方式有助于学习。但我认为很明显这是一个误解,只有当你对形式主义有了一定的了解后,才会认识到。如果你恰好已经有了数学背景,甚至是一些CS经验,这对你来说可能更自然。但是我们中的一些人没有,不管我们多么努力,形式主义总是阻碍我们。 -So this book introduces the concepts that I believe FP is built on, but comes at it by giving you a boost from below to climb up the cliff wall, rather than condescendingly shouting down at you from the top, prodding you to just figure out how to climb as you go. +所以这本书介绍了我相信函数编程是建立在其基础上的概念,但它是通过从下面给你一个动力来爬上悬崖峭壁,而不是屈尊地从顶部冲你喊叫,督促你去弄清楚怎么爬。 -## How to Find Balance +## 如何找到平衡 -If you've been around programming for very long, chances are you've heard the phrase "YAGNI" before: "You Ain't Gonna Need It". This principle primarily comes from extreme programming, and stresses the high risk and cost of building a feature before it's needed. +如果你已经在编程方面工作很长时间了,很可能你以前听过“YAGNI”这个缩写:“You Ain’t Gonna Need It”(“你不需要它”)。这个原则主要来自于极端编程,强调在需要之前构建一个特性的高风险和成本。. -Sometimes we guess we'll need a feature in the future, build it now believing it'll be easier to do as we build other stuff, then realize we guessed wrong and the feature wasn't needed, or needed to be quite different. Other times we guess right, but build a feature too early, and suck up time from the features that are genuinely needed now; we incur an opportunity cost in diluting our energy. +“YAGNI”挑战让我们要记住:即使在某种情况下这是违反直觉的,我们通常也应该推迟建造一些东西,直到目前需要它为止。我们倾向于夸大我们对未来重构的估计,即在需要时稍后添加重构的成本。很可能,以后做起来并不像我们想象的那么困难。 -YAGNI challenges us to remember: even if it's counterintuitive in a situation, we often should postpone building something until it's presently needed. We tend to exaggerate our mental estimates of the future refactoring cost of adding it later when it is needed. Odds are, it won't be as hard to do later as we might assume. +当它应用于函数式编程时,我会给出这样的警告:在本文中会讨论许多有趣的模式,但只因为您发现一些令人兴奋的模式可以应用,在您的代码的应用这些模式可能未必合适。 -As it applies to functional programming, I would offer this admonition: there will be plenty of interesting and compelling patterns discussed in this text, but just because you find some pattern exciting to apply, it may not necessarily be appropriate to do so in a given part of your code. +这就是我不同于许多正式的函数编程人员的地方:仅仅是因为您*可以*将函数编程模式应用于某个东西,但并不意味着您就*应该*将函数编程模式应用于到你的代码上。此外,有很多方法可以分割一个问题,即使您可能已经学习了一种更为复杂的方法,这种方法对维护和可扩展性来说更具“未来证明”,但在这一点上,更简单的函数编程模式可能就足够了。 -This is where I will differ from many formal FPers: just because you *can* apply FP to something doesn't mean you *should* apply FP to it. Moreover, there are many ways to slice a problem, and even though you may have learned a more sophisticated approach that is more "future-proof" to maintenance and extensibility, a simpler FP pattern might be more than sufficient in that spot. +一般来说,我建议您在编写代码时寻求平衡,并在掌握了一些技巧后,在应用函数编程概念时保持保守。在决定某个特定的模式或抽象的时候,考虑是否有助于该部分代码更具可读性,或者它只是引入了尚不保证的巧妙的复杂性时,默认了“YAGNI”原则。 -Generally, I'd recommend seeking balance in what you code, and to be conservative in your application of FP concepts as you get the hang of things. Default to the YAGNI principle in deciding if a certain pattern or abstraction will help that part of the code be more readable or if it's just introducing clever sophistication that isn't (yet) warranted. - -> Reminder, any extensibility point that’s never used isn’t just wasted effort, it’s likely to also get in your way as well +> 提醒一下,任何从未使用过的扩展点不仅仅是浪费精力,还可能妨碍您的工作。 > > Jeremy D. Miller @jeremydmiller 2/20/15 > > https://twitter.com/jeremydmiller/status/568797862441586688 -Remember, every single line of code you write has a reader cost associated with it. That reader may be another team member, or even your future self. Neither of those readers will be impressed with overly clever, unnecessary sophistication just to show off your FP prowess. +记住,您编写的每一行代码都会有相应的阅读成本。看这代码的人可能是团队的成员,甚至是你未来的自己。谁都不会对看起来老练的代码印象深刻,代码只是为了炫耀你的函数编程能力罢了。 -The best code is the code that is most readable in the future because it strikes exactly the right balance between what it can/should be (idealism) and what it must be (pragmatism). +最好的代码是未来可读性最高的代码,因为它在理想与实用之间达到了正确的平衡。 -## Resources +## 资源 -I have drawn on a great many different resources to be able to compose this text. I believe you, too, may benefit from them, so I wanted to take a moment to point them out. +我利用了大量不同的资源来撰写这篇文章。我相信你也会从中受益,下面花点时间简单介绍一下。 -### Books +### 相关书籍 -Some FP/JavaScript books that you should definitely read: +一些你不可错过的函数编程/JS书籍: * [Professor Frisby's Mostly Adequate Guide to Functional Programming](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch1.html) by [Brian Lonsdorf](https://twitter.com/drboolean) * [JavaScript Allongé](https://leanpub.com/javascriptallongesix) by [Reg Braithwaite](https://twitter.com/raganwald) * [Functional JavaScript](http://shop.oreilly.com/product/0636920028857.do) by [Michael Fogus](https://twitter.com/fogus) -### Blogs/sites +### 博客/网站 -Some other authors and content you should check out: +一些作者和内容: * [Fun Fun Function Videos](https://www.youtube.com/watch?v=BMUiFMZr7vk) by [Mattias P Johansson](https://twitter.com/mpjme) * [Awesome FP JS](https://github.com/stoeffel/awesome-fp-js) @@ -236,31 +234,29 @@ Some other authors and content you should check out: * [Functional Programming Jargon](https://github.com/hemanth/functional-programming-jargon#functional-programming-jargon) * [Functional Programming Exercises](https://github.com/InceptionCode/Functional-Programming-Exercises) -### Libraries +### 资源库 -The code snippets in this book largely do not rely on libraries. Each operation that we discover, we'll derive how to implement it in standalone, plain ol' JavaScript. However, as you begin to build more of your real code with FP, you'll soon want a library to provide optimized and highly reliable versions of these commonly accepted utilities. +这本书中的代码片段在很大程度上不依赖于库。我们发现的每一个操作,都将派生出如何在独立的纯javascript中实现它。但是,当您开始使用函数式编程构建更多的实际代码时,您很快就会希望库能够提供这些常见的实用程序的优化和高度可靠的版本。 -By the way, you need to check the documentation for the library functions you use to ensure you know how they work. There will be a lot of similarities in many of them to the code we build on in this text, but there will undoubtedly be some differences, even between popular libraries. +顺便说一下,您需要检查文档中所使用的库函数,以确保您知道它们是如何工作的。它们中的许多与我们在本文中构建的代码有很多相似之处,但毫无疑问,即使在流行的库之间,也会存在一些差异。 -Here are a few popular FP libraries for JavaScript that are a great place to start your exploration with: +下面是几个流行的JavaScript FP库,是您开始探索的好地方: * [Ramda](http://ramdajs.com) * [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide) * [functional.js](http://functionaljs.com/) * [Immutable.js](https://github.com/facebook/immutable-js) -[Appendix C takes a deeper look at these libraries](apC.md/#stuff-to-investigate) and others. +[附录C](apC.md/#stuff-to-investigate) 对这些库和其他库进行了更深入的研究. -## Summary +## 总结 -You may have a variety of reasons for starting to read this book, and different expectations of what you'll get out of it. This chapter has explained why I want you to read the book and what I want you to get out of the journey. It also helps you articulate to others (like your fellow developers) why they should come on the journey with you! +你可能有各种各样的理由开始读这本书,以及对你能从中得到什么的不同期望。这一章解释了为什么我要你读这本书,以及我要你从中得到什么。它还可以帮助您向其他人(如您的开发伙伴)清楚地表达为什么他们应该与您一起读这本书! -Functional programming is about writing code that is based on proven principles so we can gain a level of confidence and trust over the code we write and read. We shouldn't be content to write code that we anxiously *hope* works, and then abruptly breathe a sigh of relief when the test suite passes. We should *know* what it will do before we run it, and we should be absolutely confident that we've communicated all these ideas in our code for the benefit of other readers (including our future selves). +函数式编程是关于编写基于经验证的原则的代码,这样我们就可以对编写和读取的代码有自信。我们不应该满足于编写揣测的工作代码,然后测试通过后松了一口气的状态。在运行它之前,我们应该*知道*它将做什么,并且我们应该确信我们已经在代码中为其他读者(包括我们未来的自己)传达了所有这些想法。 -This is the heart of Functional-Light JavaScript. The goal is to learn to effectively communicate with our code but not have to suffocate under mountains of notation or terminology to get there. +这是该书的核心。我们的目标是学会有效地与我们的代码通信,但不必为了达到目的而困惑在符号或术语的复杂中。 -The journey to learning functional programming starts with deeply understanding the nature of what a function is. That's what we tackle in the next chapter. +学习函数编程从深入了解函数的本质开始。这就是我们在下一章要讨论的问题。 ---- - -1Buse, Raymond P. L., and Westley R. Weimer. “Learning a Metric for Code Readability.” IEEE Transactions on Software Engineering, IEEE Press, July 2010, dl.acm.org/citation.cfm?id=1850615. From 3dbdd4850a89ee24be8ae8490522e6afac240677 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 16 Aug 2019 08:54:50 +0800 Subject: [PATCH 38/63] Update ch2.md --- manuscript/ch2.md | 76 ++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/manuscript/ch2.md b/manuscript/ch2.md index 2f815440..547d82a2 100644 --- a/manuscript/ch2.md +++ b/manuscript/ch2.md @@ -1,61 +1,60 @@ -# Functional-Light JavaScript -# Chapter 2: The Nature Of Functions +# 章节2: 函数的性质 -Functional Programming is **not just programming with the `function` keyword.** Oh, if only it was that easy -- I could end the book right here! Nevertheless, functions really *are* at the center of FP. And it's how we use functions that makes our code *functional*. +函数式编程不仅仅是用“function”关键字进行定义来编程。如果这么简单的话,我可以在这里结束这本书!函数确实是函数式编程的核心。而且函数的方式使我们的代码*起作用*。 -But how sure are you that you know what *function* really means? +但是你有多确定“函数”的真正含义? -In this chapter, we're going to lay the groundwork for the rest of the book by exploring all the foundational aspects of functions. Actually, this is a review of all the things even a non-FP programmer should know about functions. But certainly if we want to get the most out of FP concepts, it's essential we *know* functions inside and out. +在这一章中,我们将通过探索函数的所有基本面,为本书的其余部分奠定基础。实际上,这是对所有内容的回顾,即使是非函数式编程程序员也应该了解函数。但当然,如果我们想从函数式编程的概念中得到最大的好处,我们就必须了解内部和外部的函数。 -Brace yourself, because there's a lot more to the function than you may have realized. +打起精神来,因为这个功能比你想象的要多得多。 -## What Is a Function? +## 什么是函数? -The question "What is a function?" superficially seems to have an obvious answer: a function is a collection of code that can be executed one or more times. +这个问题,从表面上看,似乎有一个显而易见的答案:函数是可以执行一次或多次的代码集合。 -While this definition is reasonable, it's missing some very important essence that is the core of a *function* as it applies to FP. So let's dig below the surface to understand functions more completely. +虽然这个定义看起来很合理,但它缺少一些非常重要的本质,即*函数*的核心,因为它适用于函数式编程。因此,让我们从表面开始挖掘,以更全面地理解函数。 -### Brief Math Review +### 简单的数学复习 -I know I've promised we'd stay away from math as much as possible, but bear with me for a moment as we quickly observe some fundamental things about functions and graphs from algebra before we proceed. +我知道我答应过我们会尽量远离数学,但是在我们继续之前,请稍等片刻,因为我们很快就能从代数中观察到一些关于函数和图的基本知识。 -Do you remember learning anything about `f(x)` back in school? What about the equation `y = f(x)`? +你记得在学校里学过“f(x)”或者“y=f(x)”吗? -Let's say an equation is defined like this: f(x) = 2x2 + 3. What does that mean? What does it mean to graph that equation? Here's the graph: +假设一个方程是这样定义的: f(x) = 2x2 + 3. 那是什么意思?用图表表示这个方程意味着什么?下面是图表:

-What you can notice is that for any value of `x`, say `2`, if you plug it into the equation, you get `11`. What is `11`, though? It's the *return value* of the `f(x)` function, which earlier we said represents a `y` value. +你能注意到的是,对于x的任何值,比如2,如果你把它插入方程,你得到11。11是什么?它是f(x)函数的返回值,前面我们说它代表y值。 -In other words, we can choose to interpret the input and output values as a point at `(2,11)` on that curve in the graph. And for every value of `x` we plug in, we get another `y` value that pairs with it as a coordinate for a point. Another is `(0,3)`, and another is `(-1,5)`. Put all those points together, and you have the graph of that parabolic curve as shown here. +换句话说,我们可以选择将输入和输出值解释为图中曲线上`(2,11)`处的点。对于我们插入的每一个'x'值,我们得到另一个'y'值,作为一个点的坐标与它配对。另一个是`(0,3)`,另一个是`(-1,5)`。把所有这些点放在一起,就得到了抛物线图,如图所示。 -So what's any of this got to do with FP? +那么,这和函数式编程有什么关系呢? -In math, a function always takes input(s), and always gives an output. A term you'll often hear around FP is "morphism"; this is a fancy way of describing a set of values that maps to another set of values, like the inputs of a function related to the outputs of that function. +在数学中,函数总是有输入并有输出。在函数式编程中经常听到的一个术语是’态射‘(morphism);两个数学结构之间保持结构的一种过程抽象方法,例如与该函数的输出对应另一函数的输入。 -In algebraic math, those inputs and outputs are often interpreted as components of coordinates to be graphed. In our programs, however, we can define functions with all sorts of input(s) and output(s), even though they'll rarely be interpreted as a visually plotted curve on a graph. +在代数数学中,这些输入和输出通常被解释为要绘制图形的坐标的组成部分。然而,在我们的程序中,虽然很少被解释为图形上的可视绘制曲线,当时我们可以定义具有各种输入和输出的函数。 -### Function vs Procedure +### 功能与程序 -So why all the talk of math and graphs? Because essentially Functional Programming is about embracing using functions as *functions* in this mathematical sense. +那么,为什么老是在讨论数学与图表呢?因为本质上,函数式编程就是在数学意义上接受使用函数方法作为特定程序。 -You may be more accustomed to thinking of functions as procedures. What's the difference? A procedure is an arbitrary collection of functionality. It may have inputs, it may not. It may have an output (`return` value), it may not. +您可能更习惯于将函数视为过程。有什么区别?过程是功能的任意集合。它可能有输入,也可能没有。它可能有输出(返回一个值),也可能没有。 -A function takes input(s) and definitely always has a `return` value. +函数接受输入,并且一定有一个“返回”值。 -If you plan to do Functional Programming, **you should be using functions as much as possible**, and trying to avoid procedures wherever possible. All your `function`s should take input(s) and return output(s). +如果您计划进行函数编程,**您应该尽可能多地使用函数**,并尽可能避免使用过程。所有的“函数”都应该接受输入并返回输出。 -Why? The answer to that will have many levels of meaning that we'll uncover throughout this book. +为什么这么做?这有很多层面的意义,我们会在本书中揭露。 -## Function Input +## 函数的输入 -So far, we can conclude that functions must expect input. But let's dig into how function inputs work. +到目前为止,我们可以得出这样的结论:函数必须有输入。但让我们来深入研究函数输入是如何工作的。 -You sometimes hear people refer to these inputs as "arguments" and sometimes as "parameters". So what's that all about? +您有时会听到人们将这些输入称为“参数”,有时称为“因素”。那这是怎么回事? -*Arguments* are the values you pass in, and *parameters* are the named variables inside the function that receive those passed-in values. Example: +*参数*是您传入的值,*因素*是接收传入值的函数内的命名变量。例子: ```js function foo(x,y) { @@ -66,16 +65,15 @@ var a = 3; foo( a, a * 2 ); ``` +`“a”和“a*2”是函数“foo(…)”调用的*参数*,“x”和“y”是接收参数值的*参数*(分别为“3”和“6”(“a*2”的结果))。 -`a` and `a * 2` (actually, the result of `a * 2`, which is `6`) are the *arguments* to the `foo(..)` call. `x` and `y` are the *parameters* that receive the argument values (`3` and `6`, respectively). +**注意:**在javascript中,不要求*参数*的数量与函数*因素*的数量匹配。如果传递的*参数*多于声明接收它们的函数*参数*,那么这些值就不会受到影响。这些值可以通过几种不同的方式访问,包括以前可能听说过的“arguments”对象。如果传递的*参数*少于声明的函数*参数*,则每个不匹配的参数都将被视为“未定义”变量,这意味着它在函数的作用域内存在并可用,但只以空的“未定义”值开始。 -**Note:** In JavaScript, there's no requirement that the number of *arguments* matches the number of *parameters*. If you pass more *arguments* than you have declared *parameters* to receive them, the values pass in just fine untouched. These values can be accessed in a few different ways, including the old-school `arguments` object you may have heard of before. If you pass fewer *arguments* than the declared *parameters*, each unmatched parameter is treated as an "undefined" variable, meaning it's present and available in the scope of the function, but just starts out with the empty `undefined` value. +### 默认参数 -### Defaulting Parameters +从ES6开始,参数可以声明*默认值*。如果没有传递该参数的参数,或者传递了值“undefined”,则将替换默认的赋值表达式。 -As of ES6, parameters can declare *default values*. In the case where the argument for that parameter is not passed, or it's passed the value `undefined`, the default assignment expression is substituted. - -Consider: +想一想: ```js function foo(x = 3) { @@ -87,20 +85,18 @@ foo( undefined ); // 3 foo( null ); // null foo( 0 ); // 0 ``` +考虑有助于函数可用性的默认情况是一个很好的实践。然而,在读取和理解函数如何被调用的变化方面,默认参数可能会导致更复杂的问题。在多大程度上依赖此功能方面要谨慎。 -It's always a good practice to think about any default cases that can aid the usability of your functions. However, defaulting parameters can lead to more complexity in terms of reading and understanding the variations of how a function is called. Be judicious in how much you rely on this feature. +### 计数输入 -### Counting Inputs - -The number of arguments a function "expects" -- how many arguments you'll likely want to pass to it -- is determined by the number of parameters that are declared: +函数“预期”的参数个数由声明的参数个数决定,预期个数就是您可能希望传递给它的参数个数: ```js function foo(x,y,z) { // .. } ``` - -`foo(..)` *expects* three arguments, because it has three declared parameters. This count has a special term: arity. Arity is the number of parameters in a function declaration. The arity of `foo(..)` is `3`. +` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数数。“foo(…)”的参数数量为“3”。 Furthermore, a function with arity 1 is also called "unary", a function with arity 2 is also called "binary", and a function with arity 3 or higher is called "n-ary". From 2fc562bdf665dea2bf0be12c6d8ea54c155ee66b Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 19 Aug 2019 09:24:52 +0800 Subject: [PATCH 39/63] Update ch2.md --- manuscript/ch2.md | 51 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/manuscript/ch2.md b/manuscript/ch2.md index 547d82a2..92c9a21c 100644 --- a/manuscript/ch2.md +++ b/manuscript/ch2.md @@ -96,11 +96,12 @@ function foo(x,y,z) { // .. } ``` -` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数数。“foo(…)”的参数数量为“3”。 +` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数个数。“foo(…)”的参数数量为“3”。 -Furthermore, a function with arity 1 is also called "unary", a function with arity 2 is also called "binary", and a function with arity 3 or higher is called "n-ary". +此外,一个参数的函数也称为“一元”函数,二个参数的函数也称为“二元”函数,n个参数或更高的函数称为“n元”函数。 -You may wish to inspect a function reference during the runtime of a program to determine its arity. This can be done with the `length` property of that function reference: + +您可能希望在程序运行时检查函数引用以确定其参数个数。这可以通过函数引用的“length”属性来实现: ```js function foo(x,y,z) { @@ -109,14 +110,13 @@ function foo(x,y,z) { foo.length; // 3 ``` +在执行期间确定参数个数的一个原因是,一段代码可以从多个地方引用一个函数,并根据参数个数的不同发送不同的值。 -One reason for determining the arity during execution would be if a piece of code received a function reference from multiple sources, and sent different values depending on the arity of each. - -For example, imagine a case where an `fn` function reference could expect one, two, or three arguments, but you always want to just pass a variable `x` in the last position: +例如,假设一个情况,一个“fn”函数引用可能需要一个、两个或三个参数,但您总是希望在最后一个位置传递一个变量“x”: ```js -// `fn` is set to some function reference -// `x` exists with some value +// `fn` 设置为某个函数引用 +// `x` 存在一些值 if (fn.length == 1) { fn( x ); @@ -128,8 +128,9 @@ else if (fn.length == 3) { fn( undefined, undefined, x ); } ``` +**提示:**函数的“length”属性是只读的,声明函数时就已经确定。它被看作是一段描述函数预期用途的元数据。 -**Tip:** The `length` property of a function is read-only and it's determined at the time you declare the function. It should be thought of as essentially a piece of metadata that describes something about the intended usage of the function. +需要注意的一点是,某些类型的参数列表可能会使函数的“length”属性与您可能期望的不同: One gotcha to be aware of is that certain kinds of parameter list variations can make the `length` property of the function report something different than you might expect: @@ -150,8 +151,7 @@ foo.length; // 1 bar.length; // 1 baz.length; // 1 ``` - -What about counting the number of arguments the current function call received? This was once trivial, but now the situation is slightly more complicated. Each function has an `arguments` object (array-like) available that holds a reference to each of the arguments passed in. You can then inspect the `length` property of `arguments` to figure out how many were actually passed: +要计算当前函数调用接收到的参数个数?这曾经是微不足道的,但现在情况稍微复杂一些。每个函数都有一个“arguments”对象(类似于数组),用于保存对传入的每个参数的引用。然后可以检查“arguments”的“length”属性,以确定实际传递了多少个: ```js function foo(x,y,z) { @@ -161,23 +161,23 @@ function foo(x,y,z) { foo( 3, 4 ); // 2 ``` -As of ES5 (and strict mode, specifically), `arguments` is considered by some to be sort of deprecated; many avoid using it if possible. In JS, we "never" break backward compatibility no matter how helpful that may be for future progress, so `arguments` will never be removed. But it's now commonly suggested that you avoid using it whenever possible. +从ES5(特别是严格模式)开始,“arguments”被一些人认为是不赞成使用的;许多人会避免使用它。在JS中,我们“从不”破坏向后兼容性,不管这对将来的进展有多大帮助,所以“arguments”永远不会被删除。但现在普遍建议尽可能避免使用它 -However, I suggest that `arguments.length`, and only that, is OK to keep using for those cases where you need to care about the passed number of arguments. A future version of JS might possibly add a feature that offers the ability to determine the number of arguments passed without consulting `arguments.length`; if that happens, then we can fully drop usage of `arguments`! +但是,我建议“arguments.length”,仅用于可以需要传递的参数数量的情况。未来版本的JS可能会添加一个功能,该功能可以确定传递的参数的数量,而不需要查询“arguments.length”;如果发生这种情况,那么我们可以完全放弃使用“arguments”! -Be careful: **never** access arguments positionally, like `arguments[1]`. Stick to `arguments.length` only, and only if you must. +小心:**从不**按位置访问参数,如“arguments[1]”。如果必须的话,只保留对“arguments.length”的引用。 -Except, how will you access an argument that was passed in a position beyond the declared parameters? I'll answer that in a moment; but first, take a step back and ask yourself, "Why would I want to do that?" Seriously. Think about that closely for a minute. +另外,如何在声明参数之外的位置访问传递的参数?我稍后回答;但首先仔细考虑一下问问自己,“我为什么要这样做?”。 -It should be pretty rare that this occurs; it shouldn't be something you regularly expect or rely on when writing your functions. If you find yourself in such a scenario, spend an extra 20 minutes trying to design the interaction with that function in a different way. Name that extra argument even if it's exceptional. +这种情况应该很少发生;它不应该是您在编写函数时经常需要使用的方式。如果您发现自己处于这样的场景中,请花费额外的20分钟尝试以不同的方式设计与该函数的交互。命名这个额外的论点,即使它是例外的。 -A function signature that accepts an indeterminate amount of arguments is referred to as a variadic function. Some people prefer this style of function design, but I think you'll find that often the FPer wants to avoid these where possible. +接受不确定数量参数的函数称为可变函数。有些人更喜欢这种类型的功能设计,但我认为您会发现,一般情况下,函数编程人员通常会避免这样设计。 -OK, enough harping on that point. +好吧,就这一点说得够多了。 -Say you do need to access the arguments in a positional array-like way, possibly because you're accessing an argument that doesn't have a formal parameter at that position. How do we do it? +假设您确实需要以类数组的方式访问参数,可能是您访问的参数在该位置没有正式参数的需要。那我们要怎么做? -ES6 to the rescue! Let's declare our function with the `...` operator -- variously referred to as "spread", "rest", or (my preference) "gather": +ES6可以解决这一点!可以用`…`操作符来声明我们的函数——各种各样地称为“spread”、“rest”或“gather”(我的首选项): ```js function foo(x,y,z,...args) { @@ -185,7 +185,7 @@ function foo(x,y,z,...args) { } ``` -See the `...args` in the parameter list? That's an ES6 declarative form that tells the engine to collect (ahem, "gather") all remaining arguments (if any) not assigned to named parameters, and put them in a real array named `args`. `args` will always be an array, even if it's empty. But it **will not** include values that are assigned to the `x`, `y`, and `z` parameters, only anything else that's passed in beyond those first three values: +看到参数列表中的“…args”?这是一个ES6声明形式,它告诉引擎收集所有未分配给命名参数的剩余参数,并将它们放入名为“args”的实数数组中。` args`将始终是一个数组,即使它是空的。但它**不会**包括分配给“x”、“y”和“z”参数的值,只包括超出前三个值的其他值: ```js function foo(x,y,z,...args) { @@ -198,19 +198,18 @@ foo( 1, 2, 3, 4 ); // 1 2 3 [ 4 ] foo( 1, 2, 3, 4, 5 ); // 1 2 3 [ 4, 5 ] ``` -So, if you *really* want to design a function that can account for an arbitrary number of arguments to be passed in, use `...args` (or whatever name you like) on the end. Now, you'll have a real, non-deprecated, non-yucky array to access those argument values from. +因此,如果您*真的*想设计一个函数来解释要传入的任意数量的参数,请在末尾使用“…args”(也可以使用其他变量名称)。现在,您将拥有一个真正的、不推荐使用的、不易出错的数组来访问这些参数值。 -Just pay attention to the fact that the value `4` is at position `0` of that `args`, not position `3`. And its `length` value won't include those three `1`, `2`, and `3` values. `...args` gathers everything else, not including the `x`, `y`, and `z`. +注意值“4”位于“args”的“0”位置,而不是“3”位置。它的“length”值不包括这三个“1”、“2”和“3”值。`…args'收集其他所有的参数,不包括'x'、'y'和'z'。 -You *can* use the `...` operator in the parameter list even if there's no other formal parameters declared: +您*可以*使用参数列表中的“…”扩展运算符,即使没有声明其他正式参数: ```js function foo(...args) { // .. } ``` - -Now `args` will be the full array of arguments, whatever they are, and you can use `args.length` to know exactly how many arguments have been passed in. And you're safe to use `args[1]` or `args[317]` if you so choose. Please don't pass in 318 arguments, though. +现在,“args”将是参数的完整数组,不管它们是什么,您可以使用“args.length”来确切知道传入了多少个参数。如果你这样做的话,使用“args[1]”或“args[317]”是安全的。不过,请不要传递318个参数。 ### Arrays of Arguments From 0f8a0a48995fce07540bf95e0dc134c095b8cc22 Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 19 Aug 2019 09:44:19 +0800 Subject: [PATCH 40/63] Update ch2.md --- manuscript/ch2.md | 67 ++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/manuscript/ch2.md b/manuscript/ch2.md index 547d82a2..d363b336 100644 --- a/manuscript/ch2.md +++ b/manuscript/ch2.md @@ -96,11 +96,12 @@ function foo(x,y,z) { // .. } ``` -` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数数。“foo(…)”的参数数量为“3”。 +` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数个数。“foo(…)”的参数数量为“3”。 -Furthermore, a function with arity 1 is also called "unary", a function with arity 2 is also called "binary", and a function with arity 3 or higher is called "n-ary". +此外,一个参数的函数也称为“一元”函数,二个参数的函数也称为“二元”函数,n个参数或更高的函数称为“n元”函数。 -You may wish to inspect a function reference during the runtime of a program to determine its arity. This can be done with the `length` property of that function reference: + +您可能希望在程序运行时检查函数引用以确定其参数个数。这可以通过函数引用的“length”属性来实现: ```js function foo(x,y,z) { @@ -109,14 +110,13 @@ function foo(x,y,z) { foo.length; // 3 ``` +在执行期间确定参数个数的一个原因是,一段代码可以从多个地方引用一个函数,并根据参数个数的不同发送不同的值。 -One reason for determining the arity during execution would be if a piece of code received a function reference from multiple sources, and sent different values depending on the arity of each. - -For example, imagine a case where an `fn` function reference could expect one, two, or three arguments, but you always want to just pass a variable `x` in the last position: +例如,假设一个情况,一个“fn”函数引用可能需要一个、两个或三个参数,但您总是希望在最后一个位置传递一个变量“x”: ```js -// `fn` is set to some function reference -// `x` exists with some value +// `fn` 设置为某个函数引用 +// `x` 存在一些值 if (fn.length == 1) { fn( x ); @@ -128,10 +128,10 @@ else if (fn.length == 3) { fn( undefined, undefined, x ); } ``` +**提示:**函数的“length”属性是只读的,声明函数时就已经确定。它被看作是一段描述函数预期用途的元数据。 -**Tip:** The `length` property of a function is read-only and it's determined at the time you declare the function. It should be thought of as essentially a piece of metadata that describes something about the intended usage of the function. +需要注意的一点是,某些类型的参数列表可能会使函数的“length”属性与您可能期望的不同: -One gotcha to be aware of is that certain kinds of parameter list variations can make the `length` property of the function report something different than you might expect: ```js function foo(x,y = 2) { @@ -150,8 +150,7 @@ foo.length; // 1 bar.length; // 1 baz.length; // 1 ``` - -What about counting the number of arguments the current function call received? This was once trivial, but now the situation is slightly more complicated. Each function has an `arguments` object (array-like) available that holds a reference to each of the arguments passed in. You can then inspect the `length` property of `arguments` to figure out how many were actually passed: +要计算当前函数调用接收到的参数个数?这曾经是微不足道的,但现在情况稍微复杂一些。每个函数都有一个“arguments”对象(类似于数组),用于保存对传入的每个参数的引用。然后可以检查“arguments”的“length”属性,以确定实际传递了多少个: ```js function foo(x,y,z) { @@ -161,23 +160,23 @@ function foo(x,y,z) { foo( 3, 4 ); // 2 ``` -As of ES5 (and strict mode, specifically), `arguments` is considered by some to be sort of deprecated; many avoid using it if possible. In JS, we "never" break backward compatibility no matter how helpful that may be for future progress, so `arguments` will never be removed. But it's now commonly suggested that you avoid using it whenever possible. +从ES5(特别是严格模式)开始,“arguments”被一些人认为是不赞成使用的;许多人会避免使用它。在JS中,我们“从不”破坏向后兼容性,不管这对将来的进展有多大帮助,所以“arguments”永远不会被删除。但现在普遍建议尽可能避免使用它 -However, I suggest that `arguments.length`, and only that, is OK to keep using for those cases where you need to care about the passed number of arguments. A future version of JS might possibly add a feature that offers the ability to determine the number of arguments passed without consulting `arguments.length`; if that happens, then we can fully drop usage of `arguments`! +但是,我建议“arguments.length”,仅用于可以需要传递的参数数量的情况。未来版本的JS可能会添加一个功能,该功能可以确定传递的参数的数量,而不需要查询“arguments.length”;如果发生这种情况,那么我们可以完全放弃使用“arguments”! -Be careful: **never** access arguments positionally, like `arguments[1]`. Stick to `arguments.length` only, and only if you must. +小心:**从不**按位置访问参数,如“arguments[1]”。如果必须的话,只保留对“arguments.length”的引用。 -Except, how will you access an argument that was passed in a position beyond the declared parameters? I'll answer that in a moment; but first, take a step back and ask yourself, "Why would I want to do that?" Seriously. Think about that closely for a minute. +另外,如何在声明参数之外的位置访问传递的参数?我稍后回答;但首先仔细考虑一下问问自己,“我为什么要这样做?”。 -It should be pretty rare that this occurs; it shouldn't be something you regularly expect or rely on when writing your functions. If you find yourself in such a scenario, spend an extra 20 minutes trying to design the interaction with that function in a different way. Name that extra argument even if it's exceptional. +这种情况应该很少发生;它不应该是您在编写函数时经常需要使用的方式。如果您发现自己处于这样的场景中,请花费额外的20分钟尝试以不同的方式设计与该函数的交互。命名这个额外的论点,即使它是例外的。 -A function signature that accepts an indeterminate amount of arguments is referred to as a variadic function. Some people prefer this style of function design, but I think you'll find that often the FPer wants to avoid these where possible. +接受不确定数量参数的函数称为可变函数。有些人更喜欢这种类型的功能设计,但我认为您会发现,一般情况下,函数编程人员通常会避免这样设计。 -OK, enough harping on that point. +好吧,就这一点说得够多了。 -Say you do need to access the arguments in a positional array-like way, possibly because you're accessing an argument that doesn't have a formal parameter at that position. How do we do it? +假设您确实需要以类数组的方式访问参数,可能是您访问的参数在该位置没有正式参数的需要。那我们要怎么做? -ES6 to the rescue! Let's declare our function with the `...` operator -- variously referred to as "spread", "rest", or (my preference) "gather": +ES6可以解决这一点!可以用`…`操作符来声明我们的函数——各种各样地称为“spread”、“rest”或“gather”(我的首选项): ```js function foo(x,y,z,...args) { @@ -185,7 +184,7 @@ function foo(x,y,z,...args) { } ``` -See the `...args` in the parameter list? That's an ES6 declarative form that tells the engine to collect (ahem, "gather") all remaining arguments (if any) not assigned to named parameters, and put them in a real array named `args`. `args` will always be an array, even if it's empty. But it **will not** include values that are assigned to the `x`, `y`, and `z` parameters, only anything else that's passed in beyond those first three values: +看到参数列表中的“…args”?这是一个ES6声明形式,它告诉引擎收集所有未分配给命名参数的剩余参数,并将它们放入名为“args”的实数数组中。` args`将始终是一个数组,即使它是空的。但它**不会**包括分配给“x”、“y”和“z”参数的值,只包括超出前三个值的其他值: ```js function foo(x,y,z,...args) { @@ -198,23 +197,22 @@ foo( 1, 2, 3, 4 ); // 1 2 3 [ 4 ] foo( 1, 2, 3, 4, 5 ); // 1 2 3 [ 4, 5 ] ``` -So, if you *really* want to design a function that can account for an arbitrary number of arguments to be passed in, use `...args` (or whatever name you like) on the end. Now, you'll have a real, non-deprecated, non-yucky array to access those argument values from. +因此,如果您*真的*想设计一个函数来解释要传入的任意数量的参数,请在末尾使用“…args”(也可以使用其他变量名称)。现在,您将拥有一个真正的、不推荐使用的、不易出错的数组来访问这些参数值。 -Just pay attention to the fact that the value `4` is at position `0` of that `args`, not position `3`. And its `length` value won't include those three `1`, `2`, and `3` values. `...args` gathers everything else, not including the `x`, `y`, and `z`. +注意值“4”位于“args”的“0”位置,而不是“3”位置。它的“length”值不包括这三个“1”、“2”和“3”值。`…args'收集其他所有的参数,不包括'x'、'y'和'z'。 -You *can* use the `...` operator in the parameter list even if there's no other formal parameters declared: +您*可以*使用参数列表中的“…”扩展运算符,即使没有声明其他正式参数: ```js function foo(...args) { // .. } ``` +现在,“args”将是参数的完整数组,不管它们是什么,您可以使用“args.length”来确切知道传入了多少个参数。如果你这样做的话,使用“args[1]”或“args[317]”是安全的。不过,请不要传递318个参数。 -Now `args` will be the full array of arguments, whatever they are, and you can use `args.length` to know exactly how many arguments have been passed in. And you're safe to use `args[1]` or `args[317]` if you so choose. Please don't pass in 318 arguments, though. +### 参数数组 -### Arrays of Arguments - -What if you wanted to pass along an array of values as the arguments to a function call? +如果您想将一个数组作为参数传递给函数调用,该怎么办? ```js function foo(...args) { @@ -226,21 +224,20 @@ var arr = [ 1, 2, 3, 4, 5 ]; foo( ...arr ); // 4 ``` -Our new friend `...` is used, but now not just in the parameter list; it's also used in the argument list at the call-site. It has the opposite behavior in this context. In a parameter list, we said it *gathered* arguments together. In an argument list, it *spreads* them out. So the contents of `arr` are actually spread out as individual arguments to the `foo(..)` call. Do you see how that's different from just passing in a reference to the whole `arr` array? +上面“…”被使用了,但现在不仅在参数列表中;它也在调用函数的参数列表中使用。在这种情况下,它有相反的行为。在参数列表中,我们说它*收集*参数个数。在参数列表中,`...`是*展开*了数组的参数。所以“arr”的内容实际上是作为“foo(…)”调用的单个参数展开的。那这和仅仅传递一个对整个“arr”数组的引用有什么不同吗? -By the way, multiple values and `...` spreadings can be interleaved, as you see fit: +还有,多个值和`…`扩展参数可以交错: ```js var arr = [ 2 ]; foo( 1, ...arr, 3, ...[4,5] ); // 4 ``` +思考这个“…”:在值列表位置,它是*展开*的功能。在赋值位置的参数列表收集参数,赋值给使用参数。 -Think of `...` in this symmetric sense: in a value-list position, it *spreads*. In an assignment position -- like a parameter list, because arguments get *assigned to* parameters -- it *gathers*. - -Whichever behavior you invoke, `...` makes working with arrays of arguments much easier. Gone are the days of `slice(..)`, `concat(..)`, and `apply(..)` to wrangle our argument value arrays. +无论您调用哪种行为,`…`都会使处理参数数组更加容易。“slice(…)”、“concat(…)”和“apply(…)”的日子已经变得没有用了,这些方法都需要数组参数值。 -**Tip:** Actually, these methods are not entirely useless. There will be a few places we rely on them throughout the code in this book. But certainly in most places, `...` will be much more declaratively readable, and preferable as a result. +**提示:**实际上,这些方法并不是完全无用的。在本书的整个代码中,我们将有一些地方依赖它们。但是,在大多数地方,`…`将会更加声明性地可读,因此更可取。 ### Parameter Destructuring From af903f05813a634268c03f4f572875e7544c1cb0 Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 20 Aug 2019 09:55:33 +0800 Subject: [PATCH 41/63] Update ch2.md --- manuscript/ch2.md | 123 ++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 63 deletions(-) diff --git a/manuscript/ch2.md b/manuscript/ch2.md index 547d82a2..796f8233 100644 --- a/manuscript/ch2.md +++ b/manuscript/ch2.md @@ -96,11 +96,12 @@ function foo(x,y,z) { // .. } ``` -` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数数。“foo(…)”的参数数量为“3”。 +` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数个数。“foo(…)”的参数数量为“3”。 -Furthermore, a function with arity 1 is also called "unary", a function with arity 2 is also called "binary", and a function with arity 3 or higher is called "n-ary". +此外,一个参数的函数也称为“一元”函数,二个参数的函数也称为“二元”函数,n个参数或更高的函数称为“n元”函数。 -You may wish to inspect a function reference during the runtime of a program to determine its arity. This can be done with the `length` property of that function reference: + +您可能希望在程序运行时检查函数引用以确定其参数个数。这可以通过函数引用的“length”属性来实现: ```js function foo(x,y,z) { @@ -109,14 +110,13 @@ function foo(x,y,z) { foo.length; // 3 ``` +在执行期间确定参数个数的一个原因是,一段代码可以从多个地方引用一个函数,并根据参数个数的不同发送不同的值。 -One reason for determining the arity during execution would be if a piece of code received a function reference from multiple sources, and sent different values depending on the arity of each. - -For example, imagine a case where an `fn` function reference could expect one, two, or three arguments, but you always want to just pass a variable `x` in the last position: +例如,假设一个情况,一个“fn”函数引用可能需要一个、两个或三个参数,但您总是希望在最后一个位置传递一个变量“x”: ```js -// `fn` is set to some function reference -// `x` exists with some value +// `fn` 设置为某个函数引用 +// `x` 存在一些值 if (fn.length == 1) { fn( x ); @@ -128,10 +128,10 @@ else if (fn.length == 3) { fn( undefined, undefined, x ); } ``` +**提示:**函数的“length”属性是只读的,声明函数时就已经确定。它被看作是一段描述函数预期用途的元数据。 -**Tip:** The `length` property of a function is read-only and it's determined at the time you declare the function. It should be thought of as essentially a piece of metadata that describes something about the intended usage of the function. +需要注意的一点是,某些类型的参数列表可能会使函数的“length”属性与您可能期望的不同: -One gotcha to be aware of is that certain kinds of parameter list variations can make the `length` property of the function report something different than you might expect: ```js function foo(x,y = 2) { @@ -150,8 +150,7 @@ foo.length; // 1 bar.length; // 1 baz.length; // 1 ``` - -What about counting the number of arguments the current function call received? This was once trivial, but now the situation is slightly more complicated. Each function has an `arguments` object (array-like) available that holds a reference to each of the arguments passed in. You can then inspect the `length` property of `arguments` to figure out how many were actually passed: +要计算当前函数调用接收到的参数个数?这曾经是微不足道的,但现在情况稍微复杂一些。每个函数都有一个“arguments”对象(类似于数组),用于保存对传入的每个参数的引用。然后可以检查“arguments”的“length”属性,以确定实际传递了多少个: ```js function foo(x,y,z) { @@ -161,23 +160,23 @@ function foo(x,y,z) { foo( 3, 4 ); // 2 ``` -As of ES5 (and strict mode, specifically), `arguments` is considered by some to be sort of deprecated; many avoid using it if possible. In JS, we "never" break backward compatibility no matter how helpful that may be for future progress, so `arguments` will never be removed. But it's now commonly suggested that you avoid using it whenever possible. +从ES5(特别是严格模式)开始,“arguments”被一些人认为是不赞成使用的;许多人会避免使用它。在JS中,我们“从不”破坏向后兼容性,不管这对将来的进展有多大帮助,所以“arguments”永远不会被删除。但现在普遍建议尽可能避免使用它 -However, I suggest that `arguments.length`, and only that, is OK to keep using for those cases where you need to care about the passed number of arguments. A future version of JS might possibly add a feature that offers the ability to determine the number of arguments passed without consulting `arguments.length`; if that happens, then we can fully drop usage of `arguments`! +但是,我建议“arguments.length”,仅用于可以需要传递的参数数量的情况。未来版本的JS可能会添加一个功能,该功能可以确定传递的参数的数量,而不需要查询“arguments.length”;如果发生这种情况,那么我们可以完全放弃使用“arguments”! -Be careful: **never** access arguments positionally, like `arguments[1]`. Stick to `arguments.length` only, and only if you must. +小心:**从不**按位置访问参数,如“arguments[1]”。如果必须的话,只保留对“arguments.length”的引用。 -Except, how will you access an argument that was passed in a position beyond the declared parameters? I'll answer that in a moment; but first, take a step back and ask yourself, "Why would I want to do that?" Seriously. Think about that closely for a minute. +另外,如何在声明参数之外的位置访问传递的参数?我稍后回答;但首先仔细考虑一下问问自己,“我为什么要这样做?”。 -It should be pretty rare that this occurs; it shouldn't be something you regularly expect or rely on when writing your functions. If you find yourself in such a scenario, spend an extra 20 minutes trying to design the interaction with that function in a different way. Name that extra argument even if it's exceptional. +这种情况应该很少发生;它不应该是您在编写函数时经常需要使用的方式。如果您发现自己处于这样的场景中,请花费额外的20分钟尝试以不同的方式设计与该函数的交互。命名这个额外的论点,即使它是例外的。 -A function signature that accepts an indeterminate amount of arguments is referred to as a variadic function. Some people prefer this style of function design, but I think you'll find that often the FPer wants to avoid these where possible. +接受不确定数量参数的函数称为可变函数。有些人更喜欢这种类型的功能设计,但我认为您会发现,一般情况下,函数编程人员通常会避免这样设计。 -OK, enough harping on that point. +好吧,就这一点说得够多了。 -Say you do need to access the arguments in a positional array-like way, possibly because you're accessing an argument that doesn't have a formal parameter at that position. How do we do it? +假设您确实需要以类数组的方式访问参数,可能是您访问的参数在该位置没有正式参数的需要。那我们要怎么做? -ES6 to the rescue! Let's declare our function with the `...` operator -- variously referred to as "spread", "rest", or (my preference) "gather": +ES6可以解决这一点!可以用`…`操作符来声明我们的函数——各种各样地称为“spread”、“rest”或“gather”(我的首选项): ```js function foo(x,y,z,...args) { @@ -185,7 +184,7 @@ function foo(x,y,z,...args) { } ``` -See the `...args` in the parameter list? That's an ES6 declarative form that tells the engine to collect (ahem, "gather") all remaining arguments (if any) not assigned to named parameters, and put them in a real array named `args`. `args` will always be an array, even if it's empty. But it **will not** include values that are assigned to the `x`, `y`, and `z` parameters, only anything else that's passed in beyond those first three values: +看到参数列表中的“…args”?这是一个ES6声明形式,它告诉引擎收集所有未分配给命名参数的剩余参数,并将它们放入名为“args”的实数数组中。` args`将始终是一个数组,即使它是空的。但它**不会**包括分配给“x”、“y”和“z”参数的值,只包括超出前三个值的其他值: ```js function foo(x,y,z,...args) { @@ -198,23 +197,22 @@ foo( 1, 2, 3, 4 ); // 1 2 3 [ 4 ] foo( 1, 2, 3, 4, 5 ); // 1 2 3 [ 4, 5 ] ``` -So, if you *really* want to design a function that can account for an arbitrary number of arguments to be passed in, use `...args` (or whatever name you like) on the end. Now, you'll have a real, non-deprecated, non-yucky array to access those argument values from. +因此,如果您*真的*想设计一个函数来解释要传入的任意数量的参数,请在末尾使用“…args”(也可以使用其他变量名称)。现在,您将拥有一个真正的、不推荐使用的、不易出错的数组来访问这些参数值。 -Just pay attention to the fact that the value `4` is at position `0` of that `args`, not position `3`. And its `length` value won't include those three `1`, `2`, and `3` values. `...args` gathers everything else, not including the `x`, `y`, and `z`. +注意值“4”位于“args”的“0”位置,而不是“3”位置。它的“length”值不包括这三个“1”、“2”和“3”值。`…args'收集其他所有的参数,不包括'x'、'y'和'z'。 -You *can* use the `...` operator in the parameter list even if there's no other formal parameters declared: +您*可以*使用参数列表中的“…”扩展运算符,即使没有声明其他正式参数: ```js function foo(...args) { // .. } ``` +现在,“args”将是参数的完整数组,不管它们是什么,您可以使用“args.length”来确切知道传入了多少个参数。如果你这样做的话,使用“args[1]”或“args[317]”是安全的。不过,请不要传递318个参数。 -Now `args` will be the full array of arguments, whatever they are, and you can use `args.length` to know exactly how many arguments have been passed in. And you're safe to use `args[1]` or `args[317]` if you so choose. Please don't pass in 318 arguments, though. - -### Arrays of Arguments +### 参数数组 -What if you wanted to pass along an array of values as the arguments to a function call? +如果您想将一个数组作为参数传递给函数调用,该怎么办? ```js function foo(...args) { @@ -226,25 +224,24 @@ var arr = [ 1, 2, 3, 4, 5 ]; foo( ...arr ); // 4 ``` -Our new friend `...` is used, but now not just in the parameter list; it's also used in the argument list at the call-site. It has the opposite behavior in this context. In a parameter list, we said it *gathered* arguments together. In an argument list, it *spreads* them out. So the contents of `arr` are actually spread out as individual arguments to the `foo(..)` call. Do you see how that's different from just passing in a reference to the whole `arr` array? +上面“…”被使用了,但现在不仅在参数列表中;它也在调用函数的参数列表中使用。在这种情况下,它有相反的行为。在参数列表中,我们说它*收集*参数个数。在参数列表中,`...`是*展开*了数组的参数。所以“arr”的内容实际上是作为“foo(…)”调用的单个参数展开的。那这和仅仅传递一个对整个“arr”数组的引用有什么不同吗? -By the way, multiple values and `...` spreadings can be interleaved, as you see fit: +还有,多个值和`…`扩展参数可以交错: ```js var arr = [ 2 ]; foo( 1, ...arr, 3, ...[4,5] ); // 4 ``` +思考这个“…”:在值列表位置,它是*展开*的功能。在赋值位置的参数列表收集参数,赋值给使用参数。 -Think of `...` in this symmetric sense: in a value-list position, it *spreads*. In an assignment position -- like a parameter list, because arguments get *assigned to* parameters -- it *gathers*. +无论您调用哪种行为,`…`都会使处理参数数组更加容易。“slice(…)”、“concat(…)”和“apply(…)”的日子已经变得没有用了,这些方法都需要数组参数值。 -Whichever behavior you invoke, `...` makes working with arrays of arguments much easier. Gone are the days of `slice(..)`, `concat(..)`, and `apply(..)` to wrangle our argument value arrays. +**提示:**实际上,这些方法并不是完全无用的。在本书的整个代码中,我们将有一些地方依赖它们。但是,在大多数地方,`…`将会更加声明性地可读,因此更可取。 -**Tip:** Actually, these methods are not entirely useless. There will be a few places we rely on them throughout the code in this book. But certainly in most places, `...` will be much more declaratively readable, and preferable as a result. +### 参数的解构 -### Parameter Destructuring - -Consider the variadic `foo(..)` from the previous section: +考虑上一节中的变量“foo(..)”: ```js function foo(...args) { @@ -254,7 +251,7 @@ function foo(...args) { foo( ...[1,2,3] ); ``` -What if we wanted to change that interaction so the caller of our function passes in an array of values instead of individual argument values? Just drop the two `...` usages: +如果我们想改变这种交互,让函数的调用者传递一个数组,而不是单个参数值,该怎么办?试下这两种用法: ```js function foo(args) { @@ -264,11 +261,11 @@ function foo(args) { foo( [1,2,3] ); ``` -Simple enough. But what if now we wanted to give a parameter name to each of the first two values in the passed-in array? We aren't declaring individual parameters anymore, so it seems we lost that ability. +很简单。但是,如果现在我们想给传入数组中前两个值中的每一个都提供一个参数名呢?我们不再声明单个参数,我们似乎实现不了。 -Thankfully, ES6 destructuring is the answer. Destructuring is a way to declare a *pattern* for the kind of structure (object, array, etc.) that you expect to see, and how decomposition (assignment) of its individual parts should be processed. +感谢ES6给出了答案。解构是一种为您希望看到的结构类型(对象、数组等)声明*模式*的方法,以及如何处理其各个部分的分解(分配)。 -Consider: +想一想: @@ -280,13 +277,13 @@ function foo( [x,y,...args] = [] ) { foo( [1,2,3] ); ``` -Do you spot the `[ .. ]` brackets around the parameter list now? This is called array parameter destructuring. +你看到`[ .. ]`参数列表的括号了吗?这称为数组参数解构。 -In this example, destructuring tells the engine that an array is expected in this assignment position (aka parameter). The pattern says to take the first value of that array and assign to a local parameter variable called `x`, the second to `y`, and whatever is left is *gathered* into `args`. +在这个例子中,解构函数告诉引擎在这个分配位置需要一个数组。该模式表示取数组的第一个值并将其赋给名为“x”的局部参数变量,将第二个值赋给“y”,剩下的值将赋给“args”。 -### The Importance of Declarative Style +### 声明风格的重要性 -Considering the destructured `foo(..)` we just looked at, we could instead have processed the parameters manually: +思考下我们刚才被解构的“foo(…)”,我们可以手动处理参数: ```js function foo(params) { @@ -297,20 +294,20 @@ function foo(params) { // .. } ``` +但在这里,我们强调一个我们在[第1章](ch1.md/#readability)中提到的原则:声明性代码比命令式代码更有效地通信。 -But here we highlight a principle we only briefly introduced in [Chapter 1](ch1.md/#readability): declarative code communicates more effectively than imperative code. +声明性代码(例如,前一个“foo(…)”代码段中的解构函数,或“…”运算符用法)将重点放在代码的结果上。 -Declarative code (for example, the destructuring in the former `foo(..)` snippet, or the `...` operator usages) focuses on what the outcome of a piece of code should be. +命令式代码(如后一段中的手动处理的函数)更关注如何获得结果。如果你以后读到这样的命令式代码,你必须关注执行所有的代码,以理解期望的结果。变得在那里被“编码”,很容易被细节所模糊。 -Imperative code (such as the manual assignments in the latter snippet) focuses more on how to get the outcome. If you later read such imperative code, you have to mentally execute all of it to understand the desired outcome. The outcome is *coded* there, but it's not as clear because it's clouded by the details of *how* we get there. +前面的“foo(…)”被认为更具可读性,因为解构的方法隐藏了不必要的参数输入的细节;读者可以自由地只关注处理这些参数。这显然是最重要的关注点,所以读者应该集中精力来最全面地理解代码。 -The earlier `foo(..)` is regarded as more readable, because the destructuring hides the unnecessary details of *how* to manage the parameter inputs; the reader is free to focus only on *what* we will do with those parameters. That's clearly the most important concern, so it's what the reader should be focused on to understand the code most completely. +无论我们使用的语言和库/框架的允许程度怎样,只要可能,**我们都应该努力实现声明性、自解释的代码。** -Wherever possible, and to whatever degrees our language and our libraries/frameworks will let us, **we should be striving for declarative, self-explanatory code.** -## Named Arguments +## 命名参数 -Just as we can destructure array parameters, we can destructure object parameters: +正如我们可以解构数组参数一样,我们也可以解构对象参数: ```js function foo( {x,y} = {} ) { @@ -322,21 +319,21 @@ foo( { } ); // undefined 3 ``` -We pass in an object as the single argument, and it's destructured into two separate parameter variables `x` and `y`, which are assigned the values of those corresponding property names from the object passed in. It didn't matter that the `x` property wasn't on the object; it just ended up as a variable with `undefined` like you'd expect. +我们将一个对象作为单个参数传入,它被分解为两个单独的参数变量“x”和“y”,它们从传入的对象中分配相应属性名的值。“x”属性不在对象上并不重要;它只是像您所期望的那样,以一个带有“undefined”的变量结束。 -But the part of parameter object destructuring I want you to pay attention to is the object being passed into `foo(..)`. +但是我希望您注意的是,参数对象解构的一部分对象被传递到“foo(…)”。 -With a normal call-site like `foo(undefined,3)`, position is used to map from argument to parameter; we put the `3` in the second position to get it assigned to a `y` parameter. But at this new kind of call-site where parameter destructuring is involved, a simple object-property indicates which parameter (`y`) the argument value `3` should be assigned to. +对于“foo(undefined,3)”这样的普通调用,位置表明了如何映射到函数的参数上;我们将“3”放在第二个位置,以将其分配给“y”参数。但是在参数解构的这种新的调用上,一个简单的对象参数值“3”对应分配给哪个参数(`Y`)。 -We didn't have to account for `x` in *that* call-site because in effect we didn't care about `x`. We just omitted it, instead of having to do something distracting like passing `undefined` as a positional placeholder. +我们不用解释那个调用的“x”,因为我们不关心“x”的使用。我们可以省略它,就不必做一些分散注意力的事情,比如将“undefined”作为位置占位符传递。 -Some languages have an explicit feature for this: named arguments. In other words, at the call-site, labeling an input value to indicate which parameter it maps to. JavaScript doesn't have named arguments, but parameter object destructuring is the next best thing. +有些语言有一个明确的特性:命名参数。换句话说,在调用中,标记输入值以指示它映射到哪个参数。javascript没有命名参数,但参数对象解构是下一个最好的方法。 -Another FP-related benefit of using an object destructuring to pass in potentially multiple arguments is that a function that only takes one parameter (the object) is much easier to compose with another function's single output. Much more on that in [Chapter 4](ch4.md). +使用对象析构函数传递潜在多个参数的方法对函数式编程的好处是,只接受一个参数的函数更容易与另一个函数的单个输出组合。在[第4章](ch4.md)中有阐述的更详细。 -### Unordered Parameters +### 无序参数 -Another key benefit is that named arguments, by virtue of being specified as object properties, are not fundamentally ordered. That means we can specify inputs in whatever order we want: +另一个重要的好处是,命名参数由于被指定为对象属性,所以没有从根本上进行排序。这意味着我们可以按照我们想要的任何顺序输入: ```js function foo( {x,y} = {} ) { @@ -348,11 +345,11 @@ foo( { } ); // undefined 3 ``` -We're skipping the `x` parameter by simply omitting it. Or we could specify an `x` argument if we cared to, even if we listed it after `y` in the object literal. The call-site is no longer cluttered by ordered-placeholders like `undefined` to skip a parameter. +我们只是简单地省略了'x'参数。当然我们也可以指定一个'x'参数,可以放在'y'的后面。这样调用的时候不用考虑x为“undefined”的有序占位符而忽略了参数。 -Named arguments are much more flexible, and attractive from a readability perspective, especially when the function in question can take three, four, or more inputs. +从可读性的角度来看,命名参数更灵活,更具吸引力,尤其是当相关函数可以接受三个、四个或更多输入时。 -**Tip:** If this style of function arguments seems useful or interesting to you, check out coverage of my [FPO library in Appendix C](apC.md/#bonus-fpo). +**提示:**如果这种类型的函数参数对您有用或感兴趣,请查看 [附录C的函数式编程对象](apC.md/#bonus-fpo)。 ## Function Output From 087e46f2b6844068064d7873322c74282d52c5fc Mon Sep 17 00:00:00 2001 From: Siming Date: Wed, 21 Aug 2019 09:56:38 +0800 Subject: [PATCH 42/63] Update ch2.md --- manuscript/ch2.md | 394 ++++++++++++++++++++++------------------------ 1 file changed, 191 insertions(+), 203 deletions(-) diff --git a/manuscript/ch2.md b/manuscript/ch2.md index 2f815440..99124ecb 100644 --- a/manuscript/ch2.md +++ b/manuscript/ch2.md @@ -1,61 +1,60 @@ -# Functional-Light JavaScript -# Chapter 2: The Nature Of Functions +# 章节2: 函数的性质 -Functional Programming is **not just programming with the `function` keyword.** Oh, if only it was that easy -- I could end the book right here! Nevertheless, functions really *are* at the center of FP. And it's how we use functions that makes our code *functional*. +函数式编程不仅仅是用“function”关键字进行定义来编程。如果这么简单的话,我可以在这里结束这本书!函数确实是函数式编程的核心。而且函数的方式使我们的代码*起作用*。 -But how sure are you that you know what *function* really means? +但是你有多确定“函数”的真正含义? -In this chapter, we're going to lay the groundwork for the rest of the book by exploring all the foundational aspects of functions. Actually, this is a review of all the things even a non-FP programmer should know about functions. But certainly if we want to get the most out of FP concepts, it's essential we *know* functions inside and out. +在这一章中,我们将通过探索函数的所有基本面,为本书的其余部分奠定基础。实际上,这是对所有内容的回顾,即使是非函数式编程程序员也应该了解函数。但当然,如果我们想从函数式编程的概念中得到最大的好处,我们就必须了解内部和外部的函数。 -Brace yourself, because there's a lot more to the function than you may have realized. +打起精神来,因为这个功能比你想象的要多得多。 -## What Is a Function? +## 什么是函数? -The question "What is a function?" superficially seems to have an obvious answer: a function is a collection of code that can be executed one or more times. +这个问题,从表面上看,似乎有一个显而易见的答案:函数是可以执行一次或多次的代码集合。 -While this definition is reasonable, it's missing some very important essence that is the core of a *function* as it applies to FP. So let's dig below the surface to understand functions more completely. +虽然这个定义看起来很合理,但它缺少一些非常重要的本质,即*函数*的核心,因为它适用于函数式编程。因此,让我们从表面开始挖掘,以更全面地理解函数。 -### Brief Math Review +### 简单的数学复习 -I know I've promised we'd stay away from math as much as possible, but bear with me for a moment as we quickly observe some fundamental things about functions and graphs from algebra before we proceed. +我知道我答应过我们会尽量远离数学,但是在我们继续之前,请稍等片刻,因为我们很快就能从代数中观察到一些关于函数和图的基本知识。 -Do you remember learning anything about `f(x)` back in school? What about the equation `y = f(x)`? +你记得在学校里学过“f(x)”或者“y=f(x)”吗? -Let's say an equation is defined like this: f(x) = 2x2 + 3. What does that mean? What does it mean to graph that equation? Here's the graph: +假设一个方程是这样定义的: f(x) = 2x2 + 3. 那是什么意思?用图表表示这个方程意味着什么?下面是图表:

-What you can notice is that for any value of `x`, say `2`, if you plug it into the equation, you get `11`. What is `11`, though? It's the *return value* of the `f(x)` function, which earlier we said represents a `y` value. +你能注意到的是,对于x的任何值,比如2,如果你把它插入方程,你得到11。11是什么?它是f(x)函数的返回值,前面我们说它代表y值。 -In other words, we can choose to interpret the input and output values as a point at `(2,11)` on that curve in the graph. And for every value of `x` we plug in, we get another `y` value that pairs with it as a coordinate for a point. Another is `(0,3)`, and another is `(-1,5)`. Put all those points together, and you have the graph of that parabolic curve as shown here. +换句话说,我们可以选择将输入和输出值解释为图中曲线上`(2,11)`处的点。对于我们插入的每一个'x'值,我们得到另一个'y'值,作为一个点的坐标与它配对。另一个是`(0,3)`,另一个是`(-1,5)`。把所有这些点放在一起,就得到了抛物线图,如图所示。 -So what's any of this got to do with FP? +那么,这和函数式编程有什么关系呢? -In math, a function always takes input(s), and always gives an output. A term you'll often hear around FP is "morphism"; this is a fancy way of describing a set of values that maps to another set of values, like the inputs of a function related to the outputs of that function. +在数学中,函数总是有输入并有输出。在函数式编程中经常听到的一个术语是’态射‘(morphism);两个数学结构之间保持结构的一种过程抽象方法,例如与该函数的输出对应另一函数的输入。 -In algebraic math, those inputs and outputs are often interpreted as components of coordinates to be graphed. In our programs, however, we can define functions with all sorts of input(s) and output(s), even though they'll rarely be interpreted as a visually plotted curve on a graph. +在代数数学中,这些输入和输出通常被解释为要绘制图形的坐标的组成部分。然而,在我们的程序中,虽然很少被解释为图形上的可视绘制曲线,当时我们可以定义具有各种输入和输出的函数。 -### Function vs Procedure +### 功能与程序 -So why all the talk of math and graphs? Because essentially Functional Programming is about embracing using functions as *functions* in this mathematical sense. +那么,为什么老是在讨论数学与图表呢?因为本质上,函数式编程就是在数学意义上接受使用函数方法作为特定程序。 -You may be more accustomed to thinking of functions as procedures. What's the difference? A procedure is an arbitrary collection of functionality. It may have inputs, it may not. It may have an output (`return` value), it may not. +您可能更习惯于将函数视为过程。有什么区别?过程是功能的任意集合。它可能有输入,也可能没有。它可能有输出(返回一个值),也可能没有。 -A function takes input(s) and definitely always has a `return` value. +函数接受输入,并且一定有一个“返回”值。 -If you plan to do Functional Programming, **you should be using functions as much as possible**, and trying to avoid procedures wherever possible. All your `function`s should take input(s) and return output(s). +如果您计划进行函数编程,**您应该尽可能多地使用函数**,并尽可能避免使用过程。所有的“函数”都应该接受输入并返回输出。 -Why? The answer to that will have many levels of meaning that we'll uncover throughout this book. +为什么这么做?这有很多层面的意义,我们会在本书中揭露。 -## Function Input +## 函数的输入 -So far, we can conclude that functions must expect input. But let's dig into how function inputs work. +到目前为止,我们可以得出这样的结论:函数必须有输入。但让我们来深入研究函数输入是如何工作的。 -You sometimes hear people refer to these inputs as "arguments" and sometimes as "parameters". So what's that all about? +您有时会听到人们将这些输入称为“参数”,有时称为“因素”。那这是怎么回事? -*Arguments* are the values you pass in, and *parameters* are the named variables inside the function that receive those passed-in values. Example: +*参数*是您传入的值,*因素*是接收传入值的函数内的命名变量。例子: ```js function foo(x,y) { @@ -66,16 +65,15 @@ var a = 3; foo( a, a * 2 ); ``` +`“a”和“a*2”是函数“foo(…)”调用的*参数*,“x”和“y”是接收参数值的*参数*(分别为“3”和“6”(“a*2”的结果))。 -`a` and `a * 2` (actually, the result of `a * 2`, which is `6`) are the *arguments* to the `foo(..)` call. `x` and `y` are the *parameters* that receive the argument values (`3` and `6`, respectively). +**注意:**在javascript中,不要求*参数*的数量与函数*因素*的数量匹配。如果传递的*参数*多于声明接收它们的函数*参数*,那么这些值就不会受到影响。这些值可以通过几种不同的方式访问,包括以前可能听说过的“arguments”对象。如果传递的*参数*少于声明的函数*参数*,则每个不匹配的参数都将被视为“未定义”变量,这意味着它在函数的作用域内存在并可用,但只以空的“未定义”值开始。 -**Note:** In JavaScript, there's no requirement that the number of *arguments* matches the number of *parameters*. If you pass more *arguments* than you have declared *parameters* to receive them, the values pass in just fine untouched. These values can be accessed in a few different ways, including the old-school `arguments` object you may have heard of before. If you pass fewer *arguments* than the declared *parameters*, each unmatched parameter is treated as an "undefined" variable, meaning it's present and available in the scope of the function, but just starts out with the empty `undefined` value. +### 默认参数 -### Defaulting Parameters +从ES6开始,参数可以声明*默认值*。如果没有传递该参数的参数,或者传递了值“undefined”,则将替换默认的赋值表达式。 -As of ES6, parameters can declare *default values*. In the case where the argument for that parameter is not passed, or it's passed the value `undefined`, the default assignment expression is substituted. - -Consider: +想一想: ```js function foo(x = 3) { @@ -87,24 +85,23 @@ foo( undefined ); // 3 foo( null ); // null foo( 0 ); // 0 ``` +考虑有助于函数可用性的默认情况是一个很好的实践。然而,在读取和理解函数如何被调用的变化方面,默认参数可能会导致更复杂的问题。在多大程度上依赖此功能方面要谨慎。 -It's always a good practice to think about any default cases that can aid the usability of your functions. However, defaulting parameters can lead to more complexity in terms of reading and understanding the variations of how a function is called. Be judicious in how much you rely on this feature. - -### Counting Inputs +### 计数输入 -The number of arguments a function "expects" -- how many arguments you'll likely want to pass to it -- is determined by the number of parameters that are declared: +函数“预期”的参数个数由声明的参数个数决定,预期个数就是您可能希望传递给它的参数个数: ```js function foo(x,y,z) { // .. } ``` +` foo(..)`需要三个参数,因为它有三个已声明的参数。这个计数有一个特殊的术语:参数数量。参数数量是指函数声明中的参数个数。“foo(…)”的参数数量为“3”。 -`foo(..)` *expects* three arguments, because it has three declared parameters. This count has a special term: arity. Arity is the number of parameters in a function declaration. The arity of `foo(..)` is `3`. +此外,一个参数的函数也称为“一元”函数,二个参数的函数也称为“二元”函数,n个参数或更高的函数称为“n元”函数。 -Furthermore, a function with arity 1 is also called "unary", a function with arity 2 is also called "binary", and a function with arity 3 or higher is called "n-ary". -You may wish to inspect a function reference during the runtime of a program to determine its arity. This can be done with the `length` property of that function reference: +您可能希望在程序运行时检查函数引用以确定其参数个数。这可以通过函数引用的“length”属性来实现: ```js function foo(x,y,z) { @@ -113,14 +110,13 @@ function foo(x,y,z) { foo.length; // 3 ``` +在执行期间确定参数个数的一个原因是,一段代码可以从多个地方引用一个函数,并根据参数个数的不同发送不同的值。 -One reason for determining the arity during execution would be if a piece of code received a function reference from multiple sources, and sent different values depending on the arity of each. - -For example, imagine a case where an `fn` function reference could expect one, two, or three arguments, but you always want to just pass a variable `x` in the last position: +例如,假设一个情况,一个“fn”函数引用可能需要一个、两个或三个参数,但您总是希望在最后一个位置传递一个变量“x”: ```js -// `fn` is set to some function reference -// `x` exists with some value +// `fn` 设置为某个函数引用 +// `x` 存在一些值 if (fn.length == 1) { fn( x ); @@ -132,10 +128,10 @@ else if (fn.length == 3) { fn( undefined, undefined, x ); } ``` +**提示:**函数的“length”属性是只读的,声明函数时就已经确定。它被看作是一段描述函数预期用途的元数据。 -**Tip:** The `length` property of a function is read-only and it's determined at the time you declare the function. It should be thought of as essentially a piece of metadata that describes something about the intended usage of the function. +需要注意的一点是,某些类型的参数列表可能会使函数的“length”属性与您可能期望的不同: -One gotcha to be aware of is that certain kinds of parameter list variations can make the `length` property of the function report something different than you might expect: ```js function foo(x,y = 2) { @@ -154,8 +150,7 @@ foo.length; // 1 bar.length; // 1 baz.length; // 1 ``` - -What about counting the number of arguments the current function call received? This was once trivial, but now the situation is slightly more complicated. Each function has an `arguments` object (array-like) available that holds a reference to each of the arguments passed in. You can then inspect the `length` property of `arguments` to figure out how many were actually passed: +要计算当前函数调用接收到的参数个数?这曾经是微不足道的,但现在情况稍微复杂一些。每个函数都有一个“arguments”对象(类似于数组),用于保存对传入的每个参数的引用。然后可以检查“arguments”的“length”属性,以确定实际传递了多少个: ```js function foo(x,y,z) { @@ -165,23 +160,23 @@ function foo(x,y,z) { foo( 3, 4 ); // 2 ``` -As of ES5 (and strict mode, specifically), `arguments` is considered by some to be sort of deprecated; many avoid using it if possible. In JS, we "never" break backward compatibility no matter how helpful that may be for future progress, so `arguments` will never be removed. But it's now commonly suggested that you avoid using it whenever possible. +从ES5(特别是严格模式)开始,“arguments”被一些人认为是不赞成使用的;许多人会避免使用它。在JS中,我们“从不”破坏向后兼容性,不管这对将来的进展有多大帮助,所以“arguments”永远不会被删除。但现在普遍建议尽可能避免使用它 -However, I suggest that `arguments.length`, and only that, is OK to keep using for those cases where you need to care about the passed number of arguments. A future version of JS might possibly add a feature that offers the ability to determine the number of arguments passed without consulting `arguments.length`; if that happens, then we can fully drop usage of `arguments`! +但是,我建议“arguments.length”,仅用于可以需要传递的参数数量的情况。未来版本的JS可能会添加一个功能,该功能可以确定传递的参数的数量,而不需要查询“arguments.length”;如果发生这种情况,那么我们可以完全放弃使用“arguments”! -Be careful: **never** access arguments positionally, like `arguments[1]`. Stick to `arguments.length` only, and only if you must. +小心:**从不**按位置访问参数,如“arguments[1]”。如果必须的话,只保留对“arguments.length”的引用。 -Except, how will you access an argument that was passed in a position beyond the declared parameters? I'll answer that in a moment; but first, take a step back and ask yourself, "Why would I want to do that?" Seriously. Think about that closely for a minute. +另外,如何在声明参数之外的位置访问传递的参数?我稍后回答;但首先仔细考虑一下问问自己,“我为什么要这样做?”。 -It should be pretty rare that this occurs; it shouldn't be something you regularly expect or rely on when writing your functions. If you find yourself in such a scenario, spend an extra 20 minutes trying to design the interaction with that function in a different way. Name that extra argument even if it's exceptional. +这种情况应该很少发生;它不应该是您在编写函数时经常需要使用的方式。如果您发现自己处于这样的场景中,请花费额外的20分钟尝试以不同的方式设计与该函数的交互。命名这个额外的论点,即使它是例外的。 -A function signature that accepts an indeterminate amount of arguments is referred to as a variadic function. Some people prefer this style of function design, but I think you'll find that often the FPer wants to avoid these where possible. +接受不确定数量参数的函数称为可变函数。有些人更喜欢这种类型的功能设计,但我认为您会发现,一般情况下,函数编程人员通常会避免这样设计。 -OK, enough harping on that point. +好吧,就这一点说得够多了。 -Say you do need to access the arguments in a positional array-like way, possibly because you're accessing an argument that doesn't have a formal parameter at that position. How do we do it? +假设您确实需要以类数组的方式访问参数,可能是您访问的参数在该位置没有正式参数的需要。那我们要怎么做? -ES6 to the rescue! Let's declare our function with the `...` operator -- variously referred to as "spread", "rest", or (my preference) "gather": +ES6可以解决这一点!可以用`…`操作符来声明我们的函数——各种各样地称为“spread”、“rest”或“gather”(我的首选项): ```js function foo(x,y,z,...args) { @@ -189,7 +184,7 @@ function foo(x,y,z,...args) { } ``` -See the `...args` in the parameter list? That's an ES6 declarative form that tells the engine to collect (ahem, "gather") all remaining arguments (if any) not assigned to named parameters, and put them in a real array named `args`. `args` will always be an array, even if it's empty. But it **will not** include values that are assigned to the `x`, `y`, and `z` parameters, only anything else that's passed in beyond those first three values: +看到参数列表中的“…args”?这是一个ES6声明形式,它告诉引擎收集所有未分配给命名参数的剩余参数,并将它们放入名为“args”的实数数组中。` args`将始终是一个数组,即使它是空的。但它**不会**包括分配给“x”、“y”和“z”参数的值,只包括超出前三个值的其他值: ```js function foo(x,y,z,...args) { @@ -202,23 +197,22 @@ foo( 1, 2, 3, 4 ); // 1 2 3 [ 4 ] foo( 1, 2, 3, 4, 5 ); // 1 2 3 [ 4, 5 ] ``` -So, if you *really* want to design a function that can account for an arbitrary number of arguments to be passed in, use `...args` (or whatever name you like) on the end. Now, you'll have a real, non-deprecated, non-yucky array to access those argument values from. +因此,如果您*真的*想设计一个函数来解释要传入的任意数量的参数,请在末尾使用“…args”(也可以使用其他变量名称)。现在,您将拥有一个真正的、不推荐使用的、不易出错的数组来访问这些参数值。 -Just pay attention to the fact that the value `4` is at position `0` of that `args`, not position `3`. And its `length` value won't include those three `1`, `2`, and `3` values. `...args` gathers everything else, not including the `x`, `y`, and `z`. +注意值“4”位于“args”的“0”位置,而不是“3”位置。它的“length”值不包括这三个“1”、“2”和“3”值。`…args'收集其他所有的参数,不包括'x'、'y'和'z'。 -You *can* use the `...` operator in the parameter list even if there's no other formal parameters declared: +您*可以*使用参数列表中的“…”扩展运算符,即使没有声明其他正式参数: ```js function foo(...args) { // .. } ``` +现在,“args”将是参数的完整数组,不管它们是什么,您可以使用“args.length”来确切知道传入了多少个参数。如果你这样做的话,使用“args[1]”或“args[317]”是安全的。不过,请不要传递318个参数。 -Now `args` will be the full array of arguments, whatever they are, and you can use `args.length` to know exactly how many arguments have been passed in. And you're safe to use `args[1]` or `args[317]` if you so choose. Please don't pass in 318 arguments, though. +### 参数数组 -### Arrays of Arguments - -What if you wanted to pass along an array of values as the arguments to a function call? +如果您想将一个数组作为参数传递给函数调用,该怎么办? ```js function foo(...args) { @@ -230,25 +224,24 @@ var arr = [ 1, 2, 3, 4, 5 ]; foo( ...arr ); // 4 ``` -Our new friend `...` is used, but now not just in the parameter list; it's also used in the argument list at the call-site. It has the opposite behavior in this context. In a parameter list, we said it *gathered* arguments together. In an argument list, it *spreads* them out. So the contents of `arr` are actually spread out as individual arguments to the `foo(..)` call. Do you see how that's different from just passing in a reference to the whole `arr` array? +上面“…”被使用了,但现在不仅在参数列表中;它也在调用函数的参数列表中使用。在这种情况下,它有相反的行为。在参数列表中,我们说它*收集*参数个数。在参数列表中,`...`是*展开*了数组的参数。所以“arr”的内容实际上是作为“foo(…)”调用的单个参数展开的。那这和仅仅传递一个对整个“arr”数组的引用有什么不同吗? -By the way, multiple values and `...` spreadings can be interleaved, as you see fit: +还有,多个值和`…`扩展参数可以交错: ```js var arr = [ 2 ]; foo( 1, ...arr, 3, ...[4,5] ); // 4 ``` +思考这个“…”:在值列表位置,它是*展开*的功能。在赋值位置的参数列表收集参数,赋值给使用参数。 -Think of `...` in this symmetric sense: in a value-list position, it *spreads*. In an assignment position -- like a parameter list, because arguments get *assigned to* parameters -- it *gathers*. - -Whichever behavior you invoke, `...` makes working with arrays of arguments much easier. Gone are the days of `slice(..)`, `concat(..)`, and `apply(..)` to wrangle our argument value arrays. +无论您调用哪种行为,`…`都会使处理参数数组更加容易。“slice(…)”、“concat(…)”和“apply(…)”的日子已经变得没有用了,这些方法都需要数组参数值。 -**Tip:** Actually, these methods are not entirely useless. There will be a few places we rely on them throughout the code in this book. But certainly in most places, `...` will be much more declaratively readable, and preferable as a result. +**提示:**实际上,这些方法并不是完全无用的。在本书的整个代码中,我们将有一些地方依赖它们。但是,在大多数地方,`…`将会更加声明性地可读,因此更可取。 -### Parameter Destructuring +### 参数的解构 -Consider the variadic `foo(..)` from the previous section: +考虑上一节中的变量“foo(..)”: ```js function foo(...args) { @@ -258,7 +251,7 @@ function foo(...args) { foo( ...[1,2,3] ); ``` -What if we wanted to change that interaction so the caller of our function passes in an array of values instead of individual argument values? Just drop the two `...` usages: +如果我们想改变这种交互,让函数的调用者传递一个数组,而不是单个参数值,该怎么办?试下这两种用法: ```js function foo(args) { @@ -268,11 +261,11 @@ function foo(args) { foo( [1,2,3] ); ``` -Simple enough. But what if now we wanted to give a parameter name to each of the first two values in the passed-in array? We aren't declaring individual parameters anymore, so it seems we lost that ability. +很简单。但是,如果现在我们想给传入数组中前两个值中的每一个都提供一个参数名呢?我们不再声明单个参数,我们似乎实现不了。 -Thankfully, ES6 destructuring is the answer. Destructuring is a way to declare a *pattern* for the kind of structure (object, array, etc.) that you expect to see, and how decomposition (assignment) of its individual parts should be processed. +感谢ES6给出了答案。解构是一种为您希望看到的结构类型(对象、数组等)声明*模式*的方法,以及如何处理其各个部分的分解(分配)。 -Consider: +想一想: @@ -284,13 +277,13 @@ function foo( [x,y,...args] = [] ) { foo( [1,2,3] ); ``` -Do you spot the `[ .. ]` brackets around the parameter list now? This is called array parameter destructuring. +你看到`[ .. ]`参数列表的括号了吗?这称为数组参数解构。 -In this example, destructuring tells the engine that an array is expected in this assignment position (aka parameter). The pattern says to take the first value of that array and assign to a local parameter variable called `x`, the second to `y`, and whatever is left is *gathered* into `args`. +在这个例子中,解构函数告诉引擎在这个分配位置需要一个数组。该模式表示取数组的第一个值并将其赋给名为“x”的局部参数变量,将第二个值赋给“y”,剩下的值将赋给“args”。 -### The Importance of Declarative Style +### 声明风格的重要性 -Considering the destructured `foo(..)` we just looked at, we could instead have processed the parameters manually: +思考下我们刚才被解构的“foo(…)”,我们可以手动处理参数: ```js function foo(params) { @@ -301,20 +294,20 @@ function foo(params) { // .. } ``` +但在这里,我们强调一个我们在[第1章](ch1.md/#readability)中提到的原则:声明性代码比命令式代码更有效地通信。 -But here we highlight a principle we only briefly introduced in [Chapter 1](ch1.md/#readability): declarative code communicates more effectively than imperative code. +声明性代码(例如,前一个“foo(…)”代码段中的解构函数,或“…”运算符用法)将重点放在代码的结果上。 -Declarative code (for example, the destructuring in the former `foo(..)` snippet, or the `...` operator usages) focuses on what the outcome of a piece of code should be. +命令式代码(如后一段中的手动处理的函数)更关注如何获得结果。如果你以后读到这样的命令式代码,你必须关注执行所有的代码,以理解期望的结果。变得在那里被“编码”,很容易被细节所模糊。 -Imperative code (such as the manual assignments in the latter snippet) focuses more on how to get the outcome. If you later read such imperative code, you have to mentally execute all of it to understand the desired outcome. The outcome is *coded* there, but it's not as clear because it's clouded by the details of *how* we get there. +前面的“foo(…)”被认为更具可读性,因为解构的方法隐藏了不必要的参数输入的细节;读者可以自由地只关注处理这些参数。这显然是最重要的关注点,所以读者应该集中精力来最全面地理解代码。 -The earlier `foo(..)` is regarded as more readable, because the destructuring hides the unnecessary details of *how* to manage the parameter inputs; the reader is free to focus only on *what* we will do with those parameters. That's clearly the most important concern, so it's what the reader should be focused on to understand the code most completely. +无论我们使用的语言和库/框架的允许程度怎样,只要可能,**我们都应该努力实现声明性、自解释的代码。** -Wherever possible, and to whatever degrees our language and our libraries/frameworks will let us, **we should be striving for declarative, self-explanatory code.** -## Named Arguments +## 命名参数 -Just as we can destructure array parameters, we can destructure object parameters: +正如我们可以解构数组参数一样,我们也可以解构对象参数: ```js function foo( {x,y} = {} ) { @@ -326,21 +319,21 @@ foo( { } ); // undefined 3 ``` -We pass in an object as the single argument, and it's destructured into two separate parameter variables `x` and `y`, which are assigned the values of those corresponding property names from the object passed in. It didn't matter that the `x` property wasn't on the object; it just ended up as a variable with `undefined` like you'd expect. +我们将一个对象作为单个参数传入,它被分解为两个单独的参数变量“x”和“y”,它们从传入的对象中分配相应属性名的值。“x”属性不在对象上并不重要;它只是像您所期望的那样,以一个带有“undefined”的变量结束。 -But the part of parameter object destructuring I want you to pay attention to is the object being passed into `foo(..)`. +但是我希望您注意的是,参数对象解构的一部分对象被传递到“foo(…)”。 -With a normal call-site like `foo(undefined,3)`, position is used to map from argument to parameter; we put the `3` in the second position to get it assigned to a `y` parameter. But at this new kind of call-site where parameter destructuring is involved, a simple object-property indicates which parameter (`y`) the argument value `3` should be assigned to. +对于“foo(undefined,3)”这样的普通调用,位置表明了如何映射到函数的参数上;我们将“3”放在第二个位置,以将其分配给“y”参数。但是在参数解构的这种新的调用上,一个简单的对象参数值“3”对应分配给哪个参数(`Y`)。 -We didn't have to account for `x` in *that* call-site because in effect we didn't care about `x`. We just omitted it, instead of having to do something distracting like passing `undefined` as a positional placeholder. +我们不用解释那个调用的“x”,因为我们不关心“x”的使用。我们可以省略它,就不必做一些分散注意力的事情,比如将“undefined”作为位置占位符传递。 -Some languages have an explicit feature for this: named arguments. In other words, at the call-site, labeling an input value to indicate which parameter it maps to. JavaScript doesn't have named arguments, but parameter object destructuring is the next best thing. +有些语言有一个明确的特性:命名参数。换句话说,在调用中,标记输入值以指示它映射到哪个参数。javascript没有命名参数,但参数对象解构是下一个最好的方法。 -Another FP-related benefit of using an object destructuring to pass in potentially multiple arguments is that a function that only takes one parameter (the object) is much easier to compose with another function's single output. Much more on that in [Chapter 4](ch4.md). +使用对象析构函数传递潜在多个参数的方法对函数式编程的好处是,只接受一个参数的函数更容易与另一个函数的单个输出组合。在[第4章](ch4.md)中有阐述的更详细。 -### Unordered Parameters +### 无序参数 -Another key benefit is that named arguments, by virtue of being specified as object properties, are not fundamentally ordered. That means we can specify inputs in whatever order we want: +另一个重要的好处是,命名参数由于被指定为对象属性,所以没有从根本上进行排序。这意味着我们可以按照我们想要的任何顺序输入: ```js function foo( {x,y} = {} ) { @@ -352,17 +345,17 @@ foo( { } ); // undefined 3 ``` -We're skipping the `x` parameter by simply omitting it. Or we could specify an `x` argument if we cared to, even if we listed it after `y` in the object literal. The call-site is no longer cluttered by ordered-placeholders like `undefined` to skip a parameter. +我们只是简单地省略了'x'参数。当然我们也可以指定一个'x'参数,可以放在'y'的后面。这样调用的时候不用考虑x为“undefined”的有序占位符而忽略了参数。 -Named arguments are much more flexible, and attractive from a readability perspective, especially when the function in question can take three, four, or more inputs. +从可读性的角度来看,命名参数更灵活,更具吸引力,尤其是当相关函数可以接受三个、四个或更多输入时。 -**Tip:** If this style of function arguments seems useful or interesting to you, check out coverage of my [FPO library in Appendix C](apC.md/#bonus-fpo). +**提示:**如果这种类型的函数参数对您有用或感兴趣,请查看 [附录C的函数式编程对象](apC.md/#bonus-fpo)。 -## Function Output +## 函数的输出 -Let's shift our attention from a function's inputs to its output. +让我们把注意力从函数的输入转移到函数的输出吧。 -In JavaScript, functions always return a value. These three functions all have identical `return` behavior: +在JavaScript中,函数总是返回一个值。这三个函数都具有相同的“返回”行为: ```js function foo() {} @@ -376,11 +369,11 @@ function baz() { } ``` -The `undefined` value is implicitly `return`ed if you have no `return` or if you just have an empty `return;`. +如果没有“return”,或者只有空的“return;”,则“undefined”值是隐式的“return”。 -But keeping as much with the spirit of FP function definition as possible -- using functions and not procedures -- our functions should always have outputs, which means they should explicitly `return` a value, and usually not `undefined`. +但是尽可能多地遵循函数编程定义的精神——使用函数而不是过程——我们的函数应该总是有输出,这意味着它们应该显式地“返回”一个值,而不是返回隐式的返回“undefined”。 -A `return` statement can only return a single value. So if your function needs to return multiple values, your only viable option is to collect them into a compound value like an array or an object: +“return”语句只能返回单个值。因此,如果函数需要返回多个值,唯一可行的选择是将它们收集到数组或对象这样的复合值中: ```js function foo() { @@ -389,23 +382,22 @@ function foo() { return [ retValue1, retValue2 ]; } ``` - +然后,我们从“foo()”返回的两个对应项分配给“x”和“y”: Then, we'll assign `x` and `y` from two respective items in the array that comes back from `foo()`: ```js var [ x, y ] = foo(); console.log( x + y ); // 42 ``` +将多个值收集到数组(或对象)中以返回,然后将这些值解构回不同的赋值,是透明地表示函数的多个输出的一种方法。 -Collecting multiple values into an array (or object) to return, and subsequently destructuring those values back into distinct assignments, is a way to transparently express multiple outputs for a function. +**提示:**如果我不建议您花时间来考虑一个需要多个输出的函数是否可以重构来避免这种情况,或者将其分解为两个或更多更小的单用途函数,那就是我的疏忽了。有时是可能的,有时不是;但你至少应该考虑一下这种情况的存在。 -**Tip:** I'd be remiss if I didn't suggest you take a moment to consider if a function needing multiple outputs could be refactored to avoid that, perhaps separated into two or more smaller single-purpose functions? Sometimes that will be possible, sometimes not; but you should at least consider it. +### 提前返回 -### Early Returns +“return”语句不仅返回函数的值。它也是一个流控制结构;它在该点结束了函数的执行。因此,具有多个“return”语句的函数具有多个可能的退出点,这意味着如果有多条路径可以生成该输出,则可能难以读取函数以了解其输出行为。 -The `return` statement doesn't just return a value from a function. It's also a flow control structure; it ends the execution of the function at that point. A function with multiple `return` statements thus has multiple possible exit points, meaning that it may be harder to read a function to understand its output behavior if there are many paths that could produce that output. - -Consider: +想一想: ```js function foo(x) { @@ -422,16 +414,15 @@ function foo(x) { return x; } ``` +小测验:如果不作弊,也不在浏览器中运行此代码,那么'foo(2)'返回什么?那么“foo(4)”呢?还有“foo(8)”?还有“foo(12)”? -Pop quiz: without cheating and running this code in your browser, what does `foo(2)` return? What about `foo(4)`? And `foo(8)`? And `foo(12)`? - -How confident are you in your answers? How much mental tax did you pay to get those answers? I got it wrong the first two times I tried to think it through, and I wrote it! +你对自己的答案有信心吗?为了得到这些答案,你付了多少精神税?前两次我都想错了! -I think part of the readability problem here is that we're using `return` not just to return different values, but also as a flow control construct to quit a function's execution early in certain cases. There are obviously better ways to write that flow control (the `if` logic, etc.), but I also think there are ways to make the output paths more obvious. +我认为可读性也是一部分问题,我们使用“return”不仅返回不同的值,而且作为流控制结构在某些情况下提前退出函数的执行。显然有更好的方法来编写流控制(“if”逻辑,等等),但是我也认为有一些方法可以使输出路径更加明显。 -**Note:** The answers to the pop quiz are `2`, `2`, `8`, and `13`. +**注:**小测验的答案是'2`、'2`、'8`和'13`。 -Consider this version of the code: +考虑下该版本的代码: ```js function foo(x) { @@ -460,18 +451,17 @@ function foo(x) { return retValue; } ``` +这个版本无疑更加冗长。但我认为遵循这种逻辑稍微简单一些,因为可以设置“retValue”的每个分支都由检查它是否已经设置的条件“保护”。 -This version is unquestionably more verbose. But I would argue it's slightly simpler logic to follow, because every branch where `retValue` can get set is *guarded* by the condition that checks if it's already been set. +我们使用常规流控制(' if '逻辑)来确定' retValue '的赋值,而不是从函数提前'返回'。最后,我们简单地返回“retValue”。 -Rather than `return`ing from the function early, we used normal flow control (`if` logic) to determine the `retValue`'s assignment. At the end, we simply `return retValue`. +我并不是无条件地说您应该总是有一个单一的“返回”,或者您永远不应该做提前的“返回”,但是我确实认为您应该注意“返回”的流控制部分,它在函数定义中创建了更多的含义。试着找出最明确的表达逻辑的方法;这通常是最好的方法。 -I'm not unconditionally saying that you should always have a single `return`, or that you should never do early `return`s, but I do think you should be careful about the flow control part of `return` creating more implicitness in your function definitions. Try to figure out the most explicit way to express the logic; that will often be the best way. +### Un`return`ed Outputs 取消返回的输出 -### Un`return`ed Outputs +您可能已经在编写的大多数代码中使用了一种技术,甚至可能没有考虑太多,就是让一个函数通过简单地改变自身外部的变量来输出其部分或全部值。 -One technique that you've probably used in most code you've written, and maybe didn't even think about it much, is to have a function output some or all of its values by simply changing variables outside itself. - -Remember our f(x) = 2x2 + 3 function from earlier in the chapter? We could have defined it like this in JS: +还记得我们在本章前面f(x) = 2x2 + 3的函数吗?我们可以在JS中这样定义它: ```js var y; @@ -485,7 +475,7 @@ f( 2 ); y; // 11 ``` -I know this is a silly example; we could just as easily have `return`d the value instead of setting it into `y` from within the function: +我知道这是一个愚蠢的例子;我们可以很容易地将值返回,而不是从函数中将其设置为“y”: ```js function f(x) { @@ -497,13 +487,13 @@ var y = f( 2 ); y; // 11 ``` -Both functions accomplish the same task, so is there any reason we should pick one version over the other? **Yes, absolutely.** +两个函数都完成了相同的任务,所以我们有理由选择这个版本而不是另一个版本吗?回答:**是的,当然可以。** -One way to explain the difference is that the `return` in the latter version signals an explicit output, whereas the `y` assignment in the former is an implicit output. You may already have some intuition that guides you in such cases; typically, developers prefer explicit patterns over implicit ones. +解释差异的一种方法是,后一个版本中的“返回”表示显式输出,而前一个版本中的“y”赋值是隐式输出。这么说的话,估计你知道怎么做了;通常,开发人员更喜欢显式模式而不是隐式模式。 -But changing a variable in an outer scope, as we did with the `y` assignment inside of `foo(..)`, is just one way of achieving an implicit output. A more subtle example is making changes to non-local values via reference. +但是,在外部作用域中更改变量,正如我们在“foo(…)”中使用“y”赋值所做的那样,这只是实现隐式输出的一种方法。一个更微妙的例子是通过引用对非本地值进行更改。 -Consider: +想一想: ```js function sum(list) { @@ -522,19 +512,17 @@ var nums = [ 1, 3, 9, 27, , 84 ]; sum( nums ); // 124 ``` -The most obvious output from this function is the sum `124`, which we explicitly `return`ed. But do you spot the other output? Try that code and then inspect the `nums` array. Now do you spot the difference? +这个最明显是sum函数的输出`124`,我们显式地‘返回’。但是您发现了其他输出吗?尝试该代码,然后检查“nums”数组。现在你发现区别了吗? -Instead of an `undefined` empty slot value in position `4`, now there's a `0`. The harmless looking `list[i] = 0` operation ended up affecting the array value on the outside, even though we operated on a local `list` parameter variable. +数组位置“4”中现在有一个“0”而不是“undefined”空值,看起来无害的' list[i] = 0 '操作最终影响了外部的数组值,即使我们在本地的“list”参数变量上操作。 -Why? Because `list` holds a reference-copy of the `nums` reference, not a value-copy of the `[1,3,9,..]` array value. JavaScript uses references and reference-copies for arrays, objects, and functions, so we may create an accidental output from our function all too easily. +为什么?因为从函数中创建了异常输出,'list'保存了'nums'引用的引用副本,而不是`[1,3,9,..]`数组值的值副本。通常javascript使用数组、对象和函数的引用和引用副本。 -This implicit function output has a special name in the FP world: side effects. And a function that has *no side effects* also has a special name: pure function. We'll talk a lot more about these in [Chapter 5](ch5.md), but the punchline is that we'll want to prefer pure functions and avoid side effects wherever possible. +## 函数的功能 -## Functions of Functions +函数可以接收和返回任何类型的值。接收或返回一个或多个其他函数值的函数具有特殊名称:高阶函数。 -Functions can receive and return values of any type. A function that receives or returns one or more other function values has the special name: higher-order function. - -Consider: +想一想: ```js function forEach(list,fn) { @@ -549,9 +537,9 @@ forEach( [1,2,3,4,5], function each(val){ // 1 2 3 4 5 ``` -`forEach(..)` is a higher-order function because it receives a function as an argument. +` foreach(..)`是一个高阶函数,因为它接收一个函数作为了参数。 -A higher-order function can also output another function, like: +高阶函数也可以输出另一个函数,例如: ```js function foo() { @@ -565,7 +553,7 @@ var f = foo(); f( "Hello!" ); // HELLO! ``` -`return` is not the only way to "output" an inner function: +`return`不是“输出”内部函数的唯一方法: ```js function foo() { @@ -581,17 +569,17 @@ function bar(func) { foo(); // HELLO! ``` -Functions that treat other functions as values are higher-order functions by definition. FPers write these all the time! +根据定义,将其他函数视为值的函数是高阶函数。函数式编程人员一直在写这些! -### Keeping Scope +### 作用域的保持 -One of the most powerful things in all of programming, and especially in FP, is how a function behaves when it's inside another function's scope. When the inner function makes reference to a variable from the outer function, this is called closure. +在所有编程中,尤其是在函数式编程中,最强大的功能之一就是一个函数在另一个函数的作用域中时的行为。当内部函数引用外部函数中的变量时,这称为闭包。 -Defined pragmatically: +实用性的定义: -> Closure is when a function remembers and accesses variables from outside of its own scope, even when that function is executed in a different scope. +> 闭包是指当一个函数从它自己的作用域之外记住和访问变量时,即使这个函数是在另一个作用域中执行的。 -Consider: +想一想: ```js function foo(msg) { @@ -607,11 +595,11 @@ var helloFn = foo( "Hello!" ); helloFn(); // HELLO! ``` -The `msg` parameter variable in the scope of `foo(..)` is referenced inside the inner function. When `foo(..)` is executed and the inner function is created, it captures the access to the `msg` variable, and retains that access even after being `return`ed. +'foo(..)'作用域内的'msg'参数变量在内部函数内被引用。当执行“foo(..)”并创建内部函数时,它捕获对“msg”变量的访问,并且即使在“return”之后仍然保留该访问。 -Once we have `helloFn`, a reference to the inner function, `foo(..)` has finished and it would seem as if its scope should have gone away, meaning the `msg` variable would no longer exist. But that doesn't happen, because the inner function has a closure over `msg` that keeps it alive. The closed over `msg` variable survives for as long as the inner function (now referenced by `helloFn` in a different scope) stays around. +一旦我们定义了“hellofn”,对内部函数“foo(…)”的引用就结束了,它的作用域似乎应该消失了,这意味着“msg”变量将不再存在。但并不是这样,因为内部函数在“msg”上有一个闭包,使其保持活动状态。只要内部函数(现在由另一个作用域中的“hellofn”引用)保持不变,封闭的“msg”变量就会一直存在。 -Let's look at a few more examples of closure in action: +让我们再看几个实际中的闭包示例: ```js function person(name) { @@ -627,9 +615,9 @@ fred(); // I am Fred susan(); // I am Susan ``` -The inner function `identify()` has closure over the parameter `name`. +在参数为“name”的内部函数“identify()”存在闭包。 -The access that closure enables is not restricted to merely reading the variable's original value -- it's not just a snapshot but rather a live link. You can update the value, and that new current state remains remembered until the next access: +闭包启用的访问不仅限于读取变量的原始值——它不仅仅是一个快照,而是一个活动链接。您可以更新该值,并且新的状态将一直保留到下一次访问: ```js function runningCounter(start) { @@ -648,9 +636,9 @@ score(); // 2 score( 13 ); // 15 ``` -**Warning:** For reasons that we'll explore in more depth later in the book, this example of using closure to remember a state that changes (`val`) is probably something you'll want to avoid where possible. +**警告:**我们将在本书后面更深入地探讨这个问题,这个使用闭包来记住更改(`val`)的状态的示例可能是您希望尽可能避免的。 -If you have an operation that needs two inputs, one of which you know now but the other will be specified later, you can use closure to remember the first input: +如果有一个操作需要两个输入,其中一个现在知道,另一个稍后将被指定,则可以使用闭包记住第一个输入: ```js function makeAdder(x) { @@ -659,22 +647,22 @@ function makeAdder(x) { }; } -// we already know `10` and `37` as first inputs, respectively +// 我们已经知道“10”和“37”分别作为第一个输入 var addTo10 = makeAdder( 10 ); var addTo37 = makeAdder( 37 ); -// later, we specify the second inputs +// 稍后,我们将指定第二个输入 addTo10( 3 ); // 13 addTo10( 90 ); // 100 addTo37( 13 ); // 50 ``` -Normally, a `sum(..)` function would take both an `x` and `y` input to add them together. But in this example we receive and remember (via closure) the `x` value(s) first, while the `y` value(s) are separately specified later. +通常,“sum(..)”函数会同时使用“x”和“y”输入将它们添加到一起。但在本例中,我们首先接收并记住(通过闭包)x值,而y值则在后面单独指定。 -**Note:** This technique of specifying inputs in successive function calls is very common in FP, and comes in two forms: partial application and currying. We'll dive into them more thoroughly in [Chapter 3](ch3.md/#some-now-some-later). +**注:**这种在连续函数调用中指定输入的技术在函数式编程中非常常见,有两种形式:局部应用和局部套用。我们将在[第三章](ch3.md/#some-now-some-later)中更深入地研究它们。 -Of course, since functions are just values in JS, we can remember function values via closure: +当然,由于函数只是JS中的值,我们可以通过闭包来记住函数值: ```js function formatter(formatFn) { @@ -695,25 +683,25 @@ lower( "WOW" ); // wow upperFirst( "hello" ); // Hello ``` -Instead of distributing/repeating the `toUpperCase()` and `toLowerCase()` logic all over our code, FP encourages us to create simple functions that encapsulate -- a fancy way of saying wrapping up -- that behavior. +函数式编程鼓励我们创建简单的函数来封装这种行为,而不是在代码中到处分发/重复“toUpperCase()”和“toLowerCase()”逻辑。 -Specifically, we create two simple unary functions `lower(..)` and `upperFirst(..)`, because those functions will be much easier to wire up to work with other functions in the rest of our program. +具体来说,我们创建了两个简单的一元函数“lower(..)”和“upperFirst(..)”,因为这些函数将更容易与程序其余部分中的其他函数连接起来。 -**Tip:** Did you spot how `upperFirst(..)` could have used `lower(..)`? +**提示:**您是否发现“upperfirst(..)”如何使用“lower(..)”? -We'll use closure heavily throughout the rest of the text. It may just be the most important foundational practice in all of FP, if not programming as a whole. Make sure you're really comfortable with it! +我们将在本文的其余部分大量使用闭包。它可能只是所有函数式编程中最重要的基础实践,如果不是作为一个整体进行编程的话。相信你很满意! -## Syntax +## 语法 -Before we move on from this primer on functions, let's take a moment to discuss their syntax. +在我们从这个函数入门开始之前,让我们花点时间来讨论它们的语法。 -More than many other parts of this text, the discussions in this section are mostly opinion and preference, whether you agree with the views presented here or take opposite ones. These ideas are highly subjective, though many people seem to feel rather absolutely about them. +与本文的许多其他部分相比,本节中的讨论大多是意见和偏好,无论您是否同意此处提出的观点或采取相反的观点。这些想法是非常主观的,尽管许多人似乎对它们有相当绝对的感觉。 -Ultimately, you get to decide. +不过,最后你要做决定。 -### What's in a Name? +### 命名 -Syntactically speaking, function declarations require the inclusion of a name: +从语法上讲,函数声明需要包含一个名称: ```js function helloMyNameIs() { @@ -721,27 +709,28 @@ function helloMyNameIs() { } ``` -But function expressions can come in both named and anonymous forms: +但是函数表达式可以有命名和匿名两种形式: ```js foo( function namedFunctionExpr(){ // .. } ); -bar( function(){ // <-- look, no name! +bar( function(){ // <-- 看这, 未进行命名! // .. } ); ``` -What exactly do we mean by anonymous, by the way? Specifically, functions have a `name` property that holds the string value of the name the function was given syntactically, such as `"helloMyNameIs"` or `"namedFunctionExpr"`. This `name` property is most notably used by the console/developer tools of your JS environment to list the function when it participates in a stack trace (usually from an exception). +顺便问一下,匿名到底是什么意思?具体地说,函数有一个“name”属性,它保存函数语法上给定的名称的字符串值,例如“hellomyname”或“namedfunctionexpr”。JS环境中的控制台/开发人员工具最显著地使用此“name”属性来列出函数参与堆栈跟踪时的列表(通常来自异常)。 -Anonymous functions are generally displayed as `(anonymous function)`. +匿名函数通常显示为`(anonymous function)`。 -If you've ever had to debug a JS program from nothing but a stack trace of an exception, you probably have felt the pain of seeing `(anonymous function)` appear line after line. This listing doesn't give a developer any clue whatsoever as to the path the exception came from. It's not doing the developer any favors. +如果您必须从异常的堆栈跟踪中调试JS程序,那么您可能会感到看到`(匿名函数)`一行接一行出现的痛苦。并没有给开发人员任何关于异常来源路径的线索。它对开发人员没有任何帮助。 -If you name your function expressions, the name is always used. So if you use a good name like `handleProfileClicks` instead of `foo`, you'll get much more helpful stack traces. +如果你是想使用命名函数表达式,一定要定义名称。因此,如果您使用像“handleprofileclicks”这样的好名称而不是“foo”,您将得到更多有用的堆栈跟踪。 -As of ES6, anonymous function expressions are in certain cases aided by *name inferencing*. Consider: +从ES6开始,在某些情况下,匿名函数表达式由定义的名字辅助 +想一想: ```js var x = function(){}; @@ -749,9 +738,9 @@ var x = function(){}; x.name; // x ``` -If the engine is able to guess what name you *probably* want the function to take, it will go ahead and do so. +如果引擎能够猜出您*可能*想要函数取什么名称,它将继续执行并执行此操作。 -But beware, not all syntactic forms benefit from name inferencing. Probably the most common place a function expression shows up is as an argument to a function call: +但要注意,并非所有的句法形式都能从名称推断中受益。函数表达式出现的最常见地方可能是作为函数调用的参数: ```js function foo(fn) { @@ -764,14 +753,14 @@ foo( x ); // x foo( function(){} ); // ``` -When the name can't be inferred from the immediate surrounding syntax, it remains an empty string. Such a function will be reported as `(anonymous function)` in a stack trace should one occur. +当不能从直接的周围语法推断出名称时,它仍然是一个空字符串。这样的函数将在堆栈跟踪中报告为“匿名函数”(anonymous function)。 -There are other benefits to a function being named besides the debugging question. First, the syntactic name (aka lexical name) is useful for internal self-reference. Self-reference is necessary for recursion (both sync and async) and also helpful with event handlers. +除了调试问题外,对正在命名的函数还有其他好处。首先,句法名称(又称词汇名称)对于内部自引用很有用。自引用对于递归(同步和异步)是必需的,并且对事件处理程序也很有帮助。 -Consider these different scenarios: +考虑这些不同的场景: ```js -// sync recursion: +// 同步递归: function findPropIn(propName,obj) { if (obj == undefined || typeof obj != "object") return; @@ -790,7 +779,7 @@ function findPropIn(propName,obj) { ``` ```js -// async recursion: +// 异步递归: setTimeout( function waitForIt(){ // does `it` exist yet? if (!o.it) { @@ -801,19 +790,19 @@ setTimeout( function waitForIt(){ ``` ```js -// event handler unbinding +// 事件处理解除绑定 document.getElementById( "onceBtn" ) .addEventListener( "click", function handleClick(evt){ - // unbind event + // 解除绑定 evt.target.removeEventListener( "click", handleClick, false ); // .. }, false ); ``` -In all these cases, the named function's lexical name was a useful and reliable self-reference from inside itself. +在所有这些情况下,命名函数的词法名称从内部来说是一个有用且可靠的自引用。 -Moreover, even in simple cases with one-liner functions, naming them tends to make code more self-explanatory and thus easier to read for those who haven't read it before: +此外,即使在只有一个线性函数的简单情况下,命名它们也会使代码更易于解释,因此对于以前没有阅读过它的人来说,更容易阅读: ```js people.map( function getPreferredName(person){ @@ -822,43 +811,42 @@ people.map( function getPreferredName(person){ // .. ``` -The function name `getPreferredName(..)` tells the reader something about what the mapping operation is intending to do that is not entirely obvious from just its code. This name label helps the code be more readable. +函数命名为“getpreferredname(..)”告诉读者一些关于映射操作要做什么的事情,而不仅仅是从其代码中看是显而易见的。此名称标签有助于代码更易于阅读。 -Another place where anonymous function expressions are common is with immediately invoked function expressions (IIFEs): +匿名函数表达式常见的另一个地方是立即调用的函数表达式(IIFES): ```js (function(){ - // look, I'm an IIFE! + // 看,这是立即调用函数 })(); ``` - -You virtually never see IIFEs using names for their function expressions, but they should. Why? For all the same reasons we just went over: stack trace debugging, reliable self-reference, and readability. If you can't come up with any other name for your IIFE, at least use the word IIFE: +实际上,您不会看到IIFEs在函数表达式中使用名称,但它们应该使用名称。为什么?出于所有相同的原因,我们刚刚讨论了:堆栈跟踪调试、可靠的自引用和可读性。如果你想不出你的生活的其他名字,至少要用“IIFE”这个词: ```js (function IIFE(){ - // You already knew I was an IIFE! + // 你已经知道我是立即调用函数 })(); ``` -What I'm getting at is there are multiple reasons why **named functions are always more preferable to anonymous functions.** As a matter of fact, I'd go so far as to say that there's basically never a case where an anonymous function is more preferable. They just don't really have any advantage over their named counterparts. +我的意思是,为什么**命名函数总是比匿名函数更可取,原因有很多。**事实上,我想说的是,基本上没有比匿名函数更好的情况了。他们只是没有任何优势比他们的命名对手。 -It's incredibly easy to write anonymous functions, because it's one less name we have to devote our mental attention to figuring out. +编写匿名函数是非常容易的,因为这样我们就少了一个名字来花心思去计算。 -I'll be honest; I'm as guilty of this as anyone. I don't like to struggle with naming. The first few names I come up with for a function are usually bad. I have to revisit the naming over and over. I'd much rather just punt with a good ol' anonymous function expression. +我将诚实;我和其他人一样对此感到内疚。我不喜欢纠结于命名。我为函数想到的头几个名字通常都不好。我得一遍又一遍地重新考虑这个名字。我宁愿使用一个好的匿名函数表达式。 -But we're trading ease-of-writing for pain-of-reading. This is not a good trade-off. Being lazy or uncreative enough to not want to figure out names for your functions is an all too common, but poor, excuse for using anonymous functions. +但我们正在用写作的简单性来换取阅读的痛苦。这不是一个好的权衡。懒惰或缺乏创造性,以至于不想为函数指定名称,这是使用匿名函数的常见但糟糕的借口。 -**Name every single function.** And if you sit there stumped, unable to come up with a good name for some function you've written, I'd strongly suggest you don't fully understand that function's purpose yet -- or it's just too broad or abstract. You need to go back and re-design the function until this is more clear. And by that point, a name will become more apparent. +**为每个函数命名。**如果你坐在那里手足无措,想不出一个适合你写的函数的好名字,我强烈建议你还没有完全理解这个函数的用途——或者它太宽泛或太抽象了。您需要返回并重新设计函数,直到这一点变得更加清晰。到那时,名字就会变得更加明显。 -In my practice, if I don't have a good name to use for a function, I name it `TODO` initially. I'm certain that I'll at least catch that later when I search for "TODO" comments before committing code. +在我的实践中,如果我没有一个好的函数名可以使用,我最初将其命名为' TODO '。我确信,在提交代码之前搜索“TODO”注释时,我至少会捕捉到这一点。 -I can testify from my own experience that in the struggle to name something well, I usually have come to understand it better, later, and often even refactor its design for improved readability and maintainability. +我可以从我自己的经验中证明,在努力为某个东西命名时,我通常会更好地理解它,甚至经常重构它的设计以提高可读性和可维护性。 -This time investment is well worth it. +这次投资很值得。 ### Functions Without `function` From 5069998d828ce867fa97a99bdab0de3233f2b03e Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 22 Aug 2019 09:46:41 +0800 Subject: [PATCH 43/63] Update ch2.md --- manuscript/ch2.md | 78 +++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/manuscript/ch2.md b/manuscript/ch2.md index 99124ecb..ab2ecedc 100644 --- a/manuscript/ch2.md +++ b/manuscript/ch2.md @@ -848,11 +848,11 @@ people.map( function getPreferredName(person){ 这次投资很值得。 -### Functions Without `function` +### 不用function定义的函数` -So far we've been using the full canonical syntax for functions. But you've no doubt also heard all the buzz around the ES6 `=>` arrow function syntax. +到目前为止,我们一直在使用函数的完整规范语法。但毫无疑问,您也听到了有关es6 `=>`箭头函数语法的议论。 -Compare: +对比: ```js people.map( function getPreferredName(person){ @@ -864,57 +864,56 @@ people.map( function getPreferredName(person){ people.map( person => person.nicknames[0] || person.firstName ); ``` -Whoa. +哇。 -The keyword `function` is gone, so is `return`, the parentheses (`( )`), the curly braces (`{ }`), and the innermost semicolon (`;`). In place of all that, we used a so-called fat arrow symbol (`=>`). +关键字“function”已经不存在了,还有“return”、括号(`( )`)、大括号(`{ }`)和最里面的分号(`;`)。代替所有这些,我们使用了一个所谓的箭头符号(`=>`)。 -But there's another thing we omitted. Did you spot it? The `getPreferredName` function name. +但还有一件事我们忽略了。你发现了吗?'getpreferredname'函数名也不存在了。 -That's right; `=>` arrow functions are lexically anonymous; there's no way to syntactically provide it a name. Their names can be inferred like regular functions, but again, the most common case of function expression values passed as arguments won't get any assistance in that way. Bummer. +没错;' => '箭头函数在词法上是匿名的;没有办法从语法上为它提供名称。它们的名称可以像常规函数一样进行推断,但是同样,作为参数传递的函数表达式值的最常见情况不会得到任何帮助。这点有些小失望。 -If `person.nicknames` isn't defined for some reason, an exception will be thrown, meaning this `(anonymous function)` will be at the top of the stack trace. Ugh. +如果出于某种原因没有定义“person.nicknames”,则会引发异常,这意味着这个`(匿名函数)`将位于堆栈跟踪的顶部。 -Honestly, the anonymity of `=>` arrow functions is a `=>` dagger to the heart, for me. I cannot abide by the loss of naming. It's harder to read, harder to debug, and impossible to self-reference. +老实说,对我来说,' => '箭头函数的匿名性就像一把' => '匕首刺向心脏。我不能忍受失去名字。它更难读,更难调试,也不可能自我引用。 -But if that wasn't bad enough, the other slap in the face is that there's a whole bunch of subtle syntactic variations that you must wade through if you have different scenarios for your function definition. I'm not going to cover all of them in detail here, but briefly: +但是,如果这还不够糟糕,另一个问题是,如果函数定义有不同的场景,那么您必须处理一大堆微妙的语法变化。我不打算在这里详细介绍它们,但是简单地说: ```js people.map( person => person.nicknames[0] || person.firstName ); -// multiple parameters? need ( ) +// 如果有多个参数 需要 ( ) 括起来 people.map( (person,idx) => person.nicknames[0] || person.firstName ); -// parameter destructuring? need ( ) +// 参数架构,需要 ( ) 括起来 people.map( ({ person }) => person.nicknames[0] || person.firstName ); -// parameter default? need ( ) +// 参数默认,需要 ( ) 括起来 people.map( (person = {}) => person.nicknames[0] || person.firstName ); -// returning an object? need ( ) +// 返回对象,需要 ( ) 括起来 people.map( person => ({ preferredName: person.nicknames[0] || person.firstName }) ); ``` +在函数编程领域中,“=>”之所以令人兴奋,主要是因为它几乎完全遵循函数的数学符号,尤其是在Haskell这样的函数编程语言中。' => '箭头函数语法的形状在数学上进行了通信。 -The case for excitement over `=>` in the FP world is primarily that it follows almost exactly from the mathematical notation for functions, especially in FP languages like Haskell. The shape of `=>` arrow function syntax communicates mathematically. +更进一步来说,我建议支持' => '的论据是,通过使用更轻量级的语法,我们减少了函数之间的视觉边界,这使得我们可以使用简单的函数表达式,就像我们使用延迟表达式一样——这是函数编程人员的另一个喜欢它的原因。 -Digging even further, I'd suggest that the argument in favor of `=>` is that by using much lighter-weight syntax, we reduce the visual boundaries between functions which lets us use simple function expressions much like we'd use lazy expressions -- another favorite of the FPer. +我认为大多数人都不会关注我不能忍受箭头函数的问题。他们喜欢匿名函数,喜欢节省语法。但就像我之前说的:由你决定。 -I think most FPers are going to wave off the concerns I'm sharing. They love anonymous functions and they love saving on syntax. But like I said before: you decide. +**注意:**虽然我不喜欢在实际生产代码中使用' => ',但它们在快速代码探索中非常有用。此外,在本书的其余部分中,我们将在许多地方使用箭头函数——特别是在我们介绍典型的FP实用程序时——在这些地方,代码片段中,为了优化有限的物理空间,更倾向于使用简洁。确定这种方法是否会使您自己的生产就绪代码可读性更好或更差。 -**Note:** Though I do not prefer to use `=>` in practice in my production code, they are useful in quick code explorations. Moreover, we will use arrow functions in many places throughout the rest of this book -- especially when we present typical FP utilities -- where conciseness is preferred to optimize for the limited physical space in code snippets. Make your own determinations whether this approach will make your own production-ready code more or less readable. +## this 指向的是什么? -## What's This? +如果你不熟悉JavaScript中的“this”绑定规则,我建议你看看我的书《you Don't Know JS: this & Object prototype》。对于本节的目的,我假设您知道如何为函数调用确定“this”(这是四个规则之一)。但即使你对“this”还不清楚,好消息是,我们将得出结论,如果你想做函数式编程,就不应该使用“this”。 -If you're not familiar with the `this` binding rules in JavaScript, I recommend checking out my book *You Don't Know JS: this & Object Prototypes*. For the purposes of this section, I'll assume you know how `this` gets determined for a function call (one of the four rules). But even if you're still fuzzy on *this*, the good news is we're going to conclude that you shouldn't be using `this` if you're trying to do FP. +**注意:**我们正在讨论一个我们最终会得出结论不应该使用的主题。为什么!因为讨论“this”的主题对本书后面的其他主题有影响。例如,我们对函数纯度的概念受到“this”本质上是对函数的隐式输入的影响(见[第5章](ch5.md))。此外,我们对“this”的看法会影响我们是否选择数组方法(`arr.map(..)`)与独立实用程序(`map(..,arr)`)(参见[第9章](ch9.md))。理解“this”,理解“this”为什么真的不应该成为函数式编程的一部分是至关重要的! -**Note:** We're tackling a topic that we'll ultimately conclude we shouldn't use. Why!? Because the topic of `this` has implications for other topics covered later in this book. For example, our notions of function purity are impacted by `this` being essentially an implicit input to a function (see [Chapter 5](ch5.md)). Additionally, our perspective on `this` affects whether we choose array methods (`arr.map(..)`) versus standalone utilities (`map(..,arr)`) (see [Chapter 9](ch9.md)). Understanding `this` is essential to understanding why `this` really should *not* be part of your FP! +javascript的`function`有一个“this”关键字,每个函数调用都会自动绑定该关键字。“this”关键字可以用许多不同的方式描述,但我更愿意说它为运行函数提供了一个对象上下文。 -JavaScript `function`s have a `this` keyword that's automatically bound per function call. The `this` keyword can be described in many different ways, but I prefer to say it provides an object context for the function to run against. +' this '是函数的隐式参数输入。 -`this` is an implicit parameter input for your function. - -Consider: +想一想: ```js function sum() { @@ -935,7 +934,7 @@ var s = sum.bind( context ); s(); // 3 ``` -Of course, if `this` can be input into a function implicitly, the same object context could be sent in as an explicit argument: +当然,如果可以将“this”隐式输入到函数中,则可以将相同的对象上下文作为显式参数发送: ```js function sum(ctx) { @@ -950,9 +949,9 @@ var context = { sum( context ); ``` -Simpler. And this kind of code will be a lot easier to deal with in FP. It's much easier to wire multiple functions together, or use any of the other input wrangling techniques we will get into in the next chapter, when inputs are always explicit. Doing them with implicit inputs like `this` ranges from awkward to nearly impossible depending on the scenario. +这样显得更简单。这种代码在FP中更容易处理。当输入总是显式的时候,将多个函数连接在一起,或者使用我们将在下一章中讨论的任何其他输入争用技术都要容易得多。根据场景的不同,使用诸如“this”之类的隐式输入的操作也从尴尬的境地到了无所不能的地步。 -There are other tricks we can leverage in a `this`-based system, including prototype-delegation (also covered in detail in *You Don't Know JS: this & Object Prototypes*): +在基于“this”的系统中,我们还可以利用其他一些技巧,包括原型委托(在《You don't know js:this & object prototype》中也有详细介绍): ```js var Auth = { @@ -985,11 +984,11 @@ var Login = Object.assign( Object.create( Auth ), { Login.doLogin( "fred", "123456" ); ``` -**Note:** `Object.assign(..)` is an ES6+ utility for doing a shallow assignment copy of properties from one or more source objects to a single target object: `Object.assign( target, source1, ... )`. +**注意:**`Object.assign(..)`是一个ES6+实用程序,用于将属性从一个或多个源对象浅赋值到单个目标对象:`Object.assign( target, source1, ... )'. -In case you're having trouble parsing what this code does: we have two separate objects `Login` and `Auth`, where `Login` performs prototype-delegation to `Auth`. Through delegation and the implicit `this` context sharing, these two objects virtually compose during the `this.authorize()` function call, so that properties/methods on `this` are dynamically shared with the `Auth.authorize(..)` function. +如果您在分析此代码的功能时遇到问题:我们有两个独立的对象“login”和“auth”,其中“login”执行对“auth”的原型委托。通过委托和隐式的“this”上下文共享,这两个对象实际上是在“this.authorize()”函数调用期间组成的,因此“this”上的属性/方法与“Auth.authorize(..)”函数动态共享。 -*This* code doesn't fit with various principles of FP for a variety of reasons, but one of the obvious hitches is the implicit `this` sharing. We could be more explicit about it and keep code closer to FP-friendly style: +由于各种原因,这段代码不适合函数编程的各种原则,但其中一个明显的问题是隐含的“This”共享。我们可以更明确使用,并保持代码更接近函数编程友好的风格: ```js // .. @@ -1013,19 +1012,18 @@ doLogin(user,pw) { // .. ``` +在我看来,问题不在于使用对象来组织行为。而是我们试图使用隐式输入而不是显式输入。当我使用函数式编程时,我就要明确这个“this”了。 -From my perspective, the problem is not with using objects to organize behavior. It's that we're trying to use implicit input instead of being explicit about it. When I'm wearing my FP hat, I want to leave `this` stuff on the shelf. - -## Summary +## 总结 -Functions are powerful. +函数是强大的。 -But let's be clear what a function is. It's not just a collection of statements/operations. Specifically, a function needs one or more inputs (ideally, just one!) and an output. +但是让我们清楚什么是函数。它不仅仅是语句/操作的集合。具体来说,一个函数需要一个或多个输入(理想情况下,只有一个!)和输出。 -Functions inside of functions can have closure over outer variables and remember them for later. This is one of the most important concepts in all of programming, and a fundamental foundation of FP. +函数内部的函数可以在外部变量上有闭包,并在以后记住它们。这是所有编程中最重要的概念之一,也是函数式编程的基础。 -Be careful of anonymous functions, especially `=>` arrow functions. They're convenient to write, but they shift the cost from author to reader. The whole reason we're studying FP here is to write more readable code, so don't be so quick to jump on that bandwagon. +注意匿名函数,尤其是`=>`箭头函数。他们写起来很方便,但他们把成本从作者转移到读者身上。我们在这里学习函数式编程的全部原因是为了写更可读的代码,所以不要那么快就跳上这股潮流。 -Don't use `this`-aware functions. Just don't. +不要使用“this”感知函数。不要这么做。 -You should now be developing a clear and colorful perspective in your mind of what *function* means in Functional Programming. It's time to start wrangling functions to get them to interoperate, and the next chapter teaches you a variety of critical techniques you'll need along the way. +现在,您应该在脑海中形成一个清晰而丰富的视角,来理解函数编程中的*函数*的含义。现在是时候开始讨论让它们互操作的函数了,下一章将教给您一路上需要的各种关键技术。 From 9fa5ca83bc0d518147fcda4861da99fc131488b8 Mon Sep 17 00:00:00 2001 From: Siming Date: Fri, 23 Aug 2019 09:45:27 +0800 Subject: [PATCH 44/63] Update ch3.md --- manuscript/ch3.md | 75 ++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index bcbf0b10..f3bd1768 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -1,19 +1,18 @@ -# Functional-Light JavaScript -# Chapter 3: Managing Function Inputs +# 章节3:管理函数的输入 -[Chapter 2](ch2.md) explored the core nature of JS `function`s, and laid the foundation for what makes a `function` an FP *function*. But to leverage the full power of FP, we also need patterns and practices for manipulating functions to shift and adjust their interactions -- to bend them to our will. +[第2章](ch2.md)探讨了JS函数的核心性质,并为函数以及“FP函数”奠定了基础。但是,为了充分利用函数编程的力量,我们还需要模式和实践来操纵函数来改变和调整它们的交互——使它们按我们的意愿走。 -Specifically, our attention for this chapter will be on the parameter inputs of functions. As you bring functions of all different shapes together in your programs, you'll quickly face incompatibilities in the number/order/type of inputs, as well as the need to specify some inputs at different times than others. +具体来说,我们这一章将集中在函数的参数输入上。当您将所有不同形状的函数放在一起时,您将很快面临输入的数量/顺序/类型的不兼容性,以及需要在不同的时间指定某些输入 -As a matter of fact, for stylistic purposes of readability, sometimes you'll want to define functions in a way that hides their inputs entirely! +事实上,出于可读性的风格考虑,有时您需要以完全隐藏其输入的方式定义函数! -These kinds of techniques are absolutely essential to making functions truly *function*-al. +这些技术对于使函数真正的*function*是绝对必要的。 -## All for One +## 多对一 -Imagine you're passing a function to a utility, where the utility will send multiple arguments to that function. But you may only want the function to receive a single argument. +假设您将一个函数传递给一个实用程序,实用程序将向该函数发送多个参数。但是您可能只希望函数接收单个参数。 -We can design a simple helper that wraps a function call to ensure only one argument will pass through. Since this is effectively enforcing that a function is treated as unary, let's name it as such: +我们可以设计一个简单的助手来包装一个函数调用,以确保只传递一个参数。由于这有效地强制将函数视为一元函数,我们将其命名为: @@ -24,8 +23,7 @@ function unary(fn) { }; } ``` - -Many FPers tend to prefer the shorter `=>` arrow function syntax for such code (see [Chapter 2, "Functions without `function`"](ch2.md/#functions-without-function)), such as: +对于这样的代码,许多函数编程使用者倾向于使用更短的' => '箭头函数语法(参见[第2章,“没有function定义的函数”](ch2.md/#functions-without-function)),例如: ```js var unary = @@ -34,20 +32,20 @@ var unary = fn( arg ); ``` -**Note:** No question this is more terse, sparse even. But I personally feel that whatever it may gain in symmetry with the mathematical notation, it loses more in overall readability with the functions all being anonymous, and by obscuring the scope boundaries, making deciphering closure a little more cryptic. +**注意:**毫无疑问,这更简洁,甚至稀疏。但我个人觉得,无论它在数学符号的对称性上获得了什么,它在整体可读性上损失得更多,因为所有函数都是匿名的,而且模糊了范围边界,使得解密闭包变得更加神秘。 -A commonly cited example for using `unary(..)` is with the `map(..)` utility (see [Chapter 9, "Map"](ch9.md/#map)) and `parseInt(..)`. `map(..)` calls a mapper function for each item in a list, and each time it invokes the mapper function, it passes in three arguments: `value`, `idx`, `arr`. +使用“unary(..)”的一个常见例子是使用“map(..)”实用程序(参见[章节 9,“map函数”](ch9.md/#map))和“parseInt(..)”。map(..)为列表中的每一项调用一个mapper函数,每次调用mapper函数时,都会传入三个参数:“value”、“idx”、“arr”。 -That's usually not a big deal, unless you're trying to use something as a mapper function that will behave incorrectly if it's passed too many arguments. Consider: +这通常没什么大不了的,除非您试图使用某个东西作为映射函数,如果它传递了太多参数,那么它的行为就会不正确。 +想一想: ```js ["1","2","3"].map( parseInt ); // [1,NaN,NaN] ``` +对于“parseInt(str,radix)”,很明显,当“map(..)”在第二个参数位置传递“index”时,它被“parseInt(..)”解释为进制“基数”,这是我们不想要的。 -For the signature `parseInt(str,radix)`, it's clear that when `map(..)` passes `index` in the second argument position, it's interpreted by `parseInt(..)` as the `radix`, which we don't want. - -`unary(..)` creates a function that will ignore all but the first argument passed to it, meaning the passed-in `index` is never received by `parseInt(..)` and mistaken as the `radix`: +' unary(..) '创建一个函数,该函数将忽略传递给它的除第一个参数外的所有参数,这意味着传入的' index '不会被' parseInt(..) '接收,并被误认为'基数': @@ -56,24 +54,24 @@ For the signature `parseInt(str,radix)`, it's clear that when `map(..)` passes ` // [1,2,3] ``` -### One on One +### 一对一 -Speaking of functions with only one argument, another common base utility in the FP toolbelt is a function that takes one argument and does nothing but return the value untouched: +说到只有一个参数的函数,函数式编程工具库中的另一个公共基础实用程序是一个函数,它接受一个参数,只返回未触及的值: ```js function identity(v) { return v; } -// or the ES6 => arrow form +// 或者使用ES6箭头函数 var identity = v => v; ``` -This utility looks so simple as to hardly be useful. But even simple functions can be helpful in the world of FP. Like they say in acting: there are no small parts, only small actors. +这个实用程序看起来非常简单,几乎没有什么用处。但是,即使是简单的函数,在函数式编程的世界中也是有用的。就像他们在表演中说的:没有小角色,只有小演员。 -For example, imagine you'd like to split up a string using a regular expression, but the resulting array may have some empty values in it. To discard those, we can use JS's `filter(..)` array operation (see [Chapter 9, "Filter"](ch9.md/#filter)) with `identity(..)` as the predicate: +例如,假设您想使用正则表达式拆分一个字符串,但是结果数组中可能有一些空值。为了抛弃这些,我们可以使用JS的'filter(..) '数组操作(参见[章节 9, "filter函数"](ch9.md/#filter))和' identity(..) '作为参数回调: ```js var words = " Now is the time for all... ".split( /\s|\b/ ); @@ -83,12 +81,11 @@ words; words.filter( identity ); // ["Now","is","the","time","for","all","..."] ``` +因为' identity(..) '只返回传递给它的值,JS强制每个值要么为' true '要么为' false ',这决定了是否保留或排除最后数组中的每个值。 -Because `identity(..)` simply returns the value passed to it, JS coerces each value into either `true` or `false`, and that determines whether to keep or exclude each value in the final array. +提示:在前面的例子中,可以用作谓词的另一个一元函数是JS内置的“Boolean(..)”函数,它显式地强制一个值为“true”或“false”。 -**Tip:** Another unary function that can be used as the predicate in the previous example is JS's built-in `Boolean(..)` function, which explicitly coerces a value to `true` or `false`. - -Another example of using `identity(..)` is as a default function in place of a transformation: +使用' identity(..) '的另一个例子是作为默认函数代替转换: ```js function output(msg,formatFn = identity) { @@ -103,29 +100,27 @@ function upper(txt) { output( "Hello World", upper ); // HELLO WORLD output( "Hello World" ); // Hello World ``` +您还可以看到' identity(..) '用作' map(..) '调用的默认转换函数,或作为函数列表的' reduce(..) '中的初始值;这两个实用程序都将在[第9章](ch9.md)中讨论。 -You also may see `identity(..)` used as a default transformation function for `map(..)` calls or as the initial value in a `reduce(..)` of a list of functions; both of these utilities will be covered in [Chapter 9](ch9.md). - -### Unchanging One +### 不可更改的 -Certain APIs don't let you pass a value directly into a method, but require you to pass in a function, even if that function literally just returns the value. One such API is the `then(..)` method on JS Promises: +某些API不允许将值直接传递给方法,但要求传递函数,即使该函数实际上只是返回值。JS上的“then(..)”方法就是这样一个API: ```js -// doesn't work: +// 不起作用: p1.then( foo ).then( p2 ).then( bar ); -// instead: +// 替换成: p1.then( foo ).then( function(){ return p2; } ).then( bar ); ``` -Many claim that ES6 `=>` arrow functions are the best "solution": +许多人声称ES6`=>`箭头函数是最好的“解决方案”: ```js p1.then( foo ).then( () => p2 ).then( bar ); ``` -But there's an FP utility that's more well suited for the task: - +但是有一个fp实用程序更适合此任务: ```js function constant(v) { return function value(){ @@ -140,19 +135,19 @@ var constant = v; ``` -With this tidy little FP utility, we can solve our `then(..)` annoyance properly: +有了这个整洁的小fp工具,我们可以正确地解决“then(…)”的烦恼: ```js p1.then( foo ).then( constant( p2 ) ).then( bar ); ``` -**Warning:** Although the `() => p2` arrow function version is shorter than `constant(p2)`, I would encourage you to resist the temptation to use it. The arrow function is returning a value from outside of itself, which is a bit worse from the FP perspective. We'll cover the pitfalls of such actions later in the book (see [Chapter 5](ch5.md)). +**警告:**虽然`()=>p2`箭头函数版本比`constant(p2)`简短,但我建议你不要这么使用。arrow函数从自身外部返回一个值,从fp的角度来看,这有点糟糕。我们稍后将在本书中讨论这些行为的陷阱(见[第5章](ch5.md))。 -## Adapting Arguments to Parameters +## 使参数适应参数 -There are a variety of patterns and tricks we can use to adapt a function's signature to match the kinds of arguments we want to provide to it. +我们可以使用多种模式和技巧来调整函数的签名,以匹配我们希望为其提供的参数类型。 -Recall [this function signature from Chapter 2](ch2.md/#user-content-funcparamdestr) which highlights using array parameter destructuring: +回想一下[第2章中的这个函数签名](ch2.md/user content funcparamdestr),它强调使用数组参数解构: ```js function foo( [x,y,...args] = [] ) { From c726c5506c0cf73a9ab2cf713f7e9f44f8ddf7cd Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 26 Aug 2019 09:39:20 +0800 Subject: [PATCH 45/63] Update ch3.md --- manuscript/ch3.md | 101 +++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index f3bd1768..24af5095 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -104,7 +104,7 @@ output( "Hello World" ); // Hello World ### 不可更改的 -某些API不允许将值直接传递给方法,但要求传递函数,即使该函数实际上只是返回值。JS上的“then(..)”方法就是这样一个API: +某些API不允许将值直接传递给方法,但要求传递函数,即使该函数实际上只是返回值。JS上的“then(..)”方法就是这样一个API: ```js // 不起作用: @@ -141,21 +141,21 @@ var constant = p1.then( foo ).then( constant( p2 ) ).then( bar ); ``` -**警告:**虽然`()=>p2`箭头函数版本比`constant(p2)`简短,但我建议你不要这么使用。arrow函数从自身外部返回一个值,从fp的角度来看,这有点糟糕。我们稍后将在本书中讨论这些行为的陷阱(见[第5章](ch5.md))。 +**警告:**虽然`() =>p2`箭头函数版本比`constant(p2)`简短,但我建议你不要这么使用。arrow函数从自身外部返回一个值,从fp的角度来看,这有点糟糕。我们稍后将在本书中讨论这些行为的陷阱(见[第5章](ch5.md))。 ## 使参数适应参数 我们可以使用多种模式和技巧来调整函数的签名,以匹配我们希望为其提供的参数类型。 -回想一下[第2章中的这个函数签名](ch2.md/user content funcparamdestr),它强调使用数组参数解构: +回想一下[第2章中的这个函数签名](ch2.md/user content funcparamdestr),它强调使用数组参数解构: ```js function foo( [x,y,...args] = [] ) { ``` -This pattern is handy if an array will be passed in but you want to treat its contents as individual parameters. `foo(..)` is thus technically unary -- when it's executed, only one argument (an array) will be passed to it. But inside the function, you get to address different inputs (`x`, `y`, etc) individually. +如果传入数组,但希望将其内容视为单个参数,则此模式很方便。`因此,foo(..)`在技术上是一元的——当它被执行时,只有一个参数(数组)将被传递给它。但是在函数内部,您可以分别处理不同的输入(`x`,`y`,等等)。 -However, sometimes you won't have the ability to change the declaration of the function to use array parameter destructuring. For example, imagine these functions: +但是,有时您无法将函数的声明更改为使用数组参数析构化。例如,假设这些函数: ```js function foo(x,y) { @@ -166,16 +166,15 @@ function bar(fn) { fn( [ 3, 9 ] ); } -bar( foo ); // fails +bar( foo ); // 失败 ``` +你看出`bar(foo)`失败的原因了吗? -Do you spot why `bar(foo)` fails? +数组`[3,9]`作为单个值发送到'fn(..)`,但'foo(..)`需要分别为'x'和'y'。如果我们能把“foo(..)”的声明改为“function foo([x,y])”,我们就可以了。或者,如果我们可以更改“bar(…)”的行为,使调用成为“fn(…[3,9])”,则值“3”和“9”将分别传入。 -The array `[3,9]` is sent in as a single value to `fn(..)`, but `foo(..)` expects `x` and `y` separately. If we could change the declaration of `foo(..)` to be `function foo([x,y]) { ..`, we'd be fine. Or, if we could change the behavior of `bar(..)` to make the call as `fn(...[3,9])`, the values `3` and `9` would be passed in individually. +有时,当您有两个函数以这种方式不兼容时,您将无法更改它们的声明/定义。那么,你怎么能同时使用它们呢? -There will be occasions when you have two functions that are incompatible in this way, and you won't be able to change their declarations/definitions. So, how can you use them together? - -We can define a helper to adapt a function so that it spreads out a single received array as its individual arguments: +我们可以定义一个中间函数来调整一个函数,这样它就会展开一个单独的接收数组作为它的参数: @@ -186,24 +185,24 @@ function spreadArgs(fn) { }; } -// or the ES6 => arrow form +// 或者使用ES6箭头函数 var spreadArgs = fn => argsArr => fn( ...argsArr ); ``` -**Note:** I called this helper `spreadArgs(..)`, but in libraries like Ramda it's commonly called `apply(..)`. +**注意:**我将这个中间函数称为“spreadArgs(..)”,但是在像Ramda这样的库中,它通常被称为“apply(..)”。 -Now we can use `spreadArgs(..)` to adapt `foo(..)` to work as the proper input to `bar(..)`: +现在我们可以使用“spreadArgs(..)”来调整“foo(..)”作为“bar(..)”的正确输入: ```js bar( spreadArgs( foo ) ); // 12 ``` -It won't seem clear yet why these occasions arise, but you will see them often. Essentially, `spreadArgs(..)` allows us to define functions that `return` multiple values via an array, but still have those multiple values treated independently as inputs to another function. +现在还不清楚为什么会出现这种情况,但你会经常看到。实际上,“spreadArgs(..)”允许我们定义一些函数,这些函数通过数组“返回”多个值,但仍然将这些值作为另一个函数的输入单独处理。 -While we're talking about a `spreadArgs(..)` utility, let's also define a utility to handle the opposite action: +当我们讨论一个“spreadArgs(..)”工具时,让我们也定义一个工具来处理相反的操作: ```js function gatherArgs(fn) { @@ -212,16 +211,16 @@ function gatherArgs(fn) { }; } -// or the ES6 => arrow form +// 或者使用ES6箭头函数 var gatherArgs = fn => (...argsArr) => fn( argsArr ); ``` -**Note:** In Ramda, this utility is referred to as `unapply(..)`, being that it's the opposite of `apply(..)`. I think the "spread"/"gather" terminology is a little more descriptive for what's going on. +**注意:**在Ramda中,这个实用程序被称为“unapply(..)”,因为它与“apply(..)”相反。我认为用“扩展”/“收集”的术语更能描述这个。 -We can use this utility to gather individual arguments into a single array, perhaps because we want to adapt a function with array parameter destructuring to another utility that passes arguments separately. We will [cover `reduce(..)` more fully in Chapter 9](ch9.md/#reduce); in short, it repeatedly calls its reducer function with two individual parameters, which we can now *gather* together: +我们可以使用这个实用程序将单个参数收集到一个数组中,这可能是因为我们希望将一个具有数组参数析构的函数调整为另一个单独传递参数的实用程序。我们将[在第9章更全面地讨论' reduce(..) '](ch9.md/#reduce);简而言之,它用两个单独的参数反复调用它的减速函数,我们现在可以将它们*集合*在一起: ```js function combineFirstTwo([ v1, v2 ]) { @@ -232,11 +231,11 @@ function combineFirstTwo([ v1, v2 ]) { // 15 ``` -## Some Now, Some Later +## 现在与稍后的调用 -If a function takes multiple arguments, you may want to specify some of those up front and leave the rest to be specified later. +如果一个函数接受多个参数,您可能希望预先指定其中一些参数,其余的参数留待以后指定。 -Consider this function: +思考下面这个函数: ```js function ajax(url,data,callback) { @@ -244,11 +243,11 @@ function ajax(url,data,callback) { } ``` -Let's imagine you'd like to set up several API calls where the URLs are known up front, but the data and the callback to handle the response won't be known until later. +假设您想设置几个API调用,其中url在前面是已知的,但是处理响应的数据和回调要到稍后才会知道。 -Of course, you can just defer making the `ajax(..)` call until all the bits are known, and refer to some global constant for the URL at that time. But another way is to create a function reference that already has the `url` argument preset. +当然,您可以推迟执行' ajax(..) '调用,直到所有的位都被知道,并在那个时候引用URL的某个全局常量。但另一种方法是创建一个函数引用,它已经预先设置了“url”参数。 -What we're going to do is make a new function that still calls `ajax(..)` under the covers, and it manually sets the first argument to the API URL you care about, while waiting to accept the other two arguments later: +我们要做的是创建一个新函数,它仍然调用' ajax(..) ',并手动将第一个参数设置API URL,同时等待稍后接受其他两个参数: ```js function getPerson(data,cb) { @@ -260,7 +259,7 @@ function getOrder(data,cb) { } ``` -Manually specifying these function call wrappers is certainly possible, but it may get quite tedious, especially if there will also be variations with different arguments preset, like: +手动指定这些函数的封装当然是可能的,但它可能会变得相当乏味,特别是如果还会有不同的参数预置变化,如: ```js function getCurrentUser(cb) { @@ -268,15 +267,15 @@ function getCurrentUser(cb) { } ``` -One practice an FPer gets very used to is looking for patterns where we do the same sorts of things repeatedly, and trying to turn those actions into generic reusable utilities. As a matter of fact, I'm sure that's already the instinct for many of you readers, so that's not uniquely an FP thing. But it's unquestionably important for FP. +函数编程人员非常习惯的一种实践是寻找重复执行相同类型的操作的模式,并尝试将这些操作转换为通用的可重用实用程序。事实上,我相信这已经是你们许多读者的本能,所以这并不是函数式编程所独有的。但毫无疑问,这对函数式编程很重要。 -To conceive such a utility for argument presetting, let's examine conceptually what's going on, not just looking at the manual implementations shown here. +要构思这样一个用于参数预置的实用程序,让我们从概念上研究一下发生了什么,而不只是查看这里显示的手动实现。 -One way to articulate what's going on is that the `getOrder(data,cb)` function is a *partial application* of the `ajax(url,data,cb)` function. This terminology comes from the notion that arguments are *applied* to parameters at the function call-site. And as you can see, we're only applying some of the arguments up front -- specifically, the argument for the `url` parameter -- while leaving the rest to be applied later. +一种明确的方法是“getOrder(data,cb)”函数是“ajax(url,data,cb)”函数的“部分应用程序”。这个术语来自于这样一个概念:参数被“应用”到函数调用站点的参数上。正如您所看到的,我们只在前面应用了一些参数——具体地说,就是“url”参数的参数——其余的参数将在稍后应用。 -To be a tiny bit more formal about this pattern, partial application is strictly a reduction in a function's arity; remember, that's the number of expected parameter inputs. We reduced the original `ajax(..)` function's arity from 3 to 2 for the `getOrder(..)` function. +更正式地说,部分应用严格地减少了函数的特性;记住,这是期望参数输入的个数。对于“getOrder(..)”函数,我们将原来的“ajax(..)”函数的特性从3减少到2。 -Let's define a `partial(..)` utility: +定义一个`partial(..)`工具函数: ```js function partial(fn,...presetArgs) { @@ -285,26 +284,26 @@ function partial(fn,...presetArgs) { }; } -// or the ES6 => arrow form +// 或使用ES6箭头函数 var partial = (fn,...presetArgs) => (...laterArgs) => fn( ...presetArgs, ...laterArgs ); ``` -**Tip:** Don't just take this snippet at face value. Pause for a few moments to digest what's going on with this utility. Make sure you really *get it*. +**提示:**不要只看这个片段的表面价值。暂停几分钟,消化一下这个实用程序到底发生了什么。确保你真的明白了。 -The `partial(..)` function takes an `fn` for which function we are partially applying. Then, any subsequent arguments passed in are gathered into the `presetArgs` array and saved for later. +' partial(..) '函数接受一个' fn '函数,我们对该函数进行了部分应用。然后,传入的任何后续参数都被收集到“presetArgs”数组中,并保存到后面。 -A new inner function (called `partiallyApplied(..)` just for clarity) is created and `return`ed; the inner function's own arguments are gathered into an array called `laterArgs`. +创建一个新的内部函数(称为' partiallyApplied(..) ',只是为了清晰起见)并返回;内部函数自身的参数被收集到一个名为“laterArgs”的数组中。 -Notice the references to `fn` and `presetArgs` inside this inner function? How does that work? After `partial(..)` finishes running, how does the inner function keep being able to access `fn` and `presetArgs`? If you answered **closure**, you're right on track! The inner function `partiallyApplied(..)` closes over both the `fn` and `presetArgs` variables so it can keep accessing them later, no matter where the function runs. This is why understanding closure is critical! +注意到内部函数中对' fn '和' presetArgs '的引用了吗?这是怎么回事?在' partial(..) '运行完成后,内部函数如何能够继续访问' fn '和' presetArgs ' ?如果您回答的是**闭包**,那么你是正确的!内部函数“partiallyApplied(..)”对“fn”和“presetArgs”变量都关闭,无论函数运行在何处,以后都可以继续访问它们,。这就是为什么理解闭包是至关重要的! -When the `partiallyApplied(..)` function is later executed somewhere else in your program, it uses the closed over `fn` to execute the original function, first providing any of the (closed over) `presetArgs` partial application arguments, then any further `laterArgs` arguments. +当“partiallyapplied(..)”函数在程序中的其他地方执行时,它使用关闭的“fn”来执行原始函数,首先提供任何“presetargs”部分应用程序参数,然后再提供其他“laterargs”参数。 -If any of that was confusing, stop and go re-read it. Trust me, you'll be glad you did as we get further into the text. +如果有什么让人困惑的话,停下来再读一遍。相信我,你会很高兴我们能更深入地了解。 -Let's now use the `partial(..)` utility to make those earlier partially applied functions: +现在,让我们使用' partial(..) '函数来实现前面那些部分应用的函数: ```js var getPerson = partial( ajax, "http://some.api/person" ); @@ -312,7 +311,7 @@ var getPerson = partial( ajax, "http://some.api/person" ); var getOrder = partial( ajax, "http://some.api/order" ); ``` -Take a moment to consider the shape/internals of `getPerson(..)`. It will look sorta like this: +花点时间考虑一下“getPerson(..)”的形状/内部结构。它看起来是这样的: ```js var getPerson = function partiallyApplied(...laterArgs) { @@ -320,28 +319,28 @@ var getPerson = function partiallyApplied(...laterArgs) { }; ``` -The same will be true of `getOrder(..)`. But what about `getCurrentUser(..)`? +getOrder(..)也是如此。但是' getCurrentUser(..) '呢? ```js -// version 1 +// 版本 1 var getCurrentUser = partial( ajax, "http://some.api/person", { user: CURRENT_USER_ID } ); -// version 2 +// 版本 2 var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); ``` -We can either define `getCurrentUser(..)` with both the `url` and `data` arguments specified directly (version 1), or define `getCurrentUser(..)` as a partial application of the `getPerson(..)` partial application, specifying only the additional `data` argument (version 2). +我们可以使用直接指定的“url”和“data”参数定义“getCurrentUser(..)”(版本1),或者将“getCurrentUser(..)”定义为“getPerson(..)”部分应用程序的部分应用程序,只指定附加的“data”参数(版本2)。 -Version 2 is a little cleaner to express because it reuses something already defined. As such, I think it fits a little closer to the spirit of FP. +版本2更便于表达,因为它重用了已经定义的内容。因此,我认为它更符合函数式编程的概念。 -Just to make sure we understand how these two versions will work under the covers, they look respectively kinda like: +为了确保我们理解这两个版本在背后是如何工作的,它们看起来分别有点像: ```js -// version 1 +// 版本 1 var getCurrentUser = function partiallyApplied(...laterArgs) { return ajax( "http://some.api/person", @@ -350,7 +349,7 @@ var getCurrentUser = function partiallyApplied(...laterArgs) { ); }; -// version 2 +// 版本 2 var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs){ var getPerson = function innerPartiallyApplied(...innerLaterArgs){ return ajax( "http://some.api/person", ...innerLaterArgs ); @@ -360,11 +359,11 @@ var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs){ } ``` -Again, stop and re-read those code snippets to make sure you understand what's going on there. +同样,停止并重新阅读这些代码片段,以确保您理解其中的内容。 -**Note:** Version 2 has an extra layer of function wrapping involved. That may smell strange and unnecessary, but this is just one of those things in FP that you'll want to get really comfortable with. We'll be wrapping many layers of functions onto each other as we progress through the text. Remember, this is *function*al programming! +**注意:** 版本 2包含一个额外的函数包装层。这可能听起来很奇怪,也没有必要,但这只是函数编程中你想要真正熟悉的东西之一。在阅读文本的过程中,我们将把许多层函数相互包装起来。记住,这是*函数*编程! -Let's take a look at another example of the usefulness of partial application. Consider an `add(..)` function which takes two arguments and adds them together: +让我们看一下部分应用程序的另一个有用的例子。考虑一个' add(..) '函数,它接受两个参数并将它们相加: ```js function add(x,y) { From 13ce2081937dfe9ed0037a64e7b45b04326e020f Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 27 Aug 2019 09:51:31 +0800 Subject: [PATCH 46/63] Update ch3.md --- manuscript/ch3.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index 24af5095..202b8936 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -371,7 +371,7 @@ function add(x,y) { } ``` -Now imagine we'd like take a list of numbers and add a certain number to each of them. We'll use the `map(..)` utility (see [Chapter 9, "Map"](ch9.md/#map)) built into JS arrays: +现在假设我们想要一个数字列表,并在每个列表中添加一个特定的数字。我们将使用内置在JS数组中的 `map(..)`(see [Chapter 9, "Map"](ch9.md/#map)) : ```js [1,2,3,4,5].map( function adder(val){ @@ -380,36 +380,36 @@ Now imagine we'd like take a list of numbers and add a certain number to each of // [4,5,6,7,8] ``` -The reason we can't pass `add(..)` directly to `map(..)` is because the signature of `add(..)` doesn't match the mapping function that `map(..)` expects. That's where partial application can help us: we can adapt the signature of `add(..)` to something that will match: +我们不能直接将' add(..) '传递给' map(..) '的原因是' add(..) '的签名与' map(..) '的映射函数不匹配。这就是部分应用程序可以帮助我们的地方:我们可以修改`add(..)`的签名,使之匹配: ```js [1,2,3,4,5].map( partial( add, 3 ) ); // [4,5,6,7,8] ``` -The `partial(add,3)` call produces a new unary function which is expecting only one more argument. +`partial(add,3)`调用生成一个新的一元函数,该函数只需要一个参数。 -The `map(..)` utility will loop through the array (`[1,2,3,4,5]`) and repeatedly call this unary function, once for each of those values, respectively. So, the calls made will effectively be `add(3,1)`, `add(3,2)`, `add(3,3)`, `add(3,4)`, and `add(3,5)`. The array of those results is `[4,5,6,7,8]`. +`map(..)`将循环遍历数组(`[1,2,3,4,5]`),并分别为每个值重复调用这个一元函数一次。因此,有效地调用`add(3,1)`, `add(3,2)`, `add(3,3)`, `add(3,4)`, and `add(3,5)`,产生结果的数组是'[4,5,6,7,8]' -### `bind(..)` +### `bind(..)函数` -JavaScript functions all have a built-in utility called `bind(..)`. It has two capabilities: presetting the `this` context and partially applying arguments. +javascript函数都有一个名为`bind(..)`的内置函数。它有两个功能:预设`this`上下文和应用参数。 -I think it's incredibly misguided to conflate these two capabilities in one utility. Sometimes you'll want to hard-bind the `this` context and not partially apply arguments. Other times you'll want to partially apply arguments but not care about `this` binding at all. I have never needed both at the same time. +我认为把这两种功能混为一谈是非常错误的。有时您需要绑定`this`上下文,而不是应用参数。其他时候,您可能希望作为应用参数,根本不关心`this`绑定。我是从来没有同时需要这两者的。 -The latter scenario (partial application without setting `this` context) is awkward because you have to pass an ignorable placeholder for the `this`-binding argument (the first one), usually `null`. +后一种方案比较尴尬(不设置`this`上下文的应用函数),因为必须为 `this`绑定参数(第一个)传递一个可忽略的占位符,通常为“null”。 -Consider: +想一想: ```js var getPerson = ajax.bind( null, "http://some.api/person" ); ``` -That `null` just bugs me to no end. Despite this *this* annoyance, it's mildly convenient that JS has a built-in utility for partial application. However, most FP programmers prefer using the dedicated `partial(..)` utility in their chosen FP library. +那个`null` 让人心烦。JS为部分应用程序提供了一个内置的实用程序,这还是比较方便的。然而,大多数函数编程程序员更喜欢在他们选择的函数编程库中使用专用的`partial(..)`实用程序。 -### Reversing Arguments +### 反转参数 -Recall that the signature for our Ajax function is: `ajax( url, data, cb )`. What if we wanted to partially apply the `cb` but wait to specify `data` and `url` later? We could create a utility that wraps a function to reverse its argument order: +回想一下,我们的Ajax函数的签名是:`ajax( url, data, cb )`。如果我们想要部分应用 `cb`,但要等到稍后指定`data`和`url`,又该怎么办?我们可以创建一个实用程序来包装一个函数,以逆转其参数顺序: ```js function reverseArgs(fn) { @@ -418,14 +418,14 @@ function reverseArgs(fn) { }; } -// or the ES6 => arrow form +// 运用箭头函数 var reverseArgs = fn => (...args) => fn( ...args.reverse() ); ``` -Now we can reverse the order of the `ajax(..)` arguments, so that we can then partially apply from the right rather than the left. To restore the expected order, we'll then reverse the subsequent partially applied function: +现在我们可以颠倒`ajax(..)`参数的顺序,这样我们就可以从右边而不是左边部分地应用。为了恢复预期的顺序,我们将反转后面部分应用的函数: ```js var cache = {}; From 7a9f53a8a9cadab1c1ec34df3fe59225e1bbcf2a Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 27 Aug 2019 09:54:42 +0800 Subject: [PATCH 47/63] Update ch3.md --- manuscript/ch3.md | 198 ++++++++++++++++++++++------------------------ 1 file changed, 96 insertions(+), 102 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index bcbf0b10..202b8936 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -1,19 +1,18 @@ -# Functional-Light JavaScript -# Chapter 3: Managing Function Inputs +# 章节3:管理函数的输入 -[Chapter 2](ch2.md) explored the core nature of JS `function`s, and laid the foundation for what makes a `function` an FP *function*. But to leverage the full power of FP, we also need patterns and practices for manipulating functions to shift and adjust their interactions -- to bend them to our will. +[第2章](ch2.md)探讨了JS函数的核心性质,并为函数以及“FP函数”奠定了基础。但是,为了充分利用函数编程的力量,我们还需要模式和实践来操纵函数来改变和调整它们的交互——使它们按我们的意愿走。 -Specifically, our attention for this chapter will be on the parameter inputs of functions. As you bring functions of all different shapes together in your programs, you'll quickly face incompatibilities in the number/order/type of inputs, as well as the need to specify some inputs at different times than others. +具体来说,我们这一章将集中在函数的参数输入上。当您将所有不同形状的函数放在一起时,您将很快面临输入的数量/顺序/类型的不兼容性,以及需要在不同的时间指定某些输入 -As a matter of fact, for stylistic purposes of readability, sometimes you'll want to define functions in a way that hides their inputs entirely! +事实上,出于可读性的风格考虑,有时您需要以完全隐藏其输入的方式定义函数! -These kinds of techniques are absolutely essential to making functions truly *function*-al. +这些技术对于使函数真正的*function*是绝对必要的。 -## All for One +## 多对一 -Imagine you're passing a function to a utility, where the utility will send multiple arguments to that function. But you may only want the function to receive a single argument. +假设您将一个函数传递给一个实用程序,实用程序将向该函数发送多个参数。但是您可能只希望函数接收单个参数。 -We can design a simple helper that wraps a function call to ensure only one argument will pass through. Since this is effectively enforcing that a function is treated as unary, let's name it as such: +我们可以设计一个简单的助手来包装一个函数调用,以确保只传递一个参数。由于这有效地强制将函数视为一元函数,我们将其命名为: @@ -24,8 +23,7 @@ function unary(fn) { }; } ``` - -Many FPers tend to prefer the shorter `=>` arrow function syntax for such code (see [Chapter 2, "Functions without `function`"](ch2.md/#functions-without-function)), such as: +对于这样的代码,许多函数编程使用者倾向于使用更短的' => '箭头函数语法(参见[第2章,“没有function定义的函数”](ch2.md/#functions-without-function)),例如: ```js var unary = @@ -34,20 +32,20 @@ var unary = fn( arg ); ``` -**Note:** No question this is more terse, sparse even. But I personally feel that whatever it may gain in symmetry with the mathematical notation, it loses more in overall readability with the functions all being anonymous, and by obscuring the scope boundaries, making deciphering closure a little more cryptic. +**注意:**毫无疑问,这更简洁,甚至稀疏。但我个人觉得,无论它在数学符号的对称性上获得了什么,它在整体可读性上损失得更多,因为所有函数都是匿名的,而且模糊了范围边界,使得解密闭包变得更加神秘。 -A commonly cited example for using `unary(..)` is with the `map(..)` utility (see [Chapter 9, "Map"](ch9.md/#map)) and `parseInt(..)`. `map(..)` calls a mapper function for each item in a list, and each time it invokes the mapper function, it passes in three arguments: `value`, `idx`, `arr`. +使用“unary(..)”的一个常见例子是使用“map(..)”实用程序(参见[章节 9,“map函数”](ch9.md/#map))和“parseInt(..)”。map(..)为列表中的每一项调用一个mapper函数,每次调用mapper函数时,都会传入三个参数:“value”、“idx”、“arr”。 -That's usually not a big deal, unless you're trying to use something as a mapper function that will behave incorrectly if it's passed too many arguments. Consider: +这通常没什么大不了的,除非您试图使用某个东西作为映射函数,如果它传递了太多参数,那么它的行为就会不正确。 +想一想: ```js ["1","2","3"].map( parseInt ); // [1,NaN,NaN] ``` +对于“parseInt(str,radix)”,很明显,当“map(..)”在第二个参数位置传递“index”时,它被“parseInt(..)”解释为进制“基数”,这是我们不想要的。 -For the signature `parseInt(str,radix)`, it's clear that when `map(..)` passes `index` in the second argument position, it's interpreted by `parseInt(..)` as the `radix`, which we don't want. - -`unary(..)` creates a function that will ignore all but the first argument passed to it, meaning the passed-in `index` is never received by `parseInt(..)` and mistaken as the `radix`: +' unary(..) '创建一个函数,该函数将忽略传递给它的除第一个参数外的所有参数,这意味着传入的' index '不会被' parseInt(..) '接收,并被误认为'基数': @@ -56,24 +54,24 @@ For the signature `parseInt(str,radix)`, it's clear that when `map(..)` passes ` // [1,2,3] ``` -### One on One +### 一对一 -Speaking of functions with only one argument, another common base utility in the FP toolbelt is a function that takes one argument and does nothing but return the value untouched: +说到只有一个参数的函数,函数式编程工具库中的另一个公共基础实用程序是一个函数,它接受一个参数,只返回未触及的值: ```js function identity(v) { return v; } -// or the ES6 => arrow form +// 或者使用ES6箭头函数 var identity = v => v; ``` -This utility looks so simple as to hardly be useful. But even simple functions can be helpful in the world of FP. Like they say in acting: there are no small parts, only small actors. +这个实用程序看起来非常简单,几乎没有什么用处。但是,即使是简单的函数,在函数式编程的世界中也是有用的。就像他们在表演中说的:没有小角色,只有小演员。 -For example, imagine you'd like to split up a string using a regular expression, but the resulting array may have some empty values in it. To discard those, we can use JS's `filter(..)` array operation (see [Chapter 9, "Filter"](ch9.md/#filter)) with `identity(..)` as the predicate: +例如,假设您想使用正则表达式拆分一个字符串,但是结果数组中可能有一些空值。为了抛弃这些,我们可以使用JS的'filter(..) '数组操作(参见[章节 9, "filter函数"](ch9.md/#filter))和' identity(..) '作为参数回调: ```js var words = " Now is the time for all... ".split( /\s|\b/ ); @@ -83,12 +81,11 @@ words; words.filter( identity ); // ["Now","is","the","time","for","all","..."] ``` +因为' identity(..) '只返回传递给它的值,JS强制每个值要么为' true '要么为' false ',这决定了是否保留或排除最后数组中的每个值。 -Because `identity(..)` simply returns the value passed to it, JS coerces each value into either `true` or `false`, and that determines whether to keep or exclude each value in the final array. +提示:在前面的例子中,可以用作谓词的另一个一元函数是JS内置的“Boolean(..)”函数,它显式地强制一个值为“true”或“false”。 -**Tip:** Another unary function that can be used as the predicate in the previous example is JS's built-in `Boolean(..)` function, which explicitly coerces a value to `true` or `false`. - -Another example of using `identity(..)` is as a default function in place of a transformation: +使用' identity(..) '的另一个例子是作为默认函数代替转换: ```js function output(msg,formatFn = identity) { @@ -103,29 +100,27 @@ function upper(txt) { output( "Hello World", upper ); // HELLO WORLD output( "Hello World" ); // Hello World ``` +您还可以看到' identity(..) '用作' map(..) '调用的默认转换函数,或作为函数列表的' reduce(..) '中的初始值;这两个实用程序都将在[第9章](ch9.md)中讨论。 -You also may see `identity(..)` used as a default transformation function for `map(..)` calls or as the initial value in a `reduce(..)` of a list of functions; both of these utilities will be covered in [Chapter 9](ch9.md). - -### Unchanging One +### 不可更改的 -Certain APIs don't let you pass a value directly into a method, but require you to pass in a function, even if that function literally just returns the value. One such API is the `then(..)` method on JS Promises: +某些API不允许将值直接传递给方法,但要求传递函数,即使该函数实际上只是返回值。JS上的“then(..)”方法就是这样一个API: ```js -// doesn't work: +// 不起作用: p1.then( foo ).then( p2 ).then( bar ); -// instead: +// 替换成: p1.then( foo ).then( function(){ return p2; } ).then( bar ); ``` -Many claim that ES6 `=>` arrow functions are the best "solution": +许多人声称ES6`=>`箭头函数是最好的“解决方案”: ```js p1.then( foo ).then( () => p2 ).then( bar ); ``` -But there's an FP utility that's more well suited for the task: - +但是有一个fp实用程序更适合此任务: ```js function constant(v) { return function value(){ @@ -140,27 +135,27 @@ var constant = v; ``` -With this tidy little FP utility, we can solve our `then(..)` annoyance properly: +有了这个整洁的小fp工具,我们可以正确地解决“then(…)”的烦恼: ```js p1.then( foo ).then( constant( p2 ) ).then( bar ); ``` -**Warning:** Although the `() => p2` arrow function version is shorter than `constant(p2)`, I would encourage you to resist the temptation to use it. The arrow function is returning a value from outside of itself, which is a bit worse from the FP perspective. We'll cover the pitfalls of such actions later in the book (see [Chapter 5](ch5.md)). +**警告:**虽然`() =>p2`箭头函数版本比`constant(p2)`简短,但我建议你不要这么使用。arrow函数从自身外部返回一个值,从fp的角度来看,这有点糟糕。我们稍后将在本书中讨论这些行为的陷阱(见[第5章](ch5.md))。 -## Adapting Arguments to Parameters +## 使参数适应参数 -There are a variety of patterns and tricks we can use to adapt a function's signature to match the kinds of arguments we want to provide to it. +我们可以使用多种模式和技巧来调整函数的签名,以匹配我们希望为其提供的参数类型。 -Recall [this function signature from Chapter 2](ch2.md/#user-content-funcparamdestr) which highlights using array parameter destructuring: +回想一下[第2章中的这个函数签名](ch2.md/user content funcparamdestr),它强调使用数组参数解构: ```js function foo( [x,y,...args] = [] ) { ``` -This pattern is handy if an array will be passed in but you want to treat its contents as individual parameters. `foo(..)` is thus technically unary -- when it's executed, only one argument (an array) will be passed to it. But inside the function, you get to address different inputs (`x`, `y`, etc) individually. +如果传入数组,但希望将其内容视为单个参数,则此模式很方便。`因此,foo(..)`在技术上是一元的——当它被执行时,只有一个参数(数组)将被传递给它。但是在函数内部,您可以分别处理不同的输入(`x`,`y`,等等)。 -However, sometimes you won't have the ability to change the declaration of the function to use array parameter destructuring. For example, imagine these functions: +但是,有时您无法将函数的声明更改为使用数组参数析构化。例如,假设这些函数: ```js function foo(x,y) { @@ -171,16 +166,15 @@ function bar(fn) { fn( [ 3, 9 ] ); } -bar( foo ); // fails +bar( foo ); // 失败 ``` +你看出`bar(foo)`失败的原因了吗? -Do you spot why `bar(foo)` fails? +数组`[3,9]`作为单个值发送到'fn(..)`,但'foo(..)`需要分别为'x'和'y'。如果我们能把“foo(..)”的声明改为“function foo([x,y])”,我们就可以了。或者,如果我们可以更改“bar(…)”的行为,使调用成为“fn(…[3,9])”,则值“3”和“9”将分别传入。 -The array `[3,9]` is sent in as a single value to `fn(..)`, but `foo(..)` expects `x` and `y` separately. If we could change the declaration of `foo(..)` to be `function foo([x,y]) { ..`, we'd be fine. Or, if we could change the behavior of `bar(..)` to make the call as `fn(...[3,9])`, the values `3` and `9` would be passed in individually. +有时,当您有两个函数以这种方式不兼容时,您将无法更改它们的声明/定义。那么,你怎么能同时使用它们呢? -There will be occasions when you have two functions that are incompatible in this way, and you won't be able to change their declarations/definitions. So, how can you use them together? - -We can define a helper to adapt a function so that it spreads out a single received array as its individual arguments: +我们可以定义一个中间函数来调整一个函数,这样它就会展开一个单独的接收数组作为它的参数: @@ -191,24 +185,24 @@ function spreadArgs(fn) { }; } -// or the ES6 => arrow form +// 或者使用ES6箭头函数 var spreadArgs = fn => argsArr => fn( ...argsArr ); ``` -**Note:** I called this helper `spreadArgs(..)`, but in libraries like Ramda it's commonly called `apply(..)`. +**注意:**我将这个中间函数称为“spreadArgs(..)”,但是在像Ramda这样的库中,它通常被称为“apply(..)”。 -Now we can use `spreadArgs(..)` to adapt `foo(..)` to work as the proper input to `bar(..)`: +现在我们可以使用“spreadArgs(..)”来调整“foo(..)”作为“bar(..)”的正确输入: ```js bar( spreadArgs( foo ) ); // 12 ``` -It won't seem clear yet why these occasions arise, but you will see them often. Essentially, `spreadArgs(..)` allows us to define functions that `return` multiple values via an array, but still have those multiple values treated independently as inputs to another function. +现在还不清楚为什么会出现这种情况,但你会经常看到。实际上,“spreadArgs(..)”允许我们定义一些函数,这些函数通过数组“返回”多个值,但仍然将这些值作为另一个函数的输入单独处理。 -While we're talking about a `spreadArgs(..)` utility, let's also define a utility to handle the opposite action: +当我们讨论一个“spreadArgs(..)”工具时,让我们也定义一个工具来处理相反的操作: ```js function gatherArgs(fn) { @@ -217,16 +211,16 @@ function gatherArgs(fn) { }; } -// or the ES6 => arrow form +// 或者使用ES6箭头函数 var gatherArgs = fn => (...argsArr) => fn( argsArr ); ``` -**Note:** In Ramda, this utility is referred to as `unapply(..)`, being that it's the opposite of `apply(..)`. I think the "spread"/"gather" terminology is a little more descriptive for what's going on. +**注意:**在Ramda中,这个实用程序被称为“unapply(..)”,因为它与“apply(..)”相反。我认为用“扩展”/“收集”的术语更能描述这个。 -We can use this utility to gather individual arguments into a single array, perhaps because we want to adapt a function with array parameter destructuring to another utility that passes arguments separately. We will [cover `reduce(..)` more fully in Chapter 9](ch9.md/#reduce); in short, it repeatedly calls its reducer function with two individual parameters, which we can now *gather* together: +我们可以使用这个实用程序将单个参数收集到一个数组中,这可能是因为我们希望将一个具有数组参数析构的函数调整为另一个单独传递参数的实用程序。我们将[在第9章更全面地讨论' reduce(..) '](ch9.md/#reduce);简而言之,它用两个单独的参数反复调用它的减速函数,我们现在可以将它们*集合*在一起: ```js function combineFirstTwo([ v1, v2 ]) { @@ -237,11 +231,11 @@ function combineFirstTwo([ v1, v2 ]) { // 15 ``` -## Some Now, Some Later +## 现在与稍后的调用 -If a function takes multiple arguments, you may want to specify some of those up front and leave the rest to be specified later. +如果一个函数接受多个参数,您可能希望预先指定其中一些参数,其余的参数留待以后指定。 -Consider this function: +思考下面这个函数: ```js function ajax(url,data,callback) { @@ -249,11 +243,11 @@ function ajax(url,data,callback) { } ``` -Let's imagine you'd like to set up several API calls where the URLs are known up front, but the data and the callback to handle the response won't be known until later. +假设您想设置几个API调用,其中url在前面是已知的,但是处理响应的数据和回调要到稍后才会知道。 -Of course, you can just defer making the `ajax(..)` call until all the bits are known, and refer to some global constant for the URL at that time. But another way is to create a function reference that already has the `url` argument preset. +当然,您可以推迟执行' ajax(..) '调用,直到所有的位都被知道,并在那个时候引用URL的某个全局常量。但另一种方法是创建一个函数引用,它已经预先设置了“url”参数。 -What we're going to do is make a new function that still calls `ajax(..)` under the covers, and it manually sets the first argument to the API URL you care about, while waiting to accept the other two arguments later: +我们要做的是创建一个新函数,它仍然调用' ajax(..) ',并手动将第一个参数设置API URL,同时等待稍后接受其他两个参数: ```js function getPerson(data,cb) { @@ -265,7 +259,7 @@ function getOrder(data,cb) { } ``` -Manually specifying these function call wrappers is certainly possible, but it may get quite tedious, especially if there will also be variations with different arguments preset, like: +手动指定这些函数的封装当然是可能的,但它可能会变得相当乏味,特别是如果还会有不同的参数预置变化,如: ```js function getCurrentUser(cb) { @@ -273,15 +267,15 @@ function getCurrentUser(cb) { } ``` -One practice an FPer gets very used to is looking for patterns where we do the same sorts of things repeatedly, and trying to turn those actions into generic reusable utilities. As a matter of fact, I'm sure that's already the instinct for many of you readers, so that's not uniquely an FP thing. But it's unquestionably important for FP. +函数编程人员非常习惯的一种实践是寻找重复执行相同类型的操作的模式,并尝试将这些操作转换为通用的可重用实用程序。事实上,我相信这已经是你们许多读者的本能,所以这并不是函数式编程所独有的。但毫无疑问,这对函数式编程很重要。 -To conceive such a utility for argument presetting, let's examine conceptually what's going on, not just looking at the manual implementations shown here. +要构思这样一个用于参数预置的实用程序,让我们从概念上研究一下发生了什么,而不只是查看这里显示的手动实现。 -One way to articulate what's going on is that the `getOrder(data,cb)` function is a *partial application* of the `ajax(url,data,cb)` function. This terminology comes from the notion that arguments are *applied* to parameters at the function call-site. And as you can see, we're only applying some of the arguments up front -- specifically, the argument for the `url` parameter -- while leaving the rest to be applied later. +一种明确的方法是“getOrder(data,cb)”函数是“ajax(url,data,cb)”函数的“部分应用程序”。这个术语来自于这样一个概念:参数被“应用”到函数调用站点的参数上。正如您所看到的,我们只在前面应用了一些参数——具体地说,就是“url”参数的参数——其余的参数将在稍后应用。 -To be a tiny bit more formal about this pattern, partial application is strictly a reduction in a function's arity; remember, that's the number of expected parameter inputs. We reduced the original `ajax(..)` function's arity from 3 to 2 for the `getOrder(..)` function. +更正式地说,部分应用严格地减少了函数的特性;记住,这是期望参数输入的个数。对于“getOrder(..)”函数,我们将原来的“ajax(..)”函数的特性从3减少到2。 -Let's define a `partial(..)` utility: +定义一个`partial(..)`工具函数: ```js function partial(fn,...presetArgs) { @@ -290,26 +284,26 @@ function partial(fn,...presetArgs) { }; } -// or the ES6 => arrow form +// 或使用ES6箭头函数 var partial = (fn,...presetArgs) => (...laterArgs) => fn( ...presetArgs, ...laterArgs ); ``` -**Tip:** Don't just take this snippet at face value. Pause for a few moments to digest what's going on with this utility. Make sure you really *get it*. +**提示:**不要只看这个片段的表面价值。暂停几分钟,消化一下这个实用程序到底发生了什么。确保你真的明白了。 -The `partial(..)` function takes an `fn` for which function we are partially applying. Then, any subsequent arguments passed in are gathered into the `presetArgs` array and saved for later. +' partial(..) '函数接受一个' fn '函数,我们对该函数进行了部分应用。然后,传入的任何后续参数都被收集到“presetArgs”数组中,并保存到后面。 -A new inner function (called `partiallyApplied(..)` just for clarity) is created and `return`ed; the inner function's own arguments are gathered into an array called `laterArgs`. +创建一个新的内部函数(称为' partiallyApplied(..) ',只是为了清晰起见)并返回;内部函数自身的参数被收集到一个名为“laterArgs”的数组中。 -Notice the references to `fn` and `presetArgs` inside this inner function? How does that work? After `partial(..)` finishes running, how does the inner function keep being able to access `fn` and `presetArgs`? If you answered **closure**, you're right on track! The inner function `partiallyApplied(..)` closes over both the `fn` and `presetArgs` variables so it can keep accessing them later, no matter where the function runs. This is why understanding closure is critical! +注意到内部函数中对' fn '和' presetArgs '的引用了吗?这是怎么回事?在' partial(..) '运行完成后,内部函数如何能够继续访问' fn '和' presetArgs ' ?如果您回答的是**闭包**,那么你是正确的!内部函数“partiallyApplied(..)”对“fn”和“presetArgs”变量都关闭,无论函数运行在何处,以后都可以继续访问它们,。这就是为什么理解闭包是至关重要的! -When the `partiallyApplied(..)` function is later executed somewhere else in your program, it uses the closed over `fn` to execute the original function, first providing any of the (closed over) `presetArgs` partial application arguments, then any further `laterArgs` arguments. +当“partiallyapplied(..)”函数在程序中的其他地方执行时,它使用关闭的“fn”来执行原始函数,首先提供任何“presetargs”部分应用程序参数,然后再提供其他“laterargs”参数。 -If any of that was confusing, stop and go re-read it. Trust me, you'll be glad you did as we get further into the text. +如果有什么让人困惑的话,停下来再读一遍。相信我,你会很高兴我们能更深入地了解。 -Let's now use the `partial(..)` utility to make those earlier partially applied functions: +现在,让我们使用' partial(..) '函数来实现前面那些部分应用的函数: ```js var getPerson = partial( ajax, "http://some.api/person" ); @@ -317,7 +311,7 @@ var getPerson = partial( ajax, "http://some.api/person" ); var getOrder = partial( ajax, "http://some.api/order" ); ``` -Take a moment to consider the shape/internals of `getPerson(..)`. It will look sorta like this: +花点时间考虑一下“getPerson(..)”的形状/内部结构。它看起来是这样的: ```js var getPerson = function partiallyApplied(...laterArgs) { @@ -325,28 +319,28 @@ var getPerson = function partiallyApplied(...laterArgs) { }; ``` -The same will be true of `getOrder(..)`. But what about `getCurrentUser(..)`? +getOrder(..)也是如此。但是' getCurrentUser(..) '呢? ```js -// version 1 +// 版本 1 var getCurrentUser = partial( ajax, "http://some.api/person", { user: CURRENT_USER_ID } ); -// version 2 +// 版本 2 var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); ``` -We can either define `getCurrentUser(..)` with both the `url` and `data` arguments specified directly (version 1), or define `getCurrentUser(..)` as a partial application of the `getPerson(..)` partial application, specifying only the additional `data` argument (version 2). +我们可以使用直接指定的“url”和“data”参数定义“getCurrentUser(..)”(版本1),或者将“getCurrentUser(..)”定义为“getPerson(..)”部分应用程序的部分应用程序,只指定附加的“data”参数(版本2)。 -Version 2 is a little cleaner to express because it reuses something already defined. As such, I think it fits a little closer to the spirit of FP. +版本2更便于表达,因为它重用了已经定义的内容。因此,我认为它更符合函数式编程的概念。 -Just to make sure we understand how these two versions will work under the covers, they look respectively kinda like: +为了确保我们理解这两个版本在背后是如何工作的,它们看起来分别有点像: ```js -// version 1 +// 版本 1 var getCurrentUser = function partiallyApplied(...laterArgs) { return ajax( "http://some.api/person", @@ -355,7 +349,7 @@ var getCurrentUser = function partiallyApplied(...laterArgs) { ); }; -// version 2 +// 版本 2 var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs){ var getPerson = function innerPartiallyApplied(...innerLaterArgs){ return ajax( "http://some.api/person", ...innerLaterArgs ); @@ -365,11 +359,11 @@ var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs){ } ``` -Again, stop and re-read those code snippets to make sure you understand what's going on there. +同样,停止并重新阅读这些代码片段,以确保您理解其中的内容。 -**Note:** Version 2 has an extra layer of function wrapping involved. That may smell strange and unnecessary, but this is just one of those things in FP that you'll want to get really comfortable with. We'll be wrapping many layers of functions onto each other as we progress through the text. Remember, this is *function*al programming! +**注意:** 版本 2包含一个额外的函数包装层。这可能听起来很奇怪,也没有必要,但这只是函数编程中你想要真正熟悉的东西之一。在阅读文本的过程中,我们将把许多层函数相互包装起来。记住,这是*函数*编程! -Let's take a look at another example of the usefulness of partial application. Consider an `add(..)` function which takes two arguments and adds them together: +让我们看一下部分应用程序的另一个有用的例子。考虑一个' add(..) '函数,它接受两个参数并将它们相加: ```js function add(x,y) { @@ -377,7 +371,7 @@ function add(x,y) { } ``` -Now imagine we'd like take a list of numbers and add a certain number to each of them. We'll use the `map(..)` utility (see [Chapter 9, "Map"](ch9.md/#map)) built into JS arrays: +现在假设我们想要一个数字列表,并在每个列表中添加一个特定的数字。我们将使用内置在JS数组中的 `map(..)`(see [Chapter 9, "Map"](ch9.md/#map)) : ```js [1,2,3,4,5].map( function adder(val){ @@ -386,36 +380,36 @@ Now imagine we'd like take a list of numbers and add a certain number to each of // [4,5,6,7,8] ``` -The reason we can't pass `add(..)` directly to `map(..)` is because the signature of `add(..)` doesn't match the mapping function that `map(..)` expects. That's where partial application can help us: we can adapt the signature of `add(..)` to something that will match: +我们不能直接将' add(..) '传递给' map(..) '的原因是' add(..) '的签名与' map(..) '的映射函数不匹配。这就是部分应用程序可以帮助我们的地方:我们可以修改`add(..)`的签名,使之匹配: ```js [1,2,3,4,5].map( partial( add, 3 ) ); // [4,5,6,7,8] ``` -The `partial(add,3)` call produces a new unary function which is expecting only one more argument. +`partial(add,3)`调用生成一个新的一元函数,该函数只需要一个参数。 -The `map(..)` utility will loop through the array (`[1,2,3,4,5]`) and repeatedly call this unary function, once for each of those values, respectively. So, the calls made will effectively be `add(3,1)`, `add(3,2)`, `add(3,3)`, `add(3,4)`, and `add(3,5)`. The array of those results is `[4,5,6,7,8]`. +`map(..)`将循环遍历数组(`[1,2,3,4,5]`),并分别为每个值重复调用这个一元函数一次。因此,有效地调用`add(3,1)`, `add(3,2)`, `add(3,3)`, `add(3,4)`, and `add(3,5)`,产生结果的数组是'[4,5,6,7,8]' -### `bind(..)` +### `bind(..)函数` -JavaScript functions all have a built-in utility called `bind(..)`. It has two capabilities: presetting the `this` context and partially applying arguments. +javascript函数都有一个名为`bind(..)`的内置函数。它有两个功能:预设`this`上下文和应用参数。 -I think it's incredibly misguided to conflate these two capabilities in one utility. Sometimes you'll want to hard-bind the `this` context and not partially apply arguments. Other times you'll want to partially apply arguments but not care about `this` binding at all. I have never needed both at the same time. +我认为把这两种功能混为一谈是非常错误的。有时您需要绑定`this`上下文,而不是应用参数。其他时候,您可能希望作为应用参数,根本不关心`this`绑定。我是从来没有同时需要这两者的。 -The latter scenario (partial application without setting `this` context) is awkward because you have to pass an ignorable placeholder for the `this`-binding argument (the first one), usually `null`. +后一种方案比较尴尬(不设置`this`上下文的应用函数),因为必须为 `this`绑定参数(第一个)传递一个可忽略的占位符,通常为“null”。 -Consider: +想一想: ```js var getPerson = ajax.bind( null, "http://some.api/person" ); ``` -That `null` just bugs me to no end. Despite this *this* annoyance, it's mildly convenient that JS has a built-in utility for partial application. However, most FP programmers prefer using the dedicated `partial(..)` utility in their chosen FP library. +那个`null` 让人心烦。JS为部分应用程序提供了一个内置的实用程序,这还是比较方便的。然而,大多数函数编程程序员更喜欢在他们选择的函数编程库中使用专用的`partial(..)`实用程序。 -### Reversing Arguments +### 反转参数 -Recall that the signature for our Ajax function is: `ajax( url, data, cb )`. What if we wanted to partially apply the `cb` but wait to specify `data` and `url` later? We could create a utility that wraps a function to reverse its argument order: +回想一下,我们的Ajax函数的签名是:`ajax( url, data, cb )`。如果我们想要部分应用 `cb`,但要等到稍后指定`data`和`url`,又该怎么办?我们可以创建一个实用程序来包装一个函数,以逆转其参数顺序: ```js function reverseArgs(fn) { @@ -424,14 +418,14 @@ function reverseArgs(fn) { }; } -// or the ES6 => arrow form +// 运用箭头函数 var reverseArgs = fn => (...args) => fn( ...args.reverse() ); ``` -Now we can reverse the order of the `ajax(..)` arguments, so that we can then partially apply from the right rather than the left. To restore the expected order, we'll then reverse the subsequent partially applied function: +现在我们可以颠倒`ajax(..)`参数的顺序,这样我们就可以从右边而不是左边部分地应用。为了恢复预期的顺序,我们将反转后面部分应用的函数: ```js var cache = {}; From 1dcbfc0ecb1a3bb1f5812bf7f9836d3aff451043 Mon Sep 17 00:00:00 2001 From: Siming Date: Wed, 28 Aug 2019 09:31:16 +0800 Subject: [PATCH 48/63] Update ch3.md --- manuscript/ch3.md | 155 +++++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index 202b8936..e2dbd110 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -436,11 +436,11 @@ var cacheResult = reverseArgs( } ) ); -// later: +// 改造后: cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } ); ``` -Instead of manually using `reverseArgs(..)` (twice!) for this purpose, we can define a `partialRight(..)` which partially applies the rightmost arguments. Under the covers, it can use the same double-reverse trick: +为此,我们可以定义一个`partialRight(..)`,而不是手动使用(两次!)`reverseArgs(..)` 封装后,它可以使用相同的双反转技巧: @@ -455,12 +455,11 @@ var cacheResult = partialRight( ajax, function onResult(obj){ cache[obj.id] = obj; }); -// later: +// 改造后: cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } ); ``` -Another more straightforward (and certainly more performant) implementation of `partialRight(..)` that doesn't use the double-reverse trick: - +另一个更直接(当然也更高效)的实现`partialRight(..)`,它没有使用双重反转技巧: ```js function partialRight(fn,...presetArgs) { return function partiallyApplied(...laterArgs){ @@ -468,16 +467,16 @@ function partialRight(fn,...presetArgs) { }; } -// or the ES6 => arrow form +// ES6箭头函数格式 var partialRight = (fn,...presetArgs) => (...laterArgs) => fn( ...laterArgs, ...presetArgs ); ``` -None of these implementations of `partialRight(..)` guarantee that a specific parameter will receive a specific partially applied value; it only ensures that the partially applied value(s) appear as the rightmost (aka, last) argument(s) passed to the original function. +这些`partialRight(..)`的实现都不能保证特定的参数将接收到特定的部分应用值;它只确保部分应用的值作为传递给原始函数的最右边(也就是最后一个)参数出现。 -For example: +示例: ```js function foo(x,y,z,...rest) { @@ -495,15 +494,15 @@ f( 1, 2, 3 ); // 1 2 3 ["z:last"] f( 1, 2, 3, 4 ); // 1 2 3 [4,"z:last"] ``` -The value `"z:last"` is only applied to the `z` parameter in the case where `f(..)` is called with exactly two arguments (matching `x` and `y` parameters). In all other cases, the `"z:last"` will just be the rightmost argument, however many arguments precede it. +值 `"z:last"`只适用于`z`参数,当`f(..)`被调用时正好有两个参数(匹配`x` 和`y`参数)。在所有其他情况下,`"z:last"`将是最正确的参数,不管它前面有多少个参数。 -## One at a Time +## 一次一个 -Let's examine a technique similar to partial application, where a function that expects multiple arguments is broken down into successive chained functions that each take a single argument (arity: 1) and return another function to accept the next argument. +让我们研究一种类似于部分应用程序的技术,其中期望多个参数的函数被分解为连续的链接函数,每个函数接受一个参数并返回另一个函数来接受下一个参数。 -This technique is called currying. +这种技术被称为柯里化。 -To first illustrate, let's imagine we had a curried version of `ajax(..)` already created. This is how we'd use it: +首先,让我们假设已经创建了一个课程版的“ajax(…)”。我们就是这样使用它的: ```js curriedAjax( "http://some.api/person" ) @@ -511,7 +510,7 @@ curriedAjax( "http://some.api/person" ) ( function foundUser(user){ /* .. */ } ); ``` -The three sets of `(..)`s denote three chained function calls. But perhaps splitting out each of the three calls helps see what's going on better: +`(..)`的三组表示三个链接函数调用。但也许把这三个回调分开会让你更好地了解情况: ```js var personFetcher = curriedAjax( "http://some.api/person" ); @@ -521,17 +520,17 @@ var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } ); getCurrentUser( function foundUser(user){ /* .. */ } ); ``` -Instead of taking all the arguments at once (like `ajax(..)`), or some of the arguments up front and the rest later (via `partial(..)`), this `curriedAjax(..)` function receives one argument at a time, each in a separate function call. +这个`curriedAjax(..)`函数不是一次接受所有参数(如`ajax(..)`),或者先接收一些参数,然后再接收其他参数(通过`partial(..)`),而是一次接收一个参数,每个参数都在一个单独的函数调用中。 -Currying is similar to partial application in that each successive curried call partially applies another argument to the original function, until all arguments have been passed. +柯里化类似于部分应用程序,在传递完所有参数之前,每个连续的局部套用调用都会将另一个参数部分应用于原始函数。 -The main difference is that `curriedAjax(..)` will return a function (we call it `personFetcher(..)`) that expects **only the next argument** `data`, not one that (like the earlier `getPerson(..)`) can receive all the rest of the arguments. +主要的区别是,`curriedAjax(..)`将返回一个函数(我们称之为`personFetcher(..)`),它只期望**下一个参数**`data`,而不是(像前面的`getPerson(..)`)可以接收所有其他参数的函数。 -If an original function expected five arguments, the curried form of that function would take just the first argument, and return a function to accept the second. That one would take just the second argument, and return a function to accept the third. And so on. +如果一个原始函数需要五个参数,那么该函数的柯里化形式将只接受第一个参数,并返回一个函数来接受第二个参数。它只接受第二个参数,并返回一个函数来接受第三个参数。等等。 -So currying unwinds a single higher-arity function into a series of chained unary functions. +因此,柯里化将单个高阶函数展开为一系列链式一元函数。 -How might we define a utility to do this currying? Consider: +我们如何定义一个实用程序来实现这种柯里化?考虑: @@ -551,7 +550,7 @@ function curry(fn,arity = fn.length) { })( [] ); } -// or the ES6 => arrow form +// ES6箭头函数格式 var curry = (fn,arity = fn.length,nextCurried) => (nextCurried = prevArgs => @@ -568,13 +567,13 @@ var curry = )( [] ); ``` -The approach here is to start a collection of arguments in `prevArgs` as an empty `[]` array, and add each received `nextArg` to that, calling the concatenation `args`. While `args.length` is less than `arity` (the number of declared/expected parameters of the original `fn(..)` function), make and return another `curried(..)` function to collect the next `nextArg` argument, passing the running `args` collection along as its `prevArgs`. Once we have enough `args`, execute the original `fn(..)` function with them. +这里的方法是将' prevArgs '中的参数集合作为空'[]'数组,并将每个接收到的' nextArg '添加到其中,调用连接' args '。而“参数。length '小于' arity '(原始' fn(..) '函数声明/预期参数的数量),make并返回另一个' curried(..) '函数来收集下一个' nextArg '参数,将运行中的' args '集合作为它的' prevArgs '传递。一旦我们有了足够的“args”,使用它们执行原始的“fn(..)”函数。 -By default, this implementation relies on being able to inspect the `length` property of the to-be-curried function to know how many iterations of currying we'll need before we've collected all its expected arguments. +默认情况下,此实现依赖于能够检查待处理函数的“length”属性,以了解在收集所有预期参数之前需要进行多少次柯里化。 -**Note:** If you use this implementation of `curry(..)` with a function that doesn't have an accurate `length` property, you'll need to pass the `arity` (the second parameter of `curry(..)`) to ensure `curry(..)` works correctly. `length` will be inaccurate if the function's parameter signature includes default parameter values, parameter destructuring, or is variadic with `...args` (see [Chapter 2](ch2.md)). +**注意:**如果您将' curry(..) '的这个实现用于一个没有精确的' length '属性的函数,则需要传递' arity ' (' curry(..) '的第二个参数),以确保' curry(..) '工作正常。如果函数的参数签名包含默认参数值、参数析构或变量为…args '(参见[第2章](ch2.md))。 -Here's how we would use `curry(..)` for our earlier `ajax(..)` example: +下面是我们将如何在前面的`ajax(..)` 示例中使用`curry(..)`: ```js var curriedAjax = curry( ajax ); @@ -586,18 +585,18 @@ var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } ); getCurrentUser( function foundUser(user){ /* .. */ } ); ``` -Each call partially applies one more argument to the original `ajax(..)` call, until all three have been provided and `ajax(..)` is actually invoked. +每个调用都会对原始的`ajax(..)`调用再应用一个参数,直到提供了所有三个参数并实际调用了`ajax(..)`。 -Remember our example from the discussion of partial application about adding `3` to each value in a list of numbers? As currying is similar to partial application, we could do that task with currying in almost the same way: +还记得我们在讨论部分应用程序时的例子吗?由于柯里化与局部应用类似,我们可以用几乎相同的方法来完成这个任务: ```js [1,2,3,4,5].map( curry( add )( 3 ) ); // [4,5,6,7,8] ``` -The difference between the two? `partial(add,3)` vs `curry(add)(3)`. +注意到两者之间的区别?`partial(add,3)` 与 `curry(add)(3)`。 -Why might you choose `curry(..)` over `partial(..)`? It might be helpful in the case where you know ahead of time that `add(..)` is the function to be adapted, but the value `3` isn't known yet: +为什么你会选择`curry(..)`而不是`partial(..)`?如果您事先知道`add(..)`是要修改的函数,但' 3 '的值还不知道,这可能会有帮助: ```js var adder = curry( add ); @@ -607,7 +606,7 @@ var adder = curry( add ); // [4,5,6,7,8] ``` -Let's look at another numbers example, this time adding a list of them together: +让我们看另一个数字的例子,这次把它们加在一起: ```js function sum(...nums) { @@ -620,24 +619,24 @@ function sum(...nums) { sum( 1, 2, 3, 4, 5 ); // 15 -// now with currying: +// 现在使用柯里化: // (5 to indicate how many we should wait for) var curriedSum = curry( sum, 5 ); curriedSum( 1 )( 2 )( 3 )( 4 )( 5 ); // 15 ``` -The advantage of currying here is that each call to pass in an argument produces another function that's more specialized, and we can capture and use *that* new function later in the program. Partial application specifies all the partially applied arguments up front, producing a function that's waiting for all the rest of the arguments **on the next call**. +这里的优点是,每次传入参数的调用都会产生另一个更专门化的函数,我们可以在程序的后面捕获并使用*这个*新函数。部分应用程序预先指定所有部分应用的参数,生成一个函数,该函数正在等待下一个调用**上的所有其他参数**。 -If you wanted to use partial application to specify one parameter (or several!) at a time, you'd have to keep calling `partial(..)` again on each successive partially applied function. By contrast, curried functions do this automatically, making working with individual arguments one-at-a-time more ergonomic. +如果您想使用部分应用程序一次指定一个(或多个!)参数,则必须对每个连续的部分应用的函数再次调用`partial(..)`。相比之下,柯里化的函数可以自动完成这一任务,使每次处理单个参数更加符合人体工程学。 -Both currying and partial application use closure to remember the arguments over time until all have been received, and then the original function can be invoked. +柯里化和部分应用程序都使用闭包来随着时间记住参数,直到所有参数都被接收,然后才能调用原始函数。 -### Visualizing Curried Functions +### 柯里化的可视化功能 -Let's examine more closely the `curriedSum(..)` from the previous section. Recall its usage: `curriedSum(1)(2)(3)(4)(5)`; five subsequent (chained) function calls. +让我们更仔细地研究前一节中的`curriedSum(..)`。回想一下它的用法:curriedSum(1)(2)(3)(4)(5);5个后续(链式)函数调用。 -What if we manually defined a `curriedSum(..)` instead of using `curry(..)`? How would that look? +如果我们手工定义一个`curriedSum(..)`而不是使用`curry(..)`会怎么样? ```js function curriedSum(v1) { @@ -653,11 +652,11 @@ function curriedSum(v1) { } ``` -Definitely uglier, no question. But this is an important way to visualize what's going on with a curried function. Each nested function call is returning another function that's going to accept the next argument, and that continues until we've specified all the expected arguments. +绝对更丑,毫无疑问。但这是一个很重要的方法来形象化一个柯里化函数。每个嵌套函数调用都返回另一个函数,该函数将接受下一个参数,并继续执行,直到指定了所有预期的参数。 -When trying to decipher curried functions, I've found it helps me tremendously if I can unwrap them mentally as a series of nested functions. +当我试图破译柯里化函数时,我发现如果我能在心里把它们分解成一系列嵌套函数,这对我有很大的帮助。 -In fact, to reinforce that point, let's consider the same code but written with ES6 arrow functions: +事实上,为了加强这一点,让我们考虑同样的代码,但用ES6箭头函数编写: ```js curriedSum = @@ -669,27 +668,27 @@ curriedSum = sum( v1, v2, v3, v4, v5 ); ``` -And now, all on one line: +现在,所有的都在一行: ```js curriedSum = v1 => v2 => v3 => v4 => v5 => sum( v1, v2, v3, v4, v5 ); ``` -Depending on your perspective, that form of visualizing the curried function may be more or less helpful to you. For me, it's a fair bit more obscured. +根据您的观点,这种将柯里化函数可视化的形式可能或多或少对您有帮助。对我来说,这有点模糊。 -But the reason I show it that way is that it happens to look almost identical to the mathematical notation (and Haskell syntax) for a curried function! That's one reason why those who like mathematical notation (and/or Haskell) like the ES6 arrow function form. +但我用这种方式展示它的原因是,它看起来几乎与柯里化函数的数学符号(和Haskell语法)相同!这就是那些喜欢数学符号(和/或Haskell)的人喜欢ES6箭头函数形式的原因之一。 -### Why Currying and Partial Application? +### 为什么是柯里化和局部应用? -With either style -- currying (such as `sum(1)(2)(3)`) or partial application (such as `partial(sum,1,2)(3)`) -- the call-site unquestionably looks stranger than a more common one like `sum(1,2,3)`. So **why would we ever go this direction** when adopting FP? There are multiple layers to answering that question. +使用任意一种样式——柯里化(如`sum(1)(2)(3)`)或部分应用程序(如`partial(sum,1,2)(3)`)——调用站点无疑比更常见的`sum(1,2,3)`看起来更奇怪。那么,在采用FP时,我们为什么要走这个方向呢?回答这个问题有很多层次。 -The first and most obvious reason is that both currying and partial application allow you to separate in time/space (throughout your codebase) when and where separate arguments are specified, whereas traditional function calls require all the arguments to be present at the same time. If you have a place in your code where you'll know some of the arguments and another place where the other arguments are determined, currying or partial application are very useful. +第一个也是最明显的原因是,局部套用和局部应用程序都允许在指定单独参数的时间/空间(在整个代码库中)中分离,而传统的函数调用要求所有参数同时出现。如果您在代码中有一个位置可以知道一些参数,而在另一个位置可以确定其他参数,那么局部套用或局部应用程序非常有用。 -Another layer to this answer, specifically for currying, is that composition of functions is much easier when there's only one argument. So a function that ultimately needs three arguments, if curried, becomes a function that needs just one, three times over. That kind of unary function will be a lot easier to work with when we start composing them. We'll tackle this topic later in [Chapter 4](ch4.md). +这个答案的另一个层次,特别是对于局部套用,是当只有一个参数时,函数的组合要容易得多。所以一个最终需要三个参数的函数,如果柯里化,变成一个只需要一个,三次的函数。当我们开始组合它们时,这种一元函数会更容易处理。我们将在稍后的[第4章](ch4.md)中处理这个主题。 -But the most important layer is specialization of generalized functions, and how such abstraction improves readability of code. +但是最重要的层是通用函数的专门化,以及这种抽象如何提高代码的可读性。 -Consider our running `ajax(..)` example: +考虑我们运行的 `ajax(..)` 示例: ```js ajax( @@ -699,9 +698,9 @@ ajax( ); ``` -The call-site includes all the information necessary to pass to the most generalized version of the utility (`ajax(..)`). The potential readability downside is that it may be the case that the URL and the data are not relevant information at this point in the program, but yet that information is cluttering up the call-site nonetheless. +调用站点包含传递到实用程序的最通用版本(`ajax(..)`)所需的所有信息。潜在的易读性缺点是,URL和数据在程序中的这一点上可能不是相关的信息,但是尽管如此,这些信息仍然使调用站点混乱不堪。 -Now consider: +现在想一想: ```js var getCurrentUser = partial( @@ -710,38 +709,38 @@ var getCurrentUser = partial( { user: CURRENT_USER_ID } ); -// later +// 改造后 getCurrentUser( function foundUser(user){ /* .. */ } ); ``` -In this version, we define a `getCurrentUser(..)` function ahead of time that already has known information like URL and data preset. The call-site for `getCurrentUser(..)` then isn't cluttered by information that **at that point of the code** isn't relevant. +在这个版本中,我们预先定义了一个`getCurrentUser(..)`函数,该函数已经具有URL和数据预置等已知信息。这样,`getCurrentUser(..)`的调用就不会被代码中**不相关的信息所打乱。 -Moreover, the semantic name for the function `getCurrentUser(..)` more accurately depicts what is happening than just `ajax(..)` with a URL and data would. +此外,函数`getCurrentUser(..)`的语义名称比仅使用URL和数据的`ajax(..)`更准确地描述了正在发生的事情。 -That's what abstraction is all about: separating two sets of details -- in this case, the *how* of getting a current user and the *what* we do with that user -- and inserting a semantic boundary between them, which eases the reasoning of each part independently. +这就是抽象的全部含义:分离两组细节——在本例中,是获取当前用户的*方法*和使用该用户的*方法*——并在它们之间插入语义边界,这将简化每个部分的独立推理。 -Whether you use currying or partial application, creating specialized functions from generalized ones is a powerful technique for semantic abstraction and improved readability. +无论您使用柯里化还是局部应用程序,从通用函数创建专用函数都是语义抽象和提高可读性的强大技术。 -### Currying More Than One Argument? +### 柯里化多个参数? -The definition and implementation I've given of currying thus far is, I believe, as true to the spirit as we can likely get in JavaScript. +到目前为止,我所给出的关于柯里化的定义和实现,我相信是最符合JavaScript精神的。 -Specifically, if we look briefly at how currying works in Haskell, we can observe that multiple arguments always go in to a function one at a time, one per curried call -- other than tuples (analogous to arrays for our purposes) that transport multiple values in a single argument. +具体地说,如果我们简单地看一下在Haskell中如何使用局部套用,我们可以观察到,多个参数总是一个一个地进入一个函数,每个局部套用调用一个参数——而不是在一个参数中传输多个值的元组(类似于我们的目的中的数组)。 -For example, in Haskell: +例如,在Haskell中: ```haskell foo 1 2 3 ``` -This calls the `foo` function, and has the result of passing in three values `1`, `2`, and `3`. But functions are automatically curried in Haskell, which means each value goes in as a separate curried-call. The JS equivalent of that would look like `foo(1)(2)(3)`, which is the same style as the `curry(..)` I presented earlier. +它调用`foo`函数,并传递三个值' 1 '、' 2 '和' 3 '。但是函数在Haskell中是自动柯里化的,这意味着每个值都作为一个单独的调用进入。对应的JS应该类似于`foo(1)(2)(3)`,这与我前面介绍的`curry(..)`风格相同。 -**Note:** In Haskell, `foo (1,2,3)` is not passing in those three values at once as three separate arguments, but a tuple (kinda like a JS array) as a single argument. To work, `foo` would need to be altered to handle a tuple in that argument position. As far as I can tell, there's no way in Haskell to pass all three arguments separately with just one function call; each argument gets its own curried-call. Of course, the presence of multiple calls is transparent to the Haskell developer, but it's a lot more syntactically obvious to the JS developer. +**注意:**在Haskell中,`foo (1,2,3)`不是同时作为三个单独的参数传递这三个值,而是作为一个单独的参数传递一个元组(有点像JS数组)。为了工作,需要修改' foo '来处理那个参数位置的元组。据我所知,在Haskell中不可能仅通过一个函数调用就分别传递所有三个参数;每个参数都有自己的柯里化调用。当然,对于Haskell开发人员来说,多个调用的存在是透明的,但是对于JS开发人员来说,它在语法上要明显得多。 -For these reasons, I think the `curry(..)` that I demonstrated earlier is a faithful adaptation, or what I might call "strict currying". However, it's important to note that there's a looser definition used in most popular JavaScript FP libraries. +基于这些原因,我认为我之前演示的`curry(..)`是一种忠实的改编,或者我可以称之为“严格的柯里化”。但是,需要注意的是,在大多数流行的JavaScript FP库中使用了一个更宽松的定义。 -Specifically, JS currying utilities typically allow you to specify multiple arguments for each curried-call. Revisiting our `sum(..)` example from before, this would look like: +具体来说,JS 柯里化实用程序通常允许为每个调用指定多个参数。重新查看前面的“sum(..)”示例,如下所示: ```js var curriedSum = looseCurry( sum, 5 ); @@ -749,9 +748,9 @@ var curriedSum = looseCurry( sum, 5 ); curriedSum( 1 )( 2, 3 )( 4, 5 ); // 15 ``` -We see a slight syntax savings of fewer `( )`, and an implied performance benefit of now having three function calls instead of five. But other than that, using `looseCurry(..)` is identical in end result to the narrower `curry(..)` definition from earlier. I would guess the convenience/performance factor is probably why frameworks allow multiple arguments. This seems mostly like a matter of taste. +我们看到语法上稍微节省了一些'()',并且现在有三个函数调用而不是五个函数调用带来了性能上的好处。但除此之外,使用`looseCurry(..)`与前面定义的`curry(..)`在最终结果上是相同的。我想,方便/性能因素可能是框架允许多个参数的原因。这似乎主要是品味的问题。 -We can adapt our previous currying implementation to this common looser definition: +我们可以调整我们之前的柯里化实现来适应这个更宽松的定义: @@ -772,13 +771,13 @@ function looseCurry(fn,arity = fn.length) { } ``` -Now each curried-call accepts one or more arguments (as `nextArgs`). We'll leave it as an exercise for the interested reader to define the ES6 `=>` version of `looseCurry(..)` similar to how we did it for `curry(..)` earlier. +现在,每个柯里化调用都接受一个或多个参数(作为`nextArgs`)。我们将把它作为一个练习留给感兴趣的读者来定义ES6 `=>`版本的`looseCurry(..)`类似于我们之前为`curry(..)`所做的那样。 -### No Curry for Me, Please +### 请不要对我用柯里化 -It may also be the case that you have a curried function that you'd like to essentially un-curry -- basically, to turn a function like `f(1)(2)(3)` back into a function like `g(1,2,3)`. +也有可能你有一个柯里化函数你想要取消柯里化——基本上,把一个像f(1)(2)(3)这样的函数变成像g(1,2,3)这样的函数。 -The standard utility for this is (un)shockingly typically called `uncurry(..)`. Here's a simple naive implementation: +这方面的标准实用程序通常被称为`uncurry(..)`。这里有一个简单的天真的实现: ```js function uncurry(fn) { @@ -807,7 +806,7 @@ var uncurry = }; ``` -**Warning:** Don't just assume that `uncurry(curry(f))` has the same behavior as `f`. In some libraries the uncurrying would result in a function like the original, but not all of them; certainly our example here does not. The uncurried function acts (mostly) the same as the original function if you pass as many arguments to it as the original function expected. However, if you pass fewer arguments, you still get back a partially curried function waiting for more arguments; this quirk is illustrated in the following snippet: +**警告:**不要想当然地认为`uncurry(curry(f))`与`f`具有相同的行为。在一些库中,解列会得到与原始函数类似的函数,但不是所有函数;当然我们这里的例子没有。如果传递的参数与原始函数期望的一样多,那么反柯里化函数的作用(大多数情况下)与原始函数相同。然而,如果传递更少的参数,仍然会返回一个部分柯里化的函数,等待更多的参数;下面的代码片段说明了这种怪癖: ```js function sum(...nums) { @@ -827,19 +826,19 @@ uncurriedSum( 1, 2, 3, 4, 5 ); // 15 uncurriedSum( 1, 2, 3 )( 4 )( 5 ); // 15 ``` -Probably the more common case of using `uncurry(..)` is not with a manually curried function as just shown, but with a function that comes out curried as a result of some other set of operations. We'll illustrate that scenario later in this chapter in the ["No Points" discussion](#no-points). +可能使用`uncurry(..)`更常见的情况并不是像刚才显示的那样使用手动柯里化函数,而是使用由其他一些操作集生成的柯里化函数。我们将在本章后面的[“No Points”讨论](#no-points)中演示该场景。 -## Order Matters +## 顺序的重要性 -In Chapter 2, we explored the [named arguments pattern](ch2.md/#named-arguments). One primary advantage of named arguments is not needing to juggle argument ordering, thereby improving readability. +在第2章中,我们探讨了[命名参数模式](ch2.md/#named-arguments)。命名参数的一个主要优势是不需要改变参数的顺序,从而提高可读性。 -We've looked at the advantages of using currying/partial application to provide individual arguments to a function separately. But the downside is that these techniques are traditionally based on positional arguments; argument ordering is thus an inevitable headache. +我们已经看到了使用柯里化/局部应用程序分别为函数提供单独参数的优点。但缺点是这些技术传统上是基于位置参数的;因此,论点排序是一个不可避免的头痛问题。 -Utilities like `reverseArgs(..)` (and others) are necessary to juggle arguments to get them into the right order. Sometimes we get lucky and define a function with parameters in the order that we later want to curry them, but other times that order is incompatible and we have to jump through hoops to reorder. +像`reverseArgs(..)`(和其他)这样的实用程序是调整参数以使它们处于正确顺序所必需的。有时我们很幸运地定义了一个带有参数的函数,其顺序我们稍后将对其进行修改,但有时这个顺序是不兼容的,我们必须跳过一些困难才能重新排序。 -The frustration is not merely that we need to use some utility to juggle the properties, but the fact that the usage of the utility clutters up our code a bit with extra noise. These kinds of things are like little paper cuts; one here or there isn't a showstopper, but the pain can certainly add up. +令人沮丧的是,我们不仅需要使用一些实用程序来处理属性,而且使用该实用程序会给代码带来额外的干扰,使代码变得有些混乱。这些东西就像小剪纸;这里有一个,那里没有一个,但痛苦肯定会累积起来。 -Can we improve currying/partial application to free it from these ordering concerns? Let's apply the tricks from named arguments style and invent some helper utilities for this adaptation: +我们能否改进柯里化/部分应用程序,使其免于这些排序问题?让我们应用命名参数风格的技巧,并为这种适应发明一些辅助实用程序: ```js function partialProps(fn,presetArgsObj) { From 66ec478c90441212c2dcbc138d42a2f7aa4421f0 Mon Sep 17 00:00:00 2001 From: Siming Date: Wed, 28 Aug 2019 09:31:55 +0800 Subject: [PATCH 49/63] Update ch3.md --- manuscript/ch3.md | 155 +++++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index 202b8936..e2dbd110 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -436,11 +436,11 @@ var cacheResult = reverseArgs( } ) ); -// later: +// 改造后: cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } ); ``` -Instead of manually using `reverseArgs(..)` (twice!) for this purpose, we can define a `partialRight(..)` which partially applies the rightmost arguments. Under the covers, it can use the same double-reverse trick: +为此,我们可以定义一个`partialRight(..)`,而不是手动使用(两次!)`reverseArgs(..)` 封装后,它可以使用相同的双反转技巧: @@ -455,12 +455,11 @@ var cacheResult = partialRight( ajax, function onResult(obj){ cache[obj.id] = obj; }); -// later: +// 改造后: cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } ); ``` -Another more straightforward (and certainly more performant) implementation of `partialRight(..)` that doesn't use the double-reverse trick: - +另一个更直接(当然也更高效)的实现`partialRight(..)`,它没有使用双重反转技巧: ```js function partialRight(fn,...presetArgs) { return function partiallyApplied(...laterArgs){ @@ -468,16 +467,16 @@ function partialRight(fn,...presetArgs) { }; } -// or the ES6 => arrow form +// ES6箭头函数格式 var partialRight = (fn,...presetArgs) => (...laterArgs) => fn( ...laterArgs, ...presetArgs ); ``` -None of these implementations of `partialRight(..)` guarantee that a specific parameter will receive a specific partially applied value; it only ensures that the partially applied value(s) appear as the rightmost (aka, last) argument(s) passed to the original function. +这些`partialRight(..)`的实现都不能保证特定的参数将接收到特定的部分应用值;它只确保部分应用的值作为传递给原始函数的最右边(也就是最后一个)参数出现。 -For example: +示例: ```js function foo(x,y,z,...rest) { @@ -495,15 +494,15 @@ f( 1, 2, 3 ); // 1 2 3 ["z:last"] f( 1, 2, 3, 4 ); // 1 2 3 [4,"z:last"] ``` -The value `"z:last"` is only applied to the `z` parameter in the case where `f(..)` is called with exactly two arguments (matching `x` and `y` parameters). In all other cases, the `"z:last"` will just be the rightmost argument, however many arguments precede it. +值 `"z:last"`只适用于`z`参数,当`f(..)`被调用时正好有两个参数(匹配`x` 和`y`参数)。在所有其他情况下,`"z:last"`将是最正确的参数,不管它前面有多少个参数。 -## One at a Time +## 一次一个 -Let's examine a technique similar to partial application, where a function that expects multiple arguments is broken down into successive chained functions that each take a single argument (arity: 1) and return another function to accept the next argument. +让我们研究一种类似于部分应用程序的技术,其中期望多个参数的函数被分解为连续的链接函数,每个函数接受一个参数并返回另一个函数来接受下一个参数。 -This technique is called currying. +这种技术被称为柯里化。 -To first illustrate, let's imagine we had a curried version of `ajax(..)` already created. This is how we'd use it: +首先,让我们假设已经创建了一个课程版的“ajax(…)”。我们就是这样使用它的: ```js curriedAjax( "http://some.api/person" ) @@ -511,7 +510,7 @@ curriedAjax( "http://some.api/person" ) ( function foundUser(user){ /* .. */ } ); ``` -The three sets of `(..)`s denote three chained function calls. But perhaps splitting out each of the three calls helps see what's going on better: +`(..)`的三组表示三个链接函数调用。但也许把这三个回调分开会让你更好地了解情况: ```js var personFetcher = curriedAjax( "http://some.api/person" ); @@ -521,17 +520,17 @@ var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } ); getCurrentUser( function foundUser(user){ /* .. */ } ); ``` -Instead of taking all the arguments at once (like `ajax(..)`), or some of the arguments up front and the rest later (via `partial(..)`), this `curriedAjax(..)` function receives one argument at a time, each in a separate function call. +这个`curriedAjax(..)`函数不是一次接受所有参数(如`ajax(..)`),或者先接收一些参数,然后再接收其他参数(通过`partial(..)`),而是一次接收一个参数,每个参数都在一个单独的函数调用中。 -Currying is similar to partial application in that each successive curried call partially applies another argument to the original function, until all arguments have been passed. +柯里化类似于部分应用程序,在传递完所有参数之前,每个连续的局部套用调用都会将另一个参数部分应用于原始函数。 -The main difference is that `curriedAjax(..)` will return a function (we call it `personFetcher(..)`) that expects **only the next argument** `data`, not one that (like the earlier `getPerson(..)`) can receive all the rest of the arguments. +主要的区别是,`curriedAjax(..)`将返回一个函数(我们称之为`personFetcher(..)`),它只期望**下一个参数**`data`,而不是(像前面的`getPerson(..)`)可以接收所有其他参数的函数。 -If an original function expected five arguments, the curried form of that function would take just the first argument, and return a function to accept the second. That one would take just the second argument, and return a function to accept the third. And so on. +如果一个原始函数需要五个参数,那么该函数的柯里化形式将只接受第一个参数,并返回一个函数来接受第二个参数。它只接受第二个参数,并返回一个函数来接受第三个参数。等等。 -So currying unwinds a single higher-arity function into a series of chained unary functions. +因此,柯里化将单个高阶函数展开为一系列链式一元函数。 -How might we define a utility to do this currying? Consider: +我们如何定义一个实用程序来实现这种柯里化?考虑: @@ -551,7 +550,7 @@ function curry(fn,arity = fn.length) { })( [] ); } -// or the ES6 => arrow form +// ES6箭头函数格式 var curry = (fn,arity = fn.length,nextCurried) => (nextCurried = prevArgs => @@ -568,13 +567,13 @@ var curry = )( [] ); ``` -The approach here is to start a collection of arguments in `prevArgs` as an empty `[]` array, and add each received `nextArg` to that, calling the concatenation `args`. While `args.length` is less than `arity` (the number of declared/expected parameters of the original `fn(..)` function), make and return another `curried(..)` function to collect the next `nextArg` argument, passing the running `args` collection along as its `prevArgs`. Once we have enough `args`, execute the original `fn(..)` function with them. +这里的方法是将' prevArgs '中的参数集合作为空'[]'数组,并将每个接收到的' nextArg '添加到其中,调用连接' args '。而“参数。length '小于' arity '(原始' fn(..) '函数声明/预期参数的数量),make并返回另一个' curried(..) '函数来收集下一个' nextArg '参数,将运行中的' args '集合作为它的' prevArgs '传递。一旦我们有了足够的“args”,使用它们执行原始的“fn(..)”函数。 -By default, this implementation relies on being able to inspect the `length` property of the to-be-curried function to know how many iterations of currying we'll need before we've collected all its expected arguments. +默认情况下,此实现依赖于能够检查待处理函数的“length”属性,以了解在收集所有预期参数之前需要进行多少次柯里化。 -**Note:** If you use this implementation of `curry(..)` with a function that doesn't have an accurate `length` property, you'll need to pass the `arity` (the second parameter of `curry(..)`) to ensure `curry(..)` works correctly. `length` will be inaccurate if the function's parameter signature includes default parameter values, parameter destructuring, or is variadic with `...args` (see [Chapter 2](ch2.md)). +**注意:**如果您将' curry(..) '的这个实现用于一个没有精确的' length '属性的函数,则需要传递' arity ' (' curry(..) '的第二个参数),以确保' curry(..) '工作正常。如果函数的参数签名包含默认参数值、参数析构或变量为…args '(参见[第2章](ch2.md))。 -Here's how we would use `curry(..)` for our earlier `ajax(..)` example: +下面是我们将如何在前面的`ajax(..)` 示例中使用`curry(..)`: ```js var curriedAjax = curry( ajax ); @@ -586,18 +585,18 @@ var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } ); getCurrentUser( function foundUser(user){ /* .. */ } ); ``` -Each call partially applies one more argument to the original `ajax(..)` call, until all three have been provided and `ajax(..)` is actually invoked. +每个调用都会对原始的`ajax(..)`调用再应用一个参数,直到提供了所有三个参数并实际调用了`ajax(..)`。 -Remember our example from the discussion of partial application about adding `3` to each value in a list of numbers? As currying is similar to partial application, we could do that task with currying in almost the same way: +还记得我们在讨论部分应用程序时的例子吗?由于柯里化与局部应用类似,我们可以用几乎相同的方法来完成这个任务: ```js [1,2,3,4,5].map( curry( add )( 3 ) ); // [4,5,6,7,8] ``` -The difference between the two? `partial(add,3)` vs `curry(add)(3)`. +注意到两者之间的区别?`partial(add,3)` 与 `curry(add)(3)`。 -Why might you choose `curry(..)` over `partial(..)`? It might be helpful in the case where you know ahead of time that `add(..)` is the function to be adapted, but the value `3` isn't known yet: +为什么你会选择`curry(..)`而不是`partial(..)`?如果您事先知道`add(..)`是要修改的函数,但' 3 '的值还不知道,这可能会有帮助: ```js var adder = curry( add ); @@ -607,7 +606,7 @@ var adder = curry( add ); // [4,5,6,7,8] ``` -Let's look at another numbers example, this time adding a list of them together: +让我们看另一个数字的例子,这次把它们加在一起: ```js function sum(...nums) { @@ -620,24 +619,24 @@ function sum(...nums) { sum( 1, 2, 3, 4, 5 ); // 15 -// now with currying: +// 现在使用柯里化: // (5 to indicate how many we should wait for) var curriedSum = curry( sum, 5 ); curriedSum( 1 )( 2 )( 3 )( 4 )( 5 ); // 15 ``` -The advantage of currying here is that each call to pass in an argument produces another function that's more specialized, and we can capture and use *that* new function later in the program. Partial application specifies all the partially applied arguments up front, producing a function that's waiting for all the rest of the arguments **on the next call**. +这里的优点是,每次传入参数的调用都会产生另一个更专门化的函数,我们可以在程序的后面捕获并使用*这个*新函数。部分应用程序预先指定所有部分应用的参数,生成一个函数,该函数正在等待下一个调用**上的所有其他参数**。 -If you wanted to use partial application to specify one parameter (or several!) at a time, you'd have to keep calling `partial(..)` again on each successive partially applied function. By contrast, curried functions do this automatically, making working with individual arguments one-at-a-time more ergonomic. +如果您想使用部分应用程序一次指定一个(或多个!)参数,则必须对每个连续的部分应用的函数再次调用`partial(..)`。相比之下,柯里化的函数可以自动完成这一任务,使每次处理单个参数更加符合人体工程学。 -Both currying and partial application use closure to remember the arguments over time until all have been received, and then the original function can be invoked. +柯里化和部分应用程序都使用闭包来随着时间记住参数,直到所有参数都被接收,然后才能调用原始函数。 -### Visualizing Curried Functions +### 柯里化的可视化功能 -Let's examine more closely the `curriedSum(..)` from the previous section. Recall its usage: `curriedSum(1)(2)(3)(4)(5)`; five subsequent (chained) function calls. +让我们更仔细地研究前一节中的`curriedSum(..)`。回想一下它的用法:curriedSum(1)(2)(3)(4)(5);5个后续(链式)函数调用。 -What if we manually defined a `curriedSum(..)` instead of using `curry(..)`? How would that look? +如果我们手工定义一个`curriedSum(..)`而不是使用`curry(..)`会怎么样? ```js function curriedSum(v1) { @@ -653,11 +652,11 @@ function curriedSum(v1) { } ``` -Definitely uglier, no question. But this is an important way to visualize what's going on with a curried function. Each nested function call is returning another function that's going to accept the next argument, and that continues until we've specified all the expected arguments. +绝对更丑,毫无疑问。但这是一个很重要的方法来形象化一个柯里化函数。每个嵌套函数调用都返回另一个函数,该函数将接受下一个参数,并继续执行,直到指定了所有预期的参数。 -When trying to decipher curried functions, I've found it helps me tremendously if I can unwrap them mentally as a series of nested functions. +当我试图破译柯里化函数时,我发现如果我能在心里把它们分解成一系列嵌套函数,这对我有很大的帮助。 -In fact, to reinforce that point, let's consider the same code but written with ES6 arrow functions: +事实上,为了加强这一点,让我们考虑同样的代码,但用ES6箭头函数编写: ```js curriedSum = @@ -669,27 +668,27 @@ curriedSum = sum( v1, v2, v3, v4, v5 ); ``` -And now, all on one line: +现在,所有的都在一行: ```js curriedSum = v1 => v2 => v3 => v4 => v5 => sum( v1, v2, v3, v4, v5 ); ``` -Depending on your perspective, that form of visualizing the curried function may be more or less helpful to you. For me, it's a fair bit more obscured. +根据您的观点,这种将柯里化函数可视化的形式可能或多或少对您有帮助。对我来说,这有点模糊。 -But the reason I show it that way is that it happens to look almost identical to the mathematical notation (and Haskell syntax) for a curried function! That's one reason why those who like mathematical notation (and/or Haskell) like the ES6 arrow function form. +但我用这种方式展示它的原因是,它看起来几乎与柯里化函数的数学符号(和Haskell语法)相同!这就是那些喜欢数学符号(和/或Haskell)的人喜欢ES6箭头函数形式的原因之一。 -### Why Currying and Partial Application? +### 为什么是柯里化和局部应用? -With either style -- currying (such as `sum(1)(2)(3)`) or partial application (such as `partial(sum,1,2)(3)`) -- the call-site unquestionably looks stranger than a more common one like `sum(1,2,3)`. So **why would we ever go this direction** when adopting FP? There are multiple layers to answering that question. +使用任意一种样式——柯里化(如`sum(1)(2)(3)`)或部分应用程序(如`partial(sum,1,2)(3)`)——调用站点无疑比更常见的`sum(1,2,3)`看起来更奇怪。那么,在采用FP时,我们为什么要走这个方向呢?回答这个问题有很多层次。 -The first and most obvious reason is that both currying and partial application allow you to separate in time/space (throughout your codebase) when and where separate arguments are specified, whereas traditional function calls require all the arguments to be present at the same time. If you have a place in your code where you'll know some of the arguments and another place where the other arguments are determined, currying or partial application are very useful. +第一个也是最明显的原因是,局部套用和局部应用程序都允许在指定单独参数的时间/空间(在整个代码库中)中分离,而传统的函数调用要求所有参数同时出现。如果您在代码中有一个位置可以知道一些参数,而在另一个位置可以确定其他参数,那么局部套用或局部应用程序非常有用。 -Another layer to this answer, specifically for currying, is that composition of functions is much easier when there's only one argument. So a function that ultimately needs three arguments, if curried, becomes a function that needs just one, three times over. That kind of unary function will be a lot easier to work with when we start composing them. We'll tackle this topic later in [Chapter 4](ch4.md). +这个答案的另一个层次,特别是对于局部套用,是当只有一个参数时,函数的组合要容易得多。所以一个最终需要三个参数的函数,如果柯里化,变成一个只需要一个,三次的函数。当我们开始组合它们时,这种一元函数会更容易处理。我们将在稍后的[第4章](ch4.md)中处理这个主题。 -But the most important layer is specialization of generalized functions, and how such abstraction improves readability of code. +但是最重要的层是通用函数的专门化,以及这种抽象如何提高代码的可读性。 -Consider our running `ajax(..)` example: +考虑我们运行的 `ajax(..)` 示例: ```js ajax( @@ -699,9 +698,9 @@ ajax( ); ``` -The call-site includes all the information necessary to pass to the most generalized version of the utility (`ajax(..)`). The potential readability downside is that it may be the case that the URL and the data are not relevant information at this point in the program, but yet that information is cluttering up the call-site nonetheless. +调用站点包含传递到实用程序的最通用版本(`ajax(..)`)所需的所有信息。潜在的易读性缺点是,URL和数据在程序中的这一点上可能不是相关的信息,但是尽管如此,这些信息仍然使调用站点混乱不堪。 -Now consider: +现在想一想: ```js var getCurrentUser = partial( @@ -710,38 +709,38 @@ var getCurrentUser = partial( { user: CURRENT_USER_ID } ); -// later +// 改造后 getCurrentUser( function foundUser(user){ /* .. */ } ); ``` -In this version, we define a `getCurrentUser(..)` function ahead of time that already has known information like URL and data preset. The call-site for `getCurrentUser(..)` then isn't cluttered by information that **at that point of the code** isn't relevant. +在这个版本中,我们预先定义了一个`getCurrentUser(..)`函数,该函数已经具有URL和数据预置等已知信息。这样,`getCurrentUser(..)`的调用就不会被代码中**不相关的信息所打乱。 -Moreover, the semantic name for the function `getCurrentUser(..)` more accurately depicts what is happening than just `ajax(..)` with a URL and data would. +此外,函数`getCurrentUser(..)`的语义名称比仅使用URL和数据的`ajax(..)`更准确地描述了正在发生的事情。 -That's what abstraction is all about: separating two sets of details -- in this case, the *how* of getting a current user and the *what* we do with that user -- and inserting a semantic boundary between them, which eases the reasoning of each part independently. +这就是抽象的全部含义:分离两组细节——在本例中,是获取当前用户的*方法*和使用该用户的*方法*——并在它们之间插入语义边界,这将简化每个部分的独立推理。 -Whether you use currying or partial application, creating specialized functions from generalized ones is a powerful technique for semantic abstraction and improved readability. +无论您使用柯里化还是局部应用程序,从通用函数创建专用函数都是语义抽象和提高可读性的强大技术。 -### Currying More Than One Argument? +### 柯里化多个参数? -The definition and implementation I've given of currying thus far is, I believe, as true to the spirit as we can likely get in JavaScript. +到目前为止,我所给出的关于柯里化的定义和实现,我相信是最符合JavaScript精神的。 -Specifically, if we look briefly at how currying works in Haskell, we can observe that multiple arguments always go in to a function one at a time, one per curried call -- other than tuples (analogous to arrays for our purposes) that transport multiple values in a single argument. +具体地说,如果我们简单地看一下在Haskell中如何使用局部套用,我们可以观察到,多个参数总是一个一个地进入一个函数,每个局部套用调用一个参数——而不是在一个参数中传输多个值的元组(类似于我们的目的中的数组)。 -For example, in Haskell: +例如,在Haskell中: ```haskell foo 1 2 3 ``` -This calls the `foo` function, and has the result of passing in three values `1`, `2`, and `3`. But functions are automatically curried in Haskell, which means each value goes in as a separate curried-call. The JS equivalent of that would look like `foo(1)(2)(3)`, which is the same style as the `curry(..)` I presented earlier. +它调用`foo`函数,并传递三个值' 1 '、' 2 '和' 3 '。但是函数在Haskell中是自动柯里化的,这意味着每个值都作为一个单独的调用进入。对应的JS应该类似于`foo(1)(2)(3)`,这与我前面介绍的`curry(..)`风格相同。 -**Note:** In Haskell, `foo (1,2,3)` is not passing in those three values at once as three separate arguments, but a tuple (kinda like a JS array) as a single argument. To work, `foo` would need to be altered to handle a tuple in that argument position. As far as I can tell, there's no way in Haskell to pass all three arguments separately with just one function call; each argument gets its own curried-call. Of course, the presence of multiple calls is transparent to the Haskell developer, but it's a lot more syntactically obvious to the JS developer. +**注意:**在Haskell中,`foo (1,2,3)`不是同时作为三个单独的参数传递这三个值,而是作为一个单独的参数传递一个元组(有点像JS数组)。为了工作,需要修改' foo '来处理那个参数位置的元组。据我所知,在Haskell中不可能仅通过一个函数调用就分别传递所有三个参数;每个参数都有自己的柯里化调用。当然,对于Haskell开发人员来说,多个调用的存在是透明的,但是对于JS开发人员来说,它在语法上要明显得多。 -For these reasons, I think the `curry(..)` that I demonstrated earlier is a faithful adaptation, or what I might call "strict currying". However, it's important to note that there's a looser definition used in most popular JavaScript FP libraries. +基于这些原因,我认为我之前演示的`curry(..)`是一种忠实的改编,或者我可以称之为“严格的柯里化”。但是,需要注意的是,在大多数流行的JavaScript FP库中使用了一个更宽松的定义。 -Specifically, JS currying utilities typically allow you to specify multiple arguments for each curried-call. Revisiting our `sum(..)` example from before, this would look like: +具体来说,JS 柯里化实用程序通常允许为每个调用指定多个参数。重新查看前面的“sum(..)”示例,如下所示: ```js var curriedSum = looseCurry( sum, 5 ); @@ -749,9 +748,9 @@ var curriedSum = looseCurry( sum, 5 ); curriedSum( 1 )( 2, 3 )( 4, 5 ); // 15 ``` -We see a slight syntax savings of fewer `( )`, and an implied performance benefit of now having three function calls instead of five. But other than that, using `looseCurry(..)` is identical in end result to the narrower `curry(..)` definition from earlier. I would guess the convenience/performance factor is probably why frameworks allow multiple arguments. This seems mostly like a matter of taste. +我们看到语法上稍微节省了一些'()',并且现在有三个函数调用而不是五个函数调用带来了性能上的好处。但除此之外,使用`looseCurry(..)`与前面定义的`curry(..)`在最终结果上是相同的。我想,方便/性能因素可能是框架允许多个参数的原因。这似乎主要是品味的问题。 -We can adapt our previous currying implementation to this common looser definition: +我们可以调整我们之前的柯里化实现来适应这个更宽松的定义: @@ -772,13 +771,13 @@ function looseCurry(fn,arity = fn.length) { } ``` -Now each curried-call accepts one or more arguments (as `nextArgs`). We'll leave it as an exercise for the interested reader to define the ES6 `=>` version of `looseCurry(..)` similar to how we did it for `curry(..)` earlier. +现在,每个柯里化调用都接受一个或多个参数(作为`nextArgs`)。我们将把它作为一个练习留给感兴趣的读者来定义ES6 `=>`版本的`looseCurry(..)`类似于我们之前为`curry(..)`所做的那样。 -### No Curry for Me, Please +### 请不要对我用柯里化 -It may also be the case that you have a curried function that you'd like to essentially un-curry -- basically, to turn a function like `f(1)(2)(3)` back into a function like `g(1,2,3)`. +也有可能你有一个柯里化函数你想要取消柯里化——基本上,把一个像f(1)(2)(3)这样的函数变成像g(1,2,3)这样的函数。 -The standard utility for this is (un)shockingly typically called `uncurry(..)`. Here's a simple naive implementation: +这方面的标准实用程序通常被称为`uncurry(..)`。这里有一个简单的天真的实现: ```js function uncurry(fn) { @@ -807,7 +806,7 @@ var uncurry = }; ``` -**Warning:** Don't just assume that `uncurry(curry(f))` has the same behavior as `f`. In some libraries the uncurrying would result in a function like the original, but not all of them; certainly our example here does not. The uncurried function acts (mostly) the same as the original function if you pass as many arguments to it as the original function expected. However, if you pass fewer arguments, you still get back a partially curried function waiting for more arguments; this quirk is illustrated in the following snippet: +**警告:**不要想当然地认为`uncurry(curry(f))`与`f`具有相同的行为。在一些库中,解列会得到与原始函数类似的函数,但不是所有函数;当然我们这里的例子没有。如果传递的参数与原始函数期望的一样多,那么反柯里化函数的作用(大多数情况下)与原始函数相同。然而,如果传递更少的参数,仍然会返回一个部分柯里化的函数,等待更多的参数;下面的代码片段说明了这种怪癖: ```js function sum(...nums) { @@ -827,19 +826,19 @@ uncurriedSum( 1, 2, 3, 4, 5 ); // 15 uncurriedSum( 1, 2, 3 )( 4 )( 5 ); // 15 ``` -Probably the more common case of using `uncurry(..)` is not with a manually curried function as just shown, but with a function that comes out curried as a result of some other set of operations. We'll illustrate that scenario later in this chapter in the ["No Points" discussion](#no-points). +可能使用`uncurry(..)`更常见的情况并不是像刚才显示的那样使用手动柯里化函数,而是使用由其他一些操作集生成的柯里化函数。我们将在本章后面的[“No Points”讨论](#no-points)中演示该场景。 -## Order Matters +## 顺序的重要性 -In Chapter 2, we explored the [named arguments pattern](ch2.md/#named-arguments). One primary advantage of named arguments is not needing to juggle argument ordering, thereby improving readability. +在第2章中,我们探讨了[命名参数模式](ch2.md/#named-arguments)。命名参数的一个主要优势是不需要改变参数的顺序,从而提高可读性。 -We've looked at the advantages of using currying/partial application to provide individual arguments to a function separately. But the downside is that these techniques are traditionally based on positional arguments; argument ordering is thus an inevitable headache. +我们已经看到了使用柯里化/局部应用程序分别为函数提供单独参数的优点。但缺点是这些技术传统上是基于位置参数的;因此,论点排序是一个不可避免的头痛问题。 -Utilities like `reverseArgs(..)` (and others) are necessary to juggle arguments to get them into the right order. Sometimes we get lucky and define a function with parameters in the order that we later want to curry them, but other times that order is incompatible and we have to jump through hoops to reorder. +像`reverseArgs(..)`(和其他)这样的实用程序是调整参数以使它们处于正确顺序所必需的。有时我们很幸运地定义了一个带有参数的函数,其顺序我们稍后将对其进行修改,但有时这个顺序是不兼容的,我们必须跳过一些困难才能重新排序。 -The frustration is not merely that we need to use some utility to juggle the properties, but the fact that the usage of the utility clutters up our code a bit with extra noise. These kinds of things are like little paper cuts; one here or there isn't a showstopper, but the pain can certainly add up. +令人沮丧的是,我们不仅需要使用一些实用程序来处理属性,而且使用该实用程序会给代码带来额外的干扰,使代码变得有些混乱。这些东西就像小剪纸;这里有一个,那里没有一个,但痛苦肯定会累积起来。 -Can we improve currying/partial application to free it from these ordering concerns? Let's apply the tricks from named arguments style and invent some helper utilities for this adaptation: +我们能否改进柯里化/部分应用程序,使其免于这些排序问题?让我们应用命名参数风格的技巧,并为这种适应发明一些辅助实用程序: ```js function partialProps(fn,presetArgsObj) { From 975cd229bc244f23f4534e5215dd09e8d53e1342 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 29 Aug 2019 11:05:55 +0800 Subject: [PATCH 50/63] Update ch3.md --- manuscript/ch3.md | 101 ++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index e2dbd110..e672839a 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -866,9 +866,9 @@ function curryProps(fn,arity = 1) { } ``` -**Tip:** We don't even need a `partialPropsRight(..)` because we don't need to care about what order properties are being mapped; the name mappings make that ordering concern moot! +**提示:**我们甚至不需要`partialPropsRight(..)`,因为我们不需要关心映射的顺序属性;名称映射使排序问题也变得不再重要! -Here's how to use those helpers: +以下是如何使用: ```js function foo({ x, y, z } = {}) { @@ -885,13 +885,14 @@ f2( { z: 3, x: 1 } ); // x:1 y:2 z:3 ``` -Even with currying or partial application, order doesn't matter anymore! We can now specify which arguments we want in whatever sequence makes sense. No more `reverseArgs(..)` or other nuisances. Cool! -**Tip:** If this style of function arguments seems useful or interesting to you, check out coverage of my [FPO library in Appendix C](apC.md/#bonus-fpo). +即使使用柯里化或局部应用程序,顺序也不再重要了!现在,我们可以指定在任何有意义的序列中需要哪些参数。不再有`reverseArgs(..)`或其他讨厌的东西。这样真的是太酷了! -### Spreading Properties +**提示:**如果您觉得这种类型的函数参数有用或有趣,请查看附录C中的[FPO库](apC.md/#bonus-fpo)。 -Unfortunately, we can only take advantage of currying with named arguments if we have control over the signature of `foo(..)` and define it to destructure its first parameter. What if we wanted to use this technique with a function that had its parameters individually listed (no parameter destructuring!), and we couldn't change that function signature? For example: +### 扩展属性 + +不幸的是,如果我们控制了`foo(..)`的签名,并且定义它来破坏它的第一个参数,那么我们只能利用与命名参数的冲突。如果我们想将此技术与一个单独列出参数的函数一起使用(没有参数破坏!),我们无法更改该函数签名?例如: ```js function bar(x,y,z) { @@ -899,15 +900,15 @@ function bar(x,y,z) { } ``` -Just like the `spreadArgs(..)` utility earlier, we can define a `spreadArgProps(..)` helper that takes the `key: value` pairs out of an object argument and "spreads" the values out as individual arguments. +就像前面的 `spreadArgs(..)` 实用程序一样,我们可以定义一个`spreadArgProps(..)`,它将`key: value`对从对象参数中取出,并将值作为单独的参数“展开”。 -There are some quirks to be aware of, though. With `spreadArgs(..)`, we were dealing with arrays, where ordering is well defined and obvious. However, with objects, property order is less clear and not necessarily reliable. Depending on how an object is created and properties set, we cannot be absolutely certain what enumeration order properties would come out. +不过,也有一些怪癖需要注意。使用`spreadArgs(..)`,我们处理的是数组,其中的顺序定义得很好,也很明显。然而,对于对象,属性顺序不太清晰,也不一定可靠。根据对象创建和属性设置的方式,我们不能绝对确定将产生哪些枚举顺序属性。 -Such a utility needs a way to let you define what order the function in question expects its arguments (e.g., property enumeration order). We can pass an array like `["x","y","z"]` to tell the utility to pull the properties off the object argument in exactly that order. +这样的实用程序需要一种方法来让您定义函数期望参数的顺序(例如,属性枚举顺序)。我们可以传递一个像`["x","y","z"]` 这样的数组,来告诉这个实用程序以完全相同的顺序从对象参数中提取属性。 -That's decent, but it's also unfortunate that it then *obligates* us to add that property-name array even for the simplest of functions. Is there any kind of trick we could use to detect what order the parameters are listed for a function, in at least the common simple cases? Fortunately, yes! +这很好,但不幸的是,即使对于最简单的函数,它也“强制”我们添加属性名数组。至少在常见的简单情况下,有没有什么技巧可以用来检测函数参数的排列顺序?幸运的是,有的! -JavaScript functions have a `.toString()` method that gives a string representation of the function's code, including the function declaration signature. Dusting off our regular expression parsing skills, we can parse the string representation of the function, and pull out the individually named parameters. The code looks a bit gnarly, but it's good enough to get the job done: +JavaScript函数有一个`.toString()`方法,该方法给出函数代码的字符串表示,包括函数声明签名。抛开正则表达式解析技巧,我们可以解析函数的字符串表示,并提取单独命名的参数。代码看起来有点粗糙,但已经足够完成这项工作了: ```js function spreadArgProps( @@ -925,9 +926,9 @@ function spreadArgProps( } ``` -**Note:** This utility's parameter parsing logic is far from bullet-proof; we're using regular expressions to parse code, which is already a faulty premise! But our only goal here is to handle the common cases, which this does reasonably well. We only need a sensible default detection of parameter order for functions with simple parameters (including those with default parameter values). We don't, for example, need to be able to parse out a complex destructured parameter, because we wouldn't likely be using this utility with such a function, anyway. So, this logic gets the job done 80% of the time; it lets us override the `propOrder` array for any other more complex function signature that wouldn't otherwise be correctly parsed. That's the kind of pragmatic balance this book seeks to find wherever possible. +**注意:**本实用程序的参数解析逻辑远非无懈可击;我们使用正则表达式解析代码,这已经是一个错误的前提!但我们这里的唯一目标是处理常见的情况,这做得相当好。我们只需要对具有简单参数的函数(包括具有缺省参数值的函数)的参数顺序进行合理的缺省检测。例如,我们不需要能够解析出复杂的析构参数,因为我们不太可能将这个实用程序与这样的函数一起使用。所以,这个逻辑在80%的时间里完成了任务;它允许我们覆盖任何其他更复杂的函数签名的“比例”数组,否则将无法正确解析。这就是本书所寻求的那种务实的平衡。 -Let's illustrate using our `spreadArgProps(..)` utility: +让我们用我们的`spreadArgProps(..)`工具来演示: ```js function bar(x,y,z) { @@ -944,17 +945,18 @@ f4( { z: 3, x: 1 } ); // x:1 y:2 z:3 ``` -While order is no longer a concern, usage of functions defined in this style requires you to know what each argument's exact name is. You can't just remember, "oh, the function goes in as the first argument" anymore. Instead, you have to remember, "the function parameter is called 'fn'." Conventions can create consistency of naming that lessens this burden, but it's still something to be aware of. +虽然顺序不再是问题,但是使用这种样式定义的函数需要知道每个参数的确切名称。你不能只记住,“哦,函数作为第一个参数进入”相反,您必须记住,“函数参数称为'fn'。”约定可以创建一致性的命名,从而减轻这种负担,但是仍然需要注意。 + +认真考虑这些权衡 -Weigh these trade-offs carefully. -## No Points +## 无参数风格 -A popular style of coding in the FP world aims to reduce some of the visual clutter by removing unnecessary parameter-argument mapping. This style is formally called tacit programming, or more commonly: point-free style. The term "point" here is referring to a function's parameter input. +函数编程中流行的一种编码风格旨在通过删除不必要的参数-参数映射来减少一些视觉上的混乱。这种风格的正式名称是默示编程,或者更常见的名称是:无参数风格编程。这里的术语“点”(点(point):指的是参数)指的是函数的参数输入。 -**Warning:** Stop for a moment. Let's make sure we're careful not to take this discussion as an unbounded suggestion that you go overboard trying to be point-free in your FP code at all costs. This should be a technique for improving readability, when used in moderation. But as with most things in software development, you can definitely abuse it. If your code gets harder to understand because of the hoops you have to jump through to be point-free, stop. You won't win a blue ribbon just because you found some clever but esoteric way to remove another "point" from your code. +**警告:**请稍等。让我们确保我们小心不要把这个讨论看作是一个无界的建议,即您不惜一切代价在FP代码中尝试免费。如果使用得当,这应该是一种提高可读性的技术。但与软件开发中的大多数事情一样,您肯定会滥用它。如果你的代码变得难以理解,因为你必须跳过这些障碍才能不扣分,那就停下来。您不会仅仅因为找到了一些聪明但深奥的方法来从代码中删除另一个参数就获得蓝丝带奖励。 -Let's start with a simple example: +让我们从一个简单的例子开始: ```js function double(x) { @@ -967,7 +969,7 @@ function double(x) { // [2,4,6,8,10] ``` -Can you see that `mapper(..)` and `double(..)` have the same (or compatible, anyway) signatures? The parameter ("point") `v` can directly map to the corresponding argument in the `double(..)` call. As such, the `mapper(..)` function wrapper is unnecessary. Let's simplify with point-free style: +您能看到mapper(..)`和`double(..)`具有相同(或者兼容)的签名吗?参数`v`可以直接映射到`double(..)`调用中对应的参数。因此,`mapper(..)`函数包装器是不必要的。让我们用无参数风格的方式来简化: ```js function double(x) { @@ -978,7 +980,7 @@ function double(x) { // [2,4,6,8,10] ``` -Let's revisit an example from earlier: +让我们回顾一下之前的一个例子: ```js ["1","2","3"].map( function mapper(v){ @@ -987,28 +989,28 @@ Let's revisit an example from earlier: // [1,2,3] ``` -In this example, `mapper(..)` is actually serving an important purpose, which is to discard the `index` argument that `map(..)` would pass in, because `parseInt(..)` would incorrectly interpret that value as a `radix` for the parsing. +在本例中, `mapper(..)`实际上起到了一个重要的作用,即丢弃了`map(..)`传入的`index`参数,因为 `parseInt(..)`将错误地将该`index`值解释为解析的进制'基数'。 -If you recall from the beginning of this chapter, this was an example where `unary(..)` helps us out: +如果你回想一下这一章的开头, `unary(..)` 可以帮助我们解决问题的例子: ```js ["1","2","3"].map( unary( parseInt ) ); // [1,2,3] ``` -Point-free! +这就是无参数风格! -The key thing to look for is if you have a function with parameter(s) that is/are directly passed to an inner function call. In both of the preceding examples, `mapper(..)` had the `v` parameter that was passed along to another function call. We were able to replace that layer of abstraction with a point-free expression using `unary(..)`. +要查找的关键问题是,是否有一个带有参数的函数直接传递给内部函数调用。在前面的两个示例中,`mapper(..)`具有传递给另一个函数调用的`v` 参数。我们能够用一个使用`unary(..)`的无参数表达式替换这个抽象层。 -**Warning:** You might have been tempted, as I was, to try `map(partialRight(parseInt,10))` to right-partially apply the `10` value as the `radix`. However, as we saw earlier, `partialRight(..)` only guarantees that `10` will be the last argument passed in, not that it will be specifically the second argument. Since `map(..)` itself passes three arguments (`value`, `index`, `arr`) to its mapping function, the `10` value would just be the fourth argument to `parseInt(..)`; it only pays attention to the first two. +**警告:**您可能和我一样,尝试将`map(partialRight(parseInt,10))`应用到右侧,部分应用`10`值作为进制“基数”。然而,正如我们前面看到的,`partialRight(..)`只保证`10`是传入的最后一个参数,而不是确切地说是第二个参数。由于`map(..)`本身将三个参数(`value`, `index`, `arr`) 传递给它的映射函数,`10`值将是`parseInt(..)`的第四个参数;它只关注前两个。 -Here's another example: +这是另一个例子: ```js -// convenience to avoid any potential binding issue -// with trying to use `console.log` as a function +// 方便避免任何潜在的绑定问题 +// 尝试使用`console.log`作为一个函数 function output(txt) { console.log( txt ); } @@ -1030,7 +1032,7 @@ printIf( isShortEnough, msg1 ); // Hello printIf( isShortEnough, msg2 ); ``` -Now let's say you want to print a message only if it's long enough; in other words, if it's `!isShortEnough(..)`. Your first thought is probably this: +现在假设您只想在消息足够长的情况下打印消息;换句话说,如果是运行到`!isShortEnough(..)`。你的第一个想法可能是: ```js function isLongEnough(str) { @@ -1041,9 +1043,9 @@ printIf( isLongEnough, msg1 ); printIf( isLongEnough, msg2 ); // Hello World ``` -Easy enough... but "points" now! See how `str` is passed through? Without re-implementing the `str.length` check, can we refactor this code to point-free style? +很简单…但现在看现在的参数!看看`str`是如何传递的?如果不重新实现`str.length`检查,我们可以重构这段代码为无参数风格的代码吗? -Let's define a `not(..)` negation helper (often referred to as `complement(..)` in FP libraries): +让我们定义一个 `not(..)` 函数(通常在FP库中称为`complement(..)`): ```js function not(predicate) { @@ -1059,6 +1061,7 @@ var not = !predicate( ...args ); ``` +接下来,我们使用`not(..)`来定义没有参数的`isLongEnough(..)`: Next, let's use `not(..)` to alternatively define `isLongEnough(..)` without "points": ```js @@ -1067,9 +1070,9 @@ var isLongEnough = not( isShortEnough ); printIf( isLongEnough, msg2 ); // Hello World ``` -That's pretty good, isn't it? But we *could* keep going. `printIf(..)` could be refactored to be point-free itself. +很不错,不是吗?但是我们可以继续。`printIf(..)`本身可以重构为无参数。 -We can express the `if` conditional part with a `when(..)` utility: +我们可以用一个`when(..)`工具来表达`if`条件句: ```js function when(predicate,fn) { @@ -1080,24 +1083,25 @@ function when(predicate,fn) { }; } -// or the ES6 => form +// ES6箭头格式 var when = (predicate,fn) => (...args) => predicate( ...args ) ? fn( ...args ) : undefined; ``` -Let's mix `when(..)` with a few other helper utilities we've seen earlier in this chapter, to make the point-free `printIf(..)`: +让我们将`when(..)`和我们在本章前面看到的其他一些辅助工具混合使用,以实现无参数的`printIf(..)`: ```js var printIf = uncurry( partialRight( when, output ) ); ``` -Here's how we did it: we right-partially-applied the `output` method as the second (`fn`) argument for `when(..)`, which leaves us with a function still expecting the first argument (`predicate`). *That* function when called produces another function expecting the message string; it would look like this: `fn(predicate)(str)`. +我们是这样做的:我们正确地部分应用了`output`方法作为`when(..)`的第二个(`fn`)参数,这使得我们有一个函数仍然需要第一个参数(`predicate`)。*当调用该*函数时,会生成另一个期望消息字符串的函数;它看起来像是:`fn(predicate)(str)`。 -A chain of multiple (two) function calls like that looks an awful lot like a curried function, so we `uncurry(..)` this result to produce a single function that expects the two `str` and `predicate` arguments together, which matches the original `printIf(predicate,str)` signature. -Here's the whole example put back together (assuming various utilities we've already detailed in this chapter are present): +一个由多个(两个)函数调用组成的链看起来非常像一个循环函数,因此我们`uncurry(..)`这个结果产生一个函数,它期望两个`str`和`predicate`参数在一起,这与原始的`printIf(predicate,str)`签名匹配。 + +下面是完整的例子(假设本章已经详细介绍了各种实用工具): @@ -1123,21 +1127,20 @@ printIf( isShortEnough, msg2 ); printIf( isLongEnough, msg1 ); printIf( isLongEnough, msg2 ); // Hello World ``` +希望FP(函数编程)的无参数风格编码实践开始变得更有意义。要训练自己自然地以这种方式思考,仍然需要大量的练习。**您仍然需要判断调用**是否值得这样做,以及在多大程度上有利于代码的可读性。 -Hopefully the FP practice of point-free style coding is starting to make a little more sense. It'll still take a lot of practice to train yourself to think this way naturally. **And you'll still have to make judgement calls** as to whether point-free coding is worth it, as well as what extent will benefit your code's readability. - -What do you think? Points or no points for you? +你觉得怎么样?需不需要参数的风格都在于你? -**Note:** Want more practice with point-free style coding? We'll revisit this technique in [Chapter 4, "Revisiting Points"](ch4.md/#revisiting-points), based on newfound knowledge of function composition. +**注意:**想要更多的实践无参数编码风格?我们将在[第4章,"重提参数的作用"](ch4.md/#revisiting-points)中基于函数组合的新知识重新讨论这种技术。 -## Summary +## 小结 -Partial application is a technique for reducing the arity (that is, the expected number of arguments to a function) by creating a new function where some of the arguments are preset. +部分应用程序是一种通过创建一个新的函数来减少元数(函数的预期参数数量)的技术,其中一些参数是预设的。 -Currying is a special form of partial application where the arity is reduced to 1, with a chain of successive chained function calls, each which takes one argument. Once all arguments have been specified by these function calls, the original function is executed with all the collected arguments. You can also undo a currying. +柯里化是一种特殊的部分应用形式,其中元数(函数的参数数量)被简化为1,具有一系列连续的链接函数调用,每个调用都接受一个参数。一旦这些函数调用指定了所有参数,就可以使用收集的所有参数执行原始函数。当然你也可以反柯里化。 -Other important utilities like `unary(..)`, `identity(..)`, and `constant(..)` are part of the base toolbox for FP. +其他重要的实用程序,如`unary(..)`, `identity(..)`和`constant(..)`是FP(函数编程)基本工具库的一部分。 -Point-free is a style of writing code that eliminates unnecessary verbosity of mapping parameters ("points") to arguments, with the goal of making code easier to read/understand. +无参数(Point-free)是一种编写代码的风格,它消除了将参数(“point”)映射到参数的不必要冗长,目的是使代码更容易阅读理解。 -All of these techniques twist functions around so they can work together more naturally. With your functions shaped compatibly now, the next chapter will teach you how to combine them to model the flows of data through your program. +所有这些技术都围绕着功能进行调整,以便更自然地协同工作。现在您的函数已经形成了兼容的形式,下一章将教会您如何将它们组合起来为程序中的数据流建模。 From b4b93c71492d5d77002a5dc52305505a91692c59 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 29 Aug 2019 11:06:46 +0800 Subject: [PATCH 51/63] Update ch3.md --- manuscript/ch3.md | 101 ++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/manuscript/ch3.md b/manuscript/ch3.md index e2dbd110..e672839a 100644 --- a/manuscript/ch3.md +++ b/manuscript/ch3.md @@ -866,9 +866,9 @@ function curryProps(fn,arity = 1) { } ``` -**Tip:** We don't even need a `partialPropsRight(..)` because we don't need to care about what order properties are being mapped; the name mappings make that ordering concern moot! +**提示:**我们甚至不需要`partialPropsRight(..)`,因为我们不需要关心映射的顺序属性;名称映射使排序问题也变得不再重要! -Here's how to use those helpers: +以下是如何使用: ```js function foo({ x, y, z } = {}) { @@ -885,13 +885,14 @@ f2( { z: 3, x: 1 } ); // x:1 y:2 z:3 ``` -Even with currying or partial application, order doesn't matter anymore! We can now specify which arguments we want in whatever sequence makes sense. No more `reverseArgs(..)` or other nuisances. Cool! -**Tip:** If this style of function arguments seems useful or interesting to you, check out coverage of my [FPO library in Appendix C](apC.md/#bonus-fpo). +即使使用柯里化或局部应用程序,顺序也不再重要了!现在,我们可以指定在任何有意义的序列中需要哪些参数。不再有`reverseArgs(..)`或其他讨厌的东西。这样真的是太酷了! -### Spreading Properties +**提示:**如果您觉得这种类型的函数参数有用或有趣,请查看附录C中的[FPO库](apC.md/#bonus-fpo)。 -Unfortunately, we can only take advantage of currying with named arguments if we have control over the signature of `foo(..)` and define it to destructure its first parameter. What if we wanted to use this technique with a function that had its parameters individually listed (no parameter destructuring!), and we couldn't change that function signature? For example: +### 扩展属性 + +不幸的是,如果我们控制了`foo(..)`的签名,并且定义它来破坏它的第一个参数,那么我们只能利用与命名参数的冲突。如果我们想将此技术与一个单独列出参数的函数一起使用(没有参数破坏!),我们无法更改该函数签名?例如: ```js function bar(x,y,z) { @@ -899,15 +900,15 @@ function bar(x,y,z) { } ``` -Just like the `spreadArgs(..)` utility earlier, we can define a `spreadArgProps(..)` helper that takes the `key: value` pairs out of an object argument and "spreads" the values out as individual arguments. +就像前面的 `spreadArgs(..)` 实用程序一样,我们可以定义一个`spreadArgProps(..)`,它将`key: value`对从对象参数中取出,并将值作为单独的参数“展开”。 -There are some quirks to be aware of, though. With `spreadArgs(..)`, we were dealing with arrays, where ordering is well defined and obvious. However, with objects, property order is less clear and not necessarily reliable. Depending on how an object is created and properties set, we cannot be absolutely certain what enumeration order properties would come out. +不过,也有一些怪癖需要注意。使用`spreadArgs(..)`,我们处理的是数组,其中的顺序定义得很好,也很明显。然而,对于对象,属性顺序不太清晰,也不一定可靠。根据对象创建和属性设置的方式,我们不能绝对确定将产生哪些枚举顺序属性。 -Such a utility needs a way to let you define what order the function in question expects its arguments (e.g., property enumeration order). We can pass an array like `["x","y","z"]` to tell the utility to pull the properties off the object argument in exactly that order. +这样的实用程序需要一种方法来让您定义函数期望参数的顺序(例如,属性枚举顺序)。我们可以传递一个像`["x","y","z"]` 这样的数组,来告诉这个实用程序以完全相同的顺序从对象参数中提取属性。 -That's decent, but it's also unfortunate that it then *obligates* us to add that property-name array even for the simplest of functions. Is there any kind of trick we could use to detect what order the parameters are listed for a function, in at least the common simple cases? Fortunately, yes! +这很好,但不幸的是,即使对于最简单的函数,它也“强制”我们添加属性名数组。至少在常见的简单情况下,有没有什么技巧可以用来检测函数参数的排列顺序?幸运的是,有的! -JavaScript functions have a `.toString()` method that gives a string representation of the function's code, including the function declaration signature. Dusting off our regular expression parsing skills, we can parse the string representation of the function, and pull out the individually named parameters. The code looks a bit gnarly, but it's good enough to get the job done: +JavaScript函数有一个`.toString()`方法,该方法给出函数代码的字符串表示,包括函数声明签名。抛开正则表达式解析技巧,我们可以解析函数的字符串表示,并提取单独命名的参数。代码看起来有点粗糙,但已经足够完成这项工作了: ```js function spreadArgProps( @@ -925,9 +926,9 @@ function spreadArgProps( } ``` -**Note:** This utility's parameter parsing logic is far from bullet-proof; we're using regular expressions to parse code, which is already a faulty premise! But our only goal here is to handle the common cases, which this does reasonably well. We only need a sensible default detection of parameter order for functions with simple parameters (including those with default parameter values). We don't, for example, need to be able to parse out a complex destructured parameter, because we wouldn't likely be using this utility with such a function, anyway. So, this logic gets the job done 80% of the time; it lets us override the `propOrder` array for any other more complex function signature that wouldn't otherwise be correctly parsed. That's the kind of pragmatic balance this book seeks to find wherever possible. +**注意:**本实用程序的参数解析逻辑远非无懈可击;我们使用正则表达式解析代码,这已经是一个错误的前提!但我们这里的唯一目标是处理常见的情况,这做得相当好。我们只需要对具有简单参数的函数(包括具有缺省参数值的函数)的参数顺序进行合理的缺省检测。例如,我们不需要能够解析出复杂的析构参数,因为我们不太可能将这个实用程序与这样的函数一起使用。所以,这个逻辑在80%的时间里完成了任务;它允许我们覆盖任何其他更复杂的函数签名的“比例”数组,否则将无法正确解析。这就是本书所寻求的那种务实的平衡。 -Let's illustrate using our `spreadArgProps(..)` utility: +让我们用我们的`spreadArgProps(..)`工具来演示: ```js function bar(x,y,z) { @@ -944,17 +945,18 @@ f4( { z: 3, x: 1 } ); // x:1 y:2 z:3 ``` -While order is no longer a concern, usage of functions defined in this style requires you to know what each argument's exact name is. You can't just remember, "oh, the function goes in as the first argument" anymore. Instead, you have to remember, "the function parameter is called 'fn'." Conventions can create consistency of naming that lessens this burden, but it's still something to be aware of. +虽然顺序不再是问题,但是使用这种样式定义的函数需要知道每个参数的确切名称。你不能只记住,“哦,函数作为第一个参数进入”相反,您必须记住,“函数参数称为'fn'。”约定可以创建一致性的命名,从而减轻这种负担,但是仍然需要注意。 + +认真考虑这些权衡 -Weigh these trade-offs carefully. -## No Points +## 无参数风格 -A popular style of coding in the FP world aims to reduce some of the visual clutter by removing unnecessary parameter-argument mapping. This style is formally called tacit programming, or more commonly: point-free style. The term "point" here is referring to a function's parameter input. +函数编程中流行的一种编码风格旨在通过删除不必要的参数-参数映射来减少一些视觉上的混乱。这种风格的正式名称是默示编程,或者更常见的名称是:无参数风格编程。这里的术语“点”(点(point):指的是参数)指的是函数的参数输入。 -**Warning:** Stop for a moment. Let's make sure we're careful not to take this discussion as an unbounded suggestion that you go overboard trying to be point-free in your FP code at all costs. This should be a technique for improving readability, when used in moderation. But as with most things in software development, you can definitely abuse it. If your code gets harder to understand because of the hoops you have to jump through to be point-free, stop. You won't win a blue ribbon just because you found some clever but esoteric way to remove another "point" from your code. +**警告:**请稍等。让我们确保我们小心不要把这个讨论看作是一个无界的建议,即您不惜一切代价在FP代码中尝试免费。如果使用得当,这应该是一种提高可读性的技术。但与软件开发中的大多数事情一样,您肯定会滥用它。如果你的代码变得难以理解,因为你必须跳过这些障碍才能不扣分,那就停下来。您不会仅仅因为找到了一些聪明但深奥的方法来从代码中删除另一个参数就获得蓝丝带奖励。 -Let's start with a simple example: +让我们从一个简单的例子开始: ```js function double(x) { @@ -967,7 +969,7 @@ function double(x) { // [2,4,6,8,10] ``` -Can you see that `mapper(..)` and `double(..)` have the same (or compatible, anyway) signatures? The parameter ("point") `v` can directly map to the corresponding argument in the `double(..)` call. As such, the `mapper(..)` function wrapper is unnecessary. Let's simplify with point-free style: +您能看到mapper(..)`和`double(..)`具有相同(或者兼容)的签名吗?参数`v`可以直接映射到`double(..)`调用中对应的参数。因此,`mapper(..)`函数包装器是不必要的。让我们用无参数风格的方式来简化: ```js function double(x) { @@ -978,7 +980,7 @@ function double(x) { // [2,4,6,8,10] ``` -Let's revisit an example from earlier: +让我们回顾一下之前的一个例子: ```js ["1","2","3"].map( function mapper(v){ @@ -987,28 +989,28 @@ Let's revisit an example from earlier: // [1,2,3] ``` -In this example, `mapper(..)` is actually serving an important purpose, which is to discard the `index` argument that `map(..)` would pass in, because `parseInt(..)` would incorrectly interpret that value as a `radix` for the parsing. +在本例中, `mapper(..)`实际上起到了一个重要的作用,即丢弃了`map(..)`传入的`index`参数,因为 `parseInt(..)`将错误地将该`index`值解释为解析的进制'基数'。 -If you recall from the beginning of this chapter, this was an example where `unary(..)` helps us out: +如果你回想一下这一章的开头, `unary(..)` 可以帮助我们解决问题的例子: ```js ["1","2","3"].map( unary( parseInt ) ); // [1,2,3] ``` -Point-free! +这就是无参数风格! -The key thing to look for is if you have a function with parameter(s) that is/are directly passed to an inner function call. In both of the preceding examples, `mapper(..)` had the `v` parameter that was passed along to another function call. We were able to replace that layer of abstraction with a point-free expression using `unary(..)`. +要查找的关键问题是,是否有一个带有参数的函数直接传递给内部函数调用。在前面的两个示例中,`mapper(..)`具有传递给另一个函数调用的`v` 参数。我们能够用一个使用`unary(..)`的无参数表达式替换这个抽象层。 -**Warning:** You might have been tempted, as I was, to try `map(partialRight(parseInt,10))` to right-partially apply the `10` value as the `radix`. However, as we saw earlier, `partialRight(..)` only guarantees that `10` will be the last argument passed in, not that it will be specifically the second argument. Since `map(..)` itself passes three arguments (`value`, `index`, `arr`) to its mapping function, the `10` value would just be the fourth argument to `parseInt(..)`; it only pays attention to the first two. +**警告:**您可能和我一样,尝试将`map(partialRight(parseInt,10))`应用到右侧,部分应用`10`值作为进制“基数”。然而,正如我们前面看到的,`partialRight(..)`只保证`10`是传入的最后一个参数,而不是确切地说是第二个参数。由于`map(..)`本身将三个参数(`value`, `index`, `arr`) 传递给它的映射函数,`10`值将是`parseInt(..)`的第四个参数;它只关注前两个。 -Here's another example: +这是另一个例子: ```js -// convenience to avoid any potential binding issue -// with trying to use `console.log` as a function +// 方便避免任何潜在的绑定问题 +// 尝试使用`console.log`作为一个函数 function output(txt) { console.log( txt ); } @@ -1030,7 +1032,7 @@ printIf( isShortEnough, msg1 ); // Hello printIf( isShortEnough, msg2 ); ``` -Now let's say you want to print a message only if it's long enough; in other words, if it's `!isShortEnough(..)`. Your first thought is probably this: +现在假设您只想在消息足够长的情况下打印消息;换句话说,如果是运行到`!isShortEnough(..)`。你的第一个想法可能是: ```js function isLongEnough(str) { @@ -1041,9 +1043,9 @@ printIf( isLongEnough, msg1 ); printIf( isLongEnough, msg2 ); // Hello World ``` -Easy enough... but "points" now! See how `str` is passed through? Without re-implementing the `str.length` check, can we refactor this code to point-free style? +很简单…但现在看现在的参数!看看`str`是如何传递的?如果不重新实现`str.length`检查,我们可以重构这段代码为无参数风格的代码吗? -Let's define a `not(..)` negation helper (often referred to as `complement(..)` in FP libraries): +让我们定义一个 `not(..)` 函数(通常在FP库中称为`complement(..)`): ```js function not(predicate) { @@ -1059,6 +1061,7 @@ var not = !predicate( ...args ); ``` +接下来,我们使用`not(..)`来定义没有参数的`isLongEnough(..)`: Next, let's use `not(..)` to alternatively define `isLongEnough(..)` without "points": ```js @@ -1067,9 +1070,9 @@ var isLongEnough = not( isShortEnough ); printIf( isLongEnough, msg2 ); // Hello World ``` -That's pretty good, isn't it? But we *could* keep going. `printIf(..)` could be refactored to be point-free itself. +很不错,不是吗?但是我们可以继续。`printIf(..)`本身可以重构为无参数。 -We can express the `if` conditional part with a `when(..)` utility: +我们可以用一个`when(..)`工具来表达`if`条件句: ```js function when(predicate,fn) { @@ -1080,24 +1083,25 @@ function when(predicate,fn) { }; } -// or the ES6 => form +// ES6箭头格式 var when = (predicate,fn) => (...args) => predicate( ...args ) ? fn( ...args ) : undefined; ``` -Let's mix `when(..)` with a few other helper utilities we've seen earlier in this chapter, to make the point-free `printIf(..)`: +让我们将`when(..)`和我们在本章前面看到的其他一些辅助工具混合使用,以实现无参数的`printIf(..)`: ```js var printIf = uncurry( partialRight( when, output ) ); ``` -Here's how we did it: we right-partially-applied the `output` method as the second (`fn`) argument for `when(..)`, which leaves us with a function still expecting the first argument (`predicate`). *That* function when called produces another function expecting the message string; it would look like this: `fn(predicate)(str)`. +我们是这样做的:我们正确地部分应用了`output`方法作为`when(..)`的第二个(`fn`)参数,这使得我们有一个函数仍然需要第一个参数(`predicate`)。*当调用该*函数时,会生成另一个期望消息字符串的函数;它看起来像是:`fn(predicate)(str)`。 -A chain of multiple (two) function calls like that looks an awful lot like a curried function, so we `uncurry(..)` this result to produce a single function that expects the two `str` and `predicate` arguments together, which matches the original `printIf(predicate,str)` signature. -Here's the whole example put back together (assuming various utilities we've already detailed in this chapter are present): +一个由多个(两个)函数调用组成的链看起来非常像一个循环函数,因此我们`uncurry(..)`这个结果产生一个函数,它期望两个`str`和`predicate`参数在一起,这与原始的`printIf(predicate,str)`签名匹配。 + +下面是完整的例子(假设本章已经详细介绍了各种实用工具): @@ -1123,21 +1127,20 @@ printIf( isShortEnough, msg2 ); printIf( isLongEnough, msg1 ); printIf( isLongEnough, msg2 ); // Hello World ``` +希望FP(函数编程)的无参数风格编码实践开始变得更有意义。要训练自己自然地以这种方式思考,仍然需要大量的练习。**您仍然需要判断调用**是否值得这样做,以及在多大程度上有利于代码的可读性。 -Hopefully the FP practice of point-free style coding is starting to make a little more sense. It'll still take a lot of practice to train yourself to think this way naturally. **And you'll still have to make judgement calls** as to whether point-free coding is worth it, as well as what extent will benefit your code's readability. - -What do you think? Points or no points for you? +你觉得怎么样?需不需要参数的风格都在于你? -**Note:** Want more practice with point-free style coding? We'll revisit this technique in [Chapter 4, "Revisiting Points"](ch4.md/#revisiting-points), based on newfound knowledge of function composition. +**注意:**想要更多的实践无参数编码风格?我们将在[第4章,"重提参数的作用"](ch4.md/#revisiting-points)中基于函数组合的新知识重新讨论这种技术。 -## Summary +## 小结 -Partial application is a technique for reducing the arity (that is, the expected number of arguments to a function) by creating a new function where some of the arguments are preset. +部分应用程序是一种通过创建一个新的函数来减少元数(函数的预期参数数量)的技术,其中一些参数是预设的。 -Currying is a special form of partial application where the arity is reduced to 1, with a chain of successive chained function calls, each which takes one argument. Once all arguments have been specified by these function calls, the original function is executed with all the collected arguments. You can also undo a currying. +柯里化是一种特殊的部分应用形式,其中元数(函数的参数数量)被简化为1,具有一系列连续的链接函数调用,每个调用都接受一个参数。一旦这些函数调用指定了所有参数,就可以使用收集的所有参数执行原始函数。当然你也可以反柯里化。 -Other important utilities like `unary(..)`, `identity(..)`, and `constant(..)` are part of the base toolbox for FP. +其他重要的实用程序,如`unary(..)`, `identity(..)`和`constant(..)`是FP(函数编程)基本工具库的一部分。 -Point-free is a style of writing code that eliminates unnecessary verbosity of mapping parameters ("points") to arguments, with the goal of making code easier to read/understand. +无参数(Point-free)是一种编写代码的风格,它消除了将参数(“point”)映射到参数的不必要冗长,目的是使代码更容易阅读理解。 -All of these techniques twist functions around so they can work together more naturally. With your functions shaped compatibly now, the next chapter will teach you how to combine them to model the flows of data through your program. +所有这些技术都围绕着功能进行调整,以便更自然地协同工作。现在您的函数已经形成了兼容的形式,下一章将教会您如何将它们组合起来为程序中的数据流建模。 From d9dc1c11d8a3d6763fd25f20027cee6656fed32a Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 2 Sep 2019 10:37:48 +0800 Subject: [PATCH 52/63] Update ch4.md --- manuscript/ch4.md | 57 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/manuscript/ch4.md b/manuscript/ch4.md index 96f5b2cf..49ec922f 100644 --- a/manuscript/ch4.md +++ b/manuscript/ch4.md @@ -1,41 +1,40 @@ -# Functional-Light JavaScript -# Chapter 4: Composing Functions +# 章节 4: 组合函数 -By now, I hope you're feeling much more comfortable with what it means to use functions for functional programming. +到目前为止,我希望您对使用函数进行函数编程的含义感到更舒服。 -A functional programmer sees every function in their program like a simple little Lego piece. They recognize the blue 2x2 brick at a glance, and know exactly how it works and what they can do with it. When they begin building a bigger, more complex Lego model, as they need each next piece, they already have an instinct for which of their many spare pieces to grab. +函数式程序员把程序中的每个函数都看作是一个简单的乐高积木。他们一眼就能认出蓝色的2x2砖块,并且确切地知道它是如何工作的,以及他们可以用它做什么。当他们开始建造一个更大、更复杂的乐高模型时,因为他们需要每一块积木,他们已经有了一种本能,从他们的许多备用积木中找出哪一块来。 -But sometimes you take the blue 2x2 brick and the gray 4x1 brick and put them together in a certain way, and you realize, "that's a useful piece that I need often". +但有时你把蓝色的2x2块和灰色的4x1块以某种方式放在一起,你会意识到,“这是我经常需要的一块有用的砖”。 -So now you've come up with a new "piece", a combination of two other pieces, and you can reach for that kind of piece now anytime you need it. It's more effective to recognize and use this compound blue-gray L-brick thing where it's needed than to separately think about assembling the two individual bricks each time. +所以现在你已经想出了一个新的“片段”,两个其他片段的组合,你现在可以在任何你需要的时候得到这种片段。识别并在需要的地方使用这种蓝灰色的复合l形砖要比每次单独考虑组装两个单独的砖更有效。 -Functions come in a variety of shapes and sizes. And we can define a certain combination of them to make a new compound function that will be handy in various parts of the program. This process of using functions together is called composition. +函数有各种形状和大小。我们可以定义它们的特定组合来创建一个新的复合函数,这个函数在程序的各个部分都很方便。将函数一起使用的过程称为组合。 -Composition is how an FPer models the flow of data through the program. In some senses, it's the most foundational concept in all of FP, because without it, you can't declaratively model data and state changes. In other words, everything else in FP would collapse without composition. +组合是指函数编程人员如何对程序中的数据流建模。从某种意义上说,它是函数编程中最基本的概念,因为没有它,您就不能声明性地对数据和状态更改建模。换句话说,如果没有组分,函数编程中的其他所有东西都会崩溃。 -## Output to Input +## 输出到输入 -We've already seen a few examples of composition. For example, our discussion of [`unary(..)` in Chapter 3](ch3.md/#user-content-unary) included this expression: [`[..].map(unary(parseInt))`](ch3.md/#user-content-mapunary). Think about what's happening there. +我们已经看到了一些合成的例子。例如,我们在第3章中讨论[`unary(..)`](ch3.md/#user-content-unary)时包含了这样一个表达式:[' [..].map(unary(parseInt))](ch3.md/#user-content-mapunary)。想想发生了什么。 -To compose two functions together, pass the output of the first function call as the input of the second function call. In `map(unary(parseInt))`, the `unary(parseInt)` call returns a value (a function); that value is directly passed as an argument to `map(..)`, which returns an array. +要将两个函数组合在一起,请将第一个函数调用的输出作为第二个函数调用的输入。在`map(unary(parseInt))`中,`unary(parseInt)`调用返回一个值(一个函数);该值直接作为参数传递给 `map(..)`,后者返回一个数组。 -To take a step back and visualize the conceptual flow of data, consider: +后退一步考虑并结合可视化数据的概念流: ```txt arrayValue <-- map <-- unary <-- parseInt ``` -`parseInt` is the input to `unary(..)`. The output of `unary(..)` is the input to `map(..)`. The output of `map(..)` is `arrayValue`. This is the composition of `map(..)` and `unary(..)`. +`parseInt` 是`unary(..)`的输入值. `unary(..)` 的输出值是`map(..)`的输入值. `map(..)` 的输出值是 `arrayValue`. 这就是 `map(..)` 与 `unary(..)`的组合. -**Note:** The right-to-left orientation here is on purpose, though it may seem strange at this point in your learning. We'll come back to explain that more fully later. +**注意:**这里的从右到左的方向是有意这样的,尽管在您的学习过程中这一点可能看起来很奇怪。我们稍后会详细解释。 -Think of this flow of data like a conveyor belt in a candy factory, where each operation is a step in the process of cooling, cutting, and wrapping a piece of candy. We'll use the candy factory metaphor throughout this chapter to explain what composition is. +把这个数据流想象成糖果工厂的传送带,每个操作都是冷却、切割和包装糖果过程中的一个步骤。我们将在本章中使用糖果工厂的比喻来解释什么是组合。

-Let's examine composition in action one step at a time. Consider these two utilities you might have in your program: +让我们一步一步地研究一下实际的作文。考虑一下您的程序中可能具有的这两个实用程序: ```js function words(str) { @@ -61,9 +60,9 @@ function unique(list) { } ``` -`words(..)` splits a string into an array of words. `unique(..)` takes a list of words and filters it to not have any repeat words in it. +`words(..)`将字符串分割成单词数组。`unique(..)`获取一个单词列表,并对其进行筛选,使其不包含任何重复的单词。 -To use these two utilities to analyze a string of text: +要使用这两个实用程序来分析一段文本字符串: ```js var text = "To compose two functions together, pass the \ @@ -79,35 +78,35 @@ wordsUsed; // "input","second"] ``` -We name the array output of `words(..)` as `wordsFound`. The input of `unique(..)` is also an array, so we can pass the `wordsFound` into it. +我们将`words(..)`的数组输出命名为`wordsFound`。`unique(..)`的输入也是一个数组,因此我们可以将 `wordsFound`传递给它。 -Back to the candy factory assembly line: the first machine takes as "input" the melted chocolate, and its "output" is a chunk of formed and cooled chocolate. The next machine a little down the assembly line takes as its "input" the chunk of chocolate, and its "output" is a cut-up piece of chocolate candy. Next, a machine on the line takes small pieces of chocolate candy from the conveyor belt and outputs wrapped candies ready to bag and ship. +回到糖果工厂的装配线:第一台机器将融化的巧克力作为“输入”,它的“输出”是一块成形并冷却的巧克力。下一台机器沿着装配线向下一点,它的“输入”是一块巧克力,“输出”是一块切碎的巧克力糖。接下来,生产线上的一台机器从传送带上取下小块巧克力糖,并输出包装好的糖果,准备打包运输。 -The candy factory is fairly successful with this process, but as with all businesses, management keeps searching for ways to grow. +糖果工厂在这一过程中相当成功,但与所有企业一样,管理层一直在寻找增长的途径。 -To keep up with demand for more candy production, they decide to take out the conveyor belt contraption and just stack all three machines on top of one another, so that the output valve of one is connected directly to the input valve of the one below it. There's no longer sprawling wasted space where a chunk of chocolate slowly and noisily rumbles down a conveyor belt from the first machine to the second. +为了满足更多糖果生产的需求,他们决定拿出传送带装置,把三台机器一台台叠起来,其中一台的输出阀直接连接到下一台的输入阀上。再也不会有大块巧克力在传送带上缓慢而嘈杂地从一台机器传到另一台机器的浪费空间了。 -This innovation saves a lot of room on the factory floor, so management is happy they'll get to make more candy each day! +这一创新为工厂节省了很多空间,所以管理层很高兴他们能每天生产更多的糖果! -The code equivalent of this improved candy factory configuration is to skip the intermediate step (the `wordsFound` variable in the earlier snippet), and just use the two function calls together: +与此改进的糖果生产配置等价的代码是跳过中间步骤(前面代码片段中的`wordsFound`变量),只使用两个函数调用: ```js var wordsUsed = unique( words( text ) ); ``` -**Note:** Though we typically read the function calls left-to-right -- `unique(..)` and then `words(..)` -- the order of operations will actually be more right-to-left, or inner-to-outer. `words(..)` will run first and then `unique(..)`. Later we'll talk about a pattern that matches the order of execution to our natural left-to-right reading, called `pipe(..)`. +**注意:**虽然我们通常从左到右读取函数调用`unique(..)`和`words(..)`,但是操作的顺序实际上会从右到左,或者从内到外。首先运行`words(..)`,然后运行`unique(..)`。稍后,我们将讨论一种模式,它将执行顺序与从左到右的自然读取匹配,称为`pipe(..)`。 -The stacked machines are working fine, but it's kind of clunky to have the wires hanging out all over the place. The more of these machine-stacks they create, the more cluttered the factory floor gets. And the effort to assemble and maintain all these machine stacks is awfully time intensive. +堆叠的机器工作得很好,但是到处都挂着电线有点笨重。他们创建的机器栈越多,工厂的地板就越杂乱。组装和维护所有这些机器堆栈的工作非常耗时。 -One morning, an engineer at the candy factory has a great idea. She figures that it'd be much more efficient if she made an outer box to hide all the wires; on the inside, all three of the machines are hooked up together, and on the outside everything is now neat and tidy. On the top of this fancy new machine is a valve to pour in melted chocolate and on the bottom is a valve that spits out wrapped chocolate candies. Brilliant! +一天早上,糖果厂的一位工程师有了一个好主意。她认为如果她做一个外部盒子来隐藏所有的电线会更有效率;在内部,所有的三个机器都连接在一起,而在外部,一切都是干净整洁的。在这台新机器的顶部是一个阀门,可以将融化的巧克力倒入机器,底部是一个阀门,可以吐出包装好的巧克力糖果。这样太棒了! -This single compound machine is much easier to move around and install wherever the factory needs it. The workers on the factory floor are even happier because they don't need to fidget with buttons and dials on three individual machines anymore; they quickly prefer using the single fancy machine. +这台单台复合机器更容易移动和安装在工厂需要的任何地方。工厂里的工人们更开心,因为他们不再需要摆弄三台机器上的按钮和刻度盘;他们很快就喜欢用单台花哨的机器。 -Relating back to the code: we now realize that the pairing of `words(..)` and `unique(..)` in that specific order of execution (think: compound Lego) is something we could use in several other parts of our application. So, let's define a compound function that combines them: +与代码相关:我们现在意识到`words(..)` 和 `unique(..)`以特定的执行顺序(想想:复合乐高)的配对可以在应用程序的其他几个部分中使用。所以,让我们定义一个复合函数来组合它们: ```js function uniqueWords(str) { From cd74f619b12c95050d191826a7bb22889e68a48c Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 3 Sep 2019 09:10:09 +0800 Subject: [PATCH 53/63] Update ch4.md --- manuscript/ch4.md | 50 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/manuscript/ch4.md b/manuscript/ch4.md index 49ec922f..5503724f 100644 --- a/manuscript/ch4.md +++ b/manuscript/ch4.md @@ -114,27 +114,27 @@ function uniqueWords(str) { } ``` -`uniqueWords(..)` takes a string and returns an array. It's a composition of the two functions: `unique(..)` and `words(..)`; it creates this flow of data: +`uniqueWords(..)` 接受一个字符串并返回一个数组。它由两个函数组成:`unique(..)` 和 `words(..)`;它创建了这个数据流: ```txt wordsUsed <-- unique <-- words <-- text ``` -You probably recognize it by now: the unfolding revolution in candy factory design is function composition. +您现在可能已经认识到:糖果工厂设计中正在展开的革命是功能组合。 -### Machine Making +### 机器制造 -The candy factory is humming along nicely, and thanks to all the saved space, they now have plenty of room to try out making new kinds of candies. Building on the earlier success, management is keen to keep inventing new fancy compound machines for their growing candy assortment. +糖果厂生意兴隆,由于节省了不少空间,他们现在有足够的空间来尝试制造新型糖果。在早期成功的基础上,管理层热衷于不断为他们不断增长的糖果品种发明新的神奇的复合机器。 -But the factory engineers struggle to keep up, because each time a new kind of fancy compound machine needs to be made, they spend quite a bit of time making the new outer box and fitting the individual machines into it. +但是工厂的工程师们很难跟上步伐,因为每次需要制造一种新型的高级复合机器时,他们都要花费相当多的时间来制造新的外箱,并将各个机器安装到其中。 -So the factory engineers contact an industrial machine vendor for help. They're amazed to find out that this vendor offers a **machine-making** machine! As incredible as it sounds, they purchase a machine that can take a couple of the factory's smaller machines -- the chocolate cooling one and the cutting one, for example -- and wire them together automatically, even wrapping a nice clean bigger box around them. This is surely going to make the candy factory really take off! +因此,工厂的工程师联系一个工业机器供应商寻求帮助。他们惊奇地发现这个供应商提供**机器制造**机器!听起来不可思议的是,他们购买了一台机器,可以把工厂里的几台较小的机器——比如冷却巧克力的机器和切割巧克力的机器——自动连接起来,甚至用一个干净的大盒子把它们包起来。这一定会使糖果厂真正腾飞!

-Back to code land, let's consider a utility called `compose2(..)` that creates a composition of two functions automatically, exactly the same way we did manually: +回到代码领域,让我们考虑一个名为`compose2(..)`的实用程序,它自动创建两个函数的组合,与我们手动创建的方法完全相同: ```js function compose2(fn2,fn1) { @@ -143,30 +143,30 @@ function compose2(fn2,fn1) { }; } -// or the ES6 => form +// ES6 箭头格式 var compose2 = (fn2,fn1) => origValue => fn2( fn1( origValue ) ); ``` -Did you notice that we defined the parameter order as `fn2,fn1`, and furthermore that it's the second function listed (aka `fn1` parameter name) that runs first, then the first function listed (`fn2`)? In other words, the functions compose from right-to-left. +您是否注意到,我们将参数顺序定义为`fn2,fn1`,而且它是第一个运行的第二个列出的函数(又名`fn1`参数名),然后是第一个列出的函数(`fn2`)?换句话说,函数从右到左组成。 -That may seem like a strange choice, but there are some reasons for it. Most typical FP libraries define their `compose(..)` to work right-to-left in terms of ordering, so we're sticking with that convention. +这似乎是一个奇怪的选择,但有一些原因。大多数典型的FP库都将它们的`compose(..)`定义为按顺序从右向左工作,所以我们坚持这个约定。 -But why? I think the easiest explanation (but perhaps not the most historically accurate) is that we're listing them to match the order they are written in code manually, or rather the order we encounter them when reading from left-to-right. +但是为什么呢?我认为最简单的解释(但可能不是历史上最准确的解释)是,我们列出它们是为了匹配手工编写代码的顺序,或者更确切地说,是从左到右阅读时遇到的顺序。 -`unique(words(str))` lists the functions in the left-to-right order `unique, words`, so we make our `compose2(..)` utility accept them in that order, too. The execution order is right-to-left, but the code order is left-to-right. Pay close attention to keep those distinct in your mind. +`unique(words(str))` 以从左到右的顺序列出函数`unique, words`,因此我们让我们的`compose2(..)`实用程序也按这个顺序接收它们。执行顺序是从右到左,但是代码顺序是从左到右。密切关注那些在你脑海中清晰的东西。 -Now, the more efficient definition of the candy making machine is: +现在,对糖果机更有效的定义是: ```js var uniqueWords = compose2( unique, words ); ``` -### Composition Variation +### 成分变异 -It may seem like the `<-- unique <-- words` combination is the only order these two functions can be composed. But we could actually compose them in the opposite order to create a utility with a bit of a different purpose: +看起来`<-- unique <-- words`组合是这两个函数可以组合的惟一顺序。但实际上我们可以把它们按相反的顺序组合在一起,从而创建一个具有不同用途的实用程序: ```js var letters = compose2( words, unique ); @@ -176,15 +176,15 @@ chars; // ["h","o","w","a","r","e","y","u","n"] ``` -This works because the `words(..)` utility, for value-type safety sake, first coerces its input to a string using `String(..)`. So the array that `unique(..)` returns -- now the input to `words(..)` -- becomes the string `"H,o,w, ,a,r,e,y,u,n,?"`, and then the rest of the behavior in `words(..)` processes that string into the `chars` array. +这是因为`words(..)` 实用程序出于值类型安全的考虑,首先使用`String(..)`将其输入强制转换为字符串。因此,`unique(..)`返回的数组——现在是`words(..)`的输入——变成了字符串`"H,o,w, ,a,r,e,y,u,n,?"`,然后`words(..)`中的其他行为将处理该字符串到 `chars` 数组中。 -Admittedly, this is a contrived example. But the point is that function compositions are not always unidirectional. Sometimes we put the gray brick on top of the blue brick, and sometimes we put the blue brick on top. +诚然,这是一个人为的例子。但重点是函数组合并不总是单向的。有时我们把灰砖放在蓝砖的上面,有时我们把蓝砖放在上面。 -The candy factory better be careful if they try to feed the wrapped candies into the machine that mixes and cools the chocolate! +糖果工厂最好小心,如果他们试图把包装好的糖果放进机器,混合和冷却巧克力! -## General Composition +## 一般组成 -If we can define the composition of two functions, we can just keep going to support composing any number of functions. The general data visualization flow for any number of functions being composed looks like this: +如果我们能定义两个函数的组合,我们就可以继续支持组合任意数量的函数。组成的任意数量函数的常规数据可视化流如下所示: ```txt finalValue <-- func1 <-- func2 <-- ... <-- funcN <-- origValue @@ -194,9 +194,9 @@ finalValue <-- func1 <-- func2 <-- ... <-- funcN <-- origValue

-Now the candy factory owns the best machine of all: a machine that can take any number of separate smaller machines and spit out a big fancy machine that does every step in order. That's one heck of a candy operation! It's Willy Wonka's dream! +现在糖果厂拥有所有机器中最好的一台:这台机器可以取任意数量的独立的小机器,然后吐出一台大而别致的机器,按顺序执行每一步。这可真是个糖果店啊!这是威利·旺卡的梦想! -We can implement a general `compose(..)` utility like this: +我们可以实现一个通用的 `compose(..)` 实用程序如下: @@ -216,7 +216,7 @@ function compose(...fns) { }; } -// or the ES6 => form +// ES6 箭头格式 var compose = (...fns) => result => { @@ -232,9 +232,9 @@ var compose = }; ``` -**Warning:** `fns` is a collected array of arguments, not a passed-in array, and as such, it's local to `compose(..)`. It may be tempting to think the `[...fns]` would thus be unnecessary. However, in this particular implementation, `.pop()` inside the inner `composed(..)` function is mutating the list, so if we didn't make a copy each time, the returned composed function could only be used reliably once. We'll revisit this hazard in [Chapter 6](ch6.md/#user-content-hiddenmutation). +**警告:**`fns` 是一个集合的参数数组,不是传入的数组,因此 `compose(..)`是本地的。可能会有人认为 `[...fns]` 是不必要的。但是,在这个特定的实现中,内部的`composed(..)`函数中的`.pop()`正在改变列表,因此如果我们每次都不进行复制,则返回的 `composed(..)` 函数只能可靠地使用一次。我们将在[Chapter 6](ch6.md/#user-content-hiddenmutation)中重新讨论此危害。 -Now let's look at an example of composing more than two functions. Recalling our `uniqueWords(..)` composition example, let's add a `skipShortWords(..)` to the mix: +现在我们来看一个组成两个以上函数的例子。回顾我们的`uniqueWords(..)` 组合示例,让我们在组合中添加一个`skipShortWords(..)`: ```js function skipShortWords(words) { From 423a27f760a8edf0179d8b1cc1a2b6fc0d951670 Mon Sep 17 00:00:00 2001 From: Siming Date: Tue, 10 Sep 2019 09:36:22 +0800 Subject: [PATCH 54/63] Update ch4.md --- manuscript/ch4.md | 82 +++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/manuscript/ch4.md b/manuscript/ch4.md index 5503724f..9485a6b3 100644 --- a/manuscript/ch4.md +++ b/manuscript/ch4.md @@ -250,7 +250,7 @@ function skipShortWords(words) { } ``` -Let's define `biggerWords(..)` that includes `skipShortWords(..)`. The manual composition equivalent is `skipShortWords( unique( words( text ) ) )`, so let's do that with `compose(..)`: +让我们定义 `biggerWords(..)`,其中包括`skipShortWords(..)`。手动合成相当于`skipShortWords( unique( words( text ) ) )`,所以让我们用`compose(..)`来实现: ```js var text = "To compose two functions together, pass the \ @@ -266,13 +266,13 @@ wordsUsed; // "function","input","second"] ``` -To do something more interesting with composition, let's use [`partialRight(..)`, which we first looked at in Chapter 3](ch3.md/#user-content-partialright). We can build a right-partial application of `compose(..)` itself, pre-specifying the second and third arguments (`unique(..)` and `words(..)`, respectively); we'll call it `filterWords(..)`. +要对组合做一些更有趣的事情,让我们使用这是我们在第3章中首先看到的[`partialRight(..)`](ch3.md/#user-content-partialright)。我们可以构建一个`compose(..)`本身的函数式编程中局部应用,预先指定第二个和第三个参数(`unique(..)` 和 `words(..)`;我们将其称为`filterWords(..)`。 -Then, we can complete the composition multiple times by calling `filterWords(..)`, but with different first-arguments respectively: +然后,我们可以通过调用`filterWords(..)`多次完成合成,但分别使用不同的第一个参数: ```js -// Note: uses a `<= 4` check instead of the `> 4` check -// that `skipShortWords(..)` uses +// 注意:使用`<=4'检查而不是`>4'检查 +// `skipShortWords(..)` 使用 function skipLongWords(list) { /* .. */ } var filterWords = partialRight( compose, unique, words ); @@ -288,23 +288,23 @@ shorterWords( text ); // ["to","two","pass","the","of","call","as"] ``` -Take a moment to consider what the right-partial application on `compose(..)` gives us. It allows us to specify ahead of time the first step(s) of a composition, and then create specialized variations of that composition with different subsequent steps (`biggerWords(..)` and `shorterWords(..)`). This is one of the most powerful tricks of FP! +花点时间考虑一下`compose(..)`上正确的部分应用程序给了我们什么。它允许我们提前指定构图的第一步,然后用不同的后续步骤(`biggerWords(..)`和`shorterWords(..)`)创建该构图的特殊变体。这是FP最强大的技巧之一! -You can also `curry(..)` a composition instead of partial application, though because of right-to-left ordering, you might more often want to `curry( reverseArgs(compose), ..)` rather than just `curry( compose, ..)` itself. +您也可以`curry(..)`组合而不是部分应用程序,但是由于从右到左排序,您可能更经常希望`curry( reverseArgs(compose), ..)`而不是仅仅`curry( compose, ..)`本身。 -**Note:** Because `curry(..)` (at least [the way we implemented it in Chapter 3](ch3.md/#user-content-curry)) relies on either detecting the arity (`length`) or having it manually specified, and `compose(..)` is a variadic function, you'll need to manually specify the intended arity like `curry(.. , 3)`. +**注意:**因为`curry(..)` (至少[我们在第3章中实现它的方式](ch3.md/#user-content-curry))依赖于检测元数(`length`)或手动指定它,并且`compose(..)` 是一个可变函数,所以需要手动指定预期的元素,如 `curry(.. , 3)`。 -### Alternative Implementations +### 替代实施 -While you may very well never implement your own `compose(..)` to use in production, and rather just use a library's implementation as provided, I've found that understanding how it works under the covers actually helps solidify general FP concepts very well. +虽然您可能永远不会实现自己的`compose(..)`以在生产中使用,而只是按照提供的方式使用库的实现,但我发现理解它在封面下的工作方式实际上有助于很好地巩固一般的fp概念。 -So let's examine some different implementation options for `compose(..)`. We'll also see there are some pros/cons to each implementation, especially performance. +因此,让我们检查一下`compose(..)`的一些不同实现选项。我们还将看到每个实现都有一些优缺点,特别是性能。 -We'll be looking at the [`reduce(..)` utility in detail in Chapter 9](ch9.md/#reduce), but for now, just know that it reduces a list (array) to a single finite value. It's like a fancy loop. +我们将在第9章中详细研究[`reduce(..)`](ch9.md/#reduce),但现在只需知道它将一个列表(数组)缩减为一个有限值。就像一个奇特的环。 -For example, if you did an addition-reduction across a list of numbers (such as `[1,2,3,4,5,6]`), you'd loop over them adding them together as you go. The reduction would add `1` to `2`, and add that result to `3`, and then add that result to `4`, and so on, resulting in the final summation: `21`. +例如,如果对一个数字列表(例如`[1,2,3,4,5,6]`)进行加法归约,则会在进行加法运算时循环它们。减法将把`1` 加到`2`,把结果加到`3`,然后把结果加到`4`,依此类推,最终得到`21`。 -The original version of `compose(..)` uses a loop and eagerly (aka, immediately) calculates the result of one call to pass into the next call. This is a reduction of a list of functions, so we can do that same thing with `reduce(..)`: +`compose(..)`的原始版本使用循环并立即计算要传递给下一个调用的作为一个调用的参数。这是一个函数列表的缩减,所以我们可以用`reduce(..)`做同样的事情: @@ -317,7 +317,7 @@ function compose(...fns) { }; } -// or the ES6 => form +// ES6 箭头格式 var compose = (...fns) => result => [...fns].reverse().reduce( @@ -327,15 +327,15 @@ var compose = (...fns) => ); ``` -**Note:** This implementation of `compose(..)` uses `[...fns].reverse().reduce(..)` to reduce from right-to-left. We'll [revisit `compose(..)` in Chapter 9](ch9.md/#user-content-composereduceright), instead using `reduceRight(..)` for that purpose. +**注意:**`compose(..)`的这个实现使用`[...fns].reverse().reduce(..)` 来从右到左减少。我们将在[第9章重温 `compose(..)`](ch9.md/#user-content-composereduceright)中使用`reduceRight(..)`代替。 -Notice that the `reduce(..)` looping happens each time the final `composed(..)` function is run, and that each intermediate `result(..)` is passed along to the next iteration as the input to the next call. +注意,`reduce(..)` 循环在每次运行最终的`composed(..)`函数时发生,并且每个中间的 `result(..)` 作为下一个调用的输入传递给下一个迭代。 -The advantage of this implementation is that the code is more concise and also that it uses a well-known FP construct: `reduce(..)`. And the performance of this implementation is also similar to the original `for`-loop version. +这种实现的优点是代码更加简洁,并且使用了一个众所周知的函数编程结构:`reduce(..)`。这个实现的性能也类似于最初的for循环版本。 -However, this implementation is limited in that the outer composed function (aka, the first function in the composition) can only receive a single argument. Most other implementations pass along all arguments to that first call. If every function in the composition is unary, this is no big deal. But if you need to pass multiple arguments to that first call, you'd want a different implementation. +但是,这种实现受到限制,因为外部组合函数(也就是组合中的第一个函数)只能接收单个参数。大多数其他实现都将所有参数传递给第一个调用。如果合成中的每个函数都是一元的,这没什么大不了的。但是,如果需要向第一个调用传递多个参数,则需要不同的实现。 -To fix that first call single-argument limitation, we can still use `reduce(..)` but produce a lazy-evaluation function wrapping: +为了修正第一次调用的单参数限制,我们仍然可以使用`reduce(..)`,但是会产生一个延迟计算函数包装: ```js function compose(...fns) { @@ -346,7 +346,7 @@ function compose(...fns) { } ); } -// or the ES6 => form +// ES6 箭头格式 var compose = (...fns) => fns.reverse().reduce( (fn1,fn2) => @@ -355,29 +355,29 @@ var compose = ); ``` -Notice that we return the result of the `reduce(..)` call directly, which is itself a function, not a computed result. *That* function lets us pass in as many arguments as we want, passing them all down the line to the first function call in the composition, then bubbling up each result through each subsequent call. +注意,我们直接返回`reduce(..)`调用的结果,它本身是一个函数,而不是一个计算结果。*那个*函数允许我们传入尽可能多的参数,将它们全部传递到组合中的第一个函数调用,然后在每个后续调用中弹出每个结果。 -Instead of calculating the running result and passing it along as the `reduce(..)` looping proceeds, this implementation runs the `reduce(..)` looping **once** up front at composition time, and defers all the function call calculations -- referred to as lazy calculation. Each partial result of the reduction is a successively more wrapped function. +与计算运行结果并将其作为`reduce(..)`循环进行传递不同,此实现在组合时预先运行一次 `reduce(..)`循环,并延迟所有函数调用计算——称为延迟计算。还原的每个部分结果都是一个依次封装的函数。 -When you call the final composed function and provide one or more arguments, all the levels of the big nested function, from the inner most call to the outer, are executed in reverse succession (not via a loop). +当您调用最终的组合函数并提供一个或多个参数时,大型嵌套函数的所有级别,从最内部的调用到最外部的调用,都是反向连续执行的(不是通过循环)。 -The performance characteristics will potentially be different than in the previous `reduce(..)`-based implementation. Here, `reduce(..)` only runs once to produce a big composed function, and then this composed function call simply executes all its nested functions each call. In the former version, `reduce(..)` would be run for every call. +性能特征可能与以前基于 `reduce(..)`的实现不同。在这里, `reduce(..)`只运行一次,以生成一个大型复合函数,然后这个复合函数调用简单地执行它的所有嵌套函数的每个调用。在前一个版本中,每次调用都会运行`reduce(..)` 。 -Your mileage may vary on which implementation is better, but keep in mind that this latter implementation isn't limited in argument count the way the former one is. +对于哪种实现更好,您的优势可能会有所不同,但是请记住,后一种实现并不像前一种实现那样在参数计数方面受到限制。 -We could also define `compose(..)` using recursion. The recursive definition for `compose(fn1,fn2, .. fnN)` would look like: +我们还可以使用递归定义`compose(..)`。`compose(fn1,fn2, .. fnN)`看起来像: ```txt compose( compose(fn1,fn2, .. fnN-1), fnN ); ``` -**Note:** We will cover recursion more fully in [Chapter 8](ch8.md), so if this approach seems confusing, don't worry for now. Or, go read that chapter then come back and re-read this note. :) +**注意:**我们将在[第8章](ch8.md)中更全面地讨论递归,所以如果这种方法看起来令人困惑,现在不要担心。或者,去读那一章,然后回来再读这篇笔记。:) -Here's how we implement `compose(..)` with recursion: +下面是我们如何用递归实现`compose(..)`: ```js function compose(...fns) { - // pull off the last two arguments + // 完成最后两个参数 var [ fn1, fn2, ...rest ] = fns.reverse(); var composedFn = function composed(...args){ @@ -389,7 +389,7 @@ function compose(...fns) { return compose( ...rest.reverse(), composedFn ); } -// or the ES6 => form +// ES6 箭头格式 var compose = (...fns) => { // pull off the last two arguments @@ -405,19 +405,19 @@ var compose = }; ``` -I think the benefit of a recursive implementation is mostly conceptual. I personally find it much easier to think about a repetitive action in recursive terms instead of in a loop where I have to track the running result, so I prefer the code to express it that way. +我认为递归实现的好处主要是概念性的。我个人发现,用递归的方式考虑重复操作要比在循环中跟踪运行的结果容易得多,所以我更喜欢代码以这种方式来表示它。 -Others will find the recursive approach quite a bit more daunting to mentally juggle. I invite you to make your own evaluations. +另一些人对递归方法更加气馁,把握度会没有那么强。我希望你能做好判断。 -## Reordered Composition +## 重新排序组成 -We talked earlier about the right-to-left ordering of standard `compose(..)` implementations. The advantage is in listing the arguments (functions) in the same order they'd appear if doing the composition manually. +我们在前面讨论了标准的`compose(..)`实现了从右到左的顺序。这样做的好处是,以与手动组合时相同的顺序列出参数。 -The disadvantage is they're listed in the reverse order that they execute, which could be confusing. It was also more awkward to have to use `partialRight(compose, ..)` to pre-specify the *first* function(s) to execute in the composition. +缺点是它们是以执行的相反顺序列出的,这可能会让人感到困惑。使用`partialRight(compose, ..)`来预先指定要在组合中执行*第一个*函数也更尴尬。 -The reverse ordering, composing from left-to-right, has a common name: `pipe(..)`. This name is said to come from Unix/Linux land, where multiple programs are strung together by "pipe"ing (`|` operator) the output of the first one in as the input of the second, and so on (i.e., `ls -la | grep "foo" | less`). +相反的顺序,从左到右组合,有一个共同的名称:`pipe(..)`。这个名称据说来自Unix/Linux领域,其中多个程序通过(' | '操作符)将第一个的输出作为第二个的输入串在一起,以此类推(即, `ls -la | grep "foo" | less`)。 -`pipe(..)` is identical to `compose(..)` except it processes through the list of functions in left-to-right order: +`pipe(..)`与`compose(..)`是相同的,只是它按照从左到右的顺序处理函数列表: ```js function pipe(...fns) { @@ -425,8 +425,8 @@ function pipe(...fns) { var list = [...fns]; while (list.length > 0) { - // take the first function from the list - // and execute it + // 从列表中获取第一个函数 + // 然后执行 result = list.shift()( result ); } @@ -435,7 +435,7 @@ function pipe(...fns) { } ``` -In fact, we could just define `pipe(..)` as the arguments-reversal of `compose(..)`: +实际上,我们可以将`pipe(..)`定义为`compose(..)`的参数反转: ```js var pipe = reverseArgs( compose ); From a8d14800747e48e6ff79e4379744048df724fe3f Mon Sep 17 00:00:00 2001 From: Siming Date: Wed, 11 Sep 2019 09:26:10 +0800 Subject: [PATCH 55/63] Update preface.md --- manuscript/preface.md | 50 +++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/manuscript/preface.md b/manuscript/preface.md index b7fe6185..274c623c 100644 --- a/manuscript/preface.md +++ b/manuscript/preface.md @@ -1,48 +1,42 @@ -# Functional-Light JavaScript -# Preface +# JavaScript轻量级函数式编程 +# 序言 -> *A monad is just a monoid in the category of endofunctors.* +> *单子只是内函子范畴中的一个单子。* -Did I just lose you? Don't worry, I'd be lost, too! All those terms that only mean something to the already-initiated in Functional Programming™ (FP) are just jumbled nonsense to many of the rest of us. +我刚刚失去你了吗?别担心,我也会迷路的!所有那些只对函数式编程中已经开始的术语有意义,对我们其他人来说都是胡说八道。 -This book is not going to teach you what those words mean. If that's what you're looking for, keep looking. In fact, there are already plenty of great books that teach FP the *right way*, from the top-down. Those words have important meanings and if you formally study FP in-depth, you'll absolutely want to get familiar with them. +这本书不会教你这些词的意思。如果这就是你要找的,那就继续找。事实上,已经有很多伟大的书籍从上到下教授FP“正确的方法”。这些词有重要的意义,如果你深入学习FP,你一定会想要熟悉它们。 -But this book is going to approach the topic quite differently. I'm going to present fundamental FP concepts from the ground-up, with fewer special or non-intuitive terms than most approaches to FP. We'll try to take a practical approach to each principle rather than a purely academic angle. **There will be terms**, no doubt. But we'll be careful and deliberate about introducing them and explaining why they're important. +但这本书将以完全不同的方式探讨这个问题。我将用比大多数FP方法更少的特殊或非直观的术语,从头开始介绍基本的FP概念。我们将试着对每一个原则都采取实际的方法,而不是单纯从学术角度。毫无疑问,会有条款的。但我们会小心谨慎地介绍它们并解释它们为什么重要。 -Sadly, I am not a card-carrying member of the FP Cool Kids Club. I've never been formally taught anything about FP. And though I have a CS academic background and I am decent at math, mathematical notation is not how my brain understands programming. I have never written a line of Scheme, Clojure, or Haskell. I'm not an old-school Lisp'r. +遗憾的是,我不是FP Cool Kids Club的会员。我从来没有正式学过FP。虽然我有计算机科学的学术背景,我的数学也不错,但数学符号并不是我大脑理解编程的方式。我从来没有写过一行Scheme、Clojure或Haskell。我不是一个老派的口齿不清的人。 -I *have* attended countless conference talks about FP, each one with the desperate clinging hope that finally, *this time* would be the time I understood what this whole functional programming mysticism is all about. And each time, I came away frustrated and reminded that those terms got all mixed up in my head and I had no idea if or what I learned. Maybe I learned things. But I couldn't figure out what those things were for the longest time. +我*参加过*无数次关于FP的会议,每一次都带着绝望的执着希望,最终,*这次*将是我理解整个函数式编程神秘主义的时候。每一次,当我沮丧地离开的时候,我都提醒自己那些术语都混在了我的脑海里,我不知道自己是否学到了什么,也不知道自己学到了什么。也许我学到了一些东西。但在很长一段时间里,我都不知道这些东西是什么。 -Little by little, across those various exposures, I teased out bits and pieces of important concepts that seem to just come all too naturally to the formal FPer. I learned them slowly and I learned them pragmatically and experientially, not academically with appropriate terminology. Have you ever known a thing for a long time, and only later found out it had a specific name you never knew!? +渐渐地,通过这些不同的曝光,我梳理出了一些重要的概念,这些概念对于正式的函数式编程人员来说似乎太自然了。我学得很慢,而且是用实际和经验学的,而不是用专业术语学的。你是否知道一件事很久了,后来才发现它有一个你从来不知道的名字! -Maybe you're like me; I heard terms such as "map-reduce" around industry segments like "big data" for years with no real idea what they were. Eventually I learned what the `map(..)` function did -- all long before I had any idea that list operations were a cornerstone of the FPer path and what makes them so important. I knew what *map* was long before I ever knew it was called `map(..)`. +也许你和我一样;多年来,我在诸如“大数据”之类的行业领域里听到过"map-reduce"之类的术语,但我并不知道它们是什么。最终,我了解了`map(..)`函数的作用——这一切都发生在我意识到列表操作是函数编程人员路径的基石以及是什么让它们如此重要之前。早在我知道`map(..)`之前,我就知道`map(..)`是什么。 -Eventually I began to gather all these tidbits of understanding into what I now call "Functional-Light Programming" (FLP). +最终,我开始将所有这些零碎的理解汇集到我现在称为“轻量级函数式编程”的东西中。 -## Mission +## 任务 -But why is it so important for you to learn functional programming, even the light form? +但是为什么学习函数式编程如此重要,即使是简单的形式? -I've come to believe something very deeply in recent years, so much so you could *almost* call it a religious belief. I believe that programming is fundamentally about humans, not about code. I believe that code is first and foremost a means of human communication, and only as a *side effect* (hear my self-referential chuckle) does it instruct the computer. +近年来,我开始深深地相信一些东西,以至于你几乎可以把它称为一种宗教信仰。我相信编程本质上是关于人类的,而不是代码。我相信代码首先是人类交流的一种方式,它只是作为另一种方式来指导计算机。 -The way I see it, functional programming is at its heart about using patterns in your code that are well-known, understandable, *and* proven to keep away the mistakes that make code harder to understand. In that view, FP -- or, ahem, FLP! -- might be one of the most important collections of tools any developer could acquire. +在我看来,函数式编程的核心是在代码中使用众所周知的、可理解的,也已被证明可以避免使代码更难理解的错误的模式。从这个角度看,函数编程——或者是JavaScript轻量级函数式编程,可能是任何开发人员都能获得的最重要的工具集合之一。 -> The curse of the monad is that... once you understand... you lose the ability to explain it to anyone else. -> -> Douglas Crockford 2012 "Monads and Gonads" -> -> https://www.youtube.com/watch?v=dkZFtimgAcM +我希望这本书“也许”打破了术语诅咒,直到最后的附录我们都不会谈论“monads”这样难懂的术语,。 -I hope this book "Maybe" breaks the spirit of that curse, even though we won't talk about "monads" until the very end in the appendices. +正式的函数编程开发人员通常会断言函数编程的“真正价值”在于100%地使用它:它是一个全有或全无的命题。他们认为,如果你在项目的某一部分使用FP,而在另一部分不使用,那么整个项目就会被非FP的东西污染,因此遭受的痛苦就足够了,以至于FP可能不值得。 -The formal FPer will often assert that the *real value* of FP is in using it essentially 100%: it's an all-or-nothing proposition. The belief is that if you use FP in one part of your program but not in another, the whole program is polluted by the non-FP stuff and therefore suffers enough that the FP was probably not worth it. +我将毫不含糊地说:**我认为绝对主义是虚假的**。对我来说,这就像说这本书只有在我自始至终使用完美语法和主动语态的情况下才算好一样愚蠢;如果我犯了任何错误,就会降低整本书的质量。无稽之谈。 -I'll say unequivocally: **I think that absolutism is bogus**. That's as silly to me as suggesting that this book is only good if I use perfect grammar and active voice throughout; if I make any mistakes, it degrades the entire book's quality. Nonsense. +我写得越清楚、连贯,你的阅读体验就会越好。但我不是一个100%完美的作家。有些部分会写得更好。我还可以改进的部分不会使这本书中其他有用的部分失效。 -The better I am at writing in a clear, consistent voice, the better your reading experience will be. But I'm not a 100% perfect author. Some parts will be better written than others. The parts where I can still improve are not going to invalidate the other parts of this book which are useful. +这和我们的代码是一致的。将这些原则应用到代码的更多部分越多,代码就会越好。25%的时间好好利用它们,你会得到一些好处。80%的时间使用它们,你会看到更多的好处。 -And so it goes with our code. The more you can apply these principles to more parts of your code, the better your code will be. Use them well 25% of the time, and you'll get some good benefit. Use them 80% of the time, and you'll see even more benefit. +除了少数例外,我认为您不会在本文中找到很多绝对词。相反,我们将谈论抱负、目标和奋斗的原则。我们将讨论函数的平衡、实用主义和权衡。 -With perhaps a few exceptions, I don't think you'll find many absolutes in this text. We'll instead talk about aspirations, goals, principles to strive for. We'll talk about balance and pragmatism and trade-offs. - -Welcome to this journey into the most useful and practical foundations of FP. We both have plenty to learn! +欢迎来到这趟旅程,了解FP最有用和最实用的基础。我们都有很多东西要学! From 0cef4c40ebef76f407be85cd31058bea8d3a76dd Mon Sep 17 00:00:00 2001 From: Siming Date: Wed, 11 Sep 2019 09:27:21 +0800 Subject: [PATCH 56/63] Update foreword.md --- manuscript/foreword.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/manuscript/foreword.md b/manuscript/foreword.md index 1b7b7c9a..8b8a93fe 100644 --- a/manuscript/foreword.md +++ b/manuscript/foreword.md @@ -1,22 +1,22 @@ -# Functional-Light JavaScript -# Foreword +# JavaScript轻量级函数式编程 +# 序言 -It's no secret that I am a Functional Programming nut. I evangelize functional ideas and languages wherever I can, try to read the latest academic papers, study abstract algebra in my spare time…the works. Even in JavaScript, I refuse to write an impure statement, which is what led to writing *Professor Frisby's Mostly Adequate Guide to Functional Programming*. Yep, full on, dogmatic zealot. +众所周知,我是一个函数式编程迷。我尽可能地传播函数思想和语言,努力阅读最新的学术论文,在业余时间学习抽象代数……即使在JavaScript中,我也拒绝编写不纯的语句,这就是编写弗里斯比教授的《函数式编程指南》的原因。是的,十足的教条狂热者。 -I was not always this way… I was once obsessed with objects. I loved modeling the "real world". I was the inventor of synthetic automatons, tinkering through the night with masterful precision. The creator of sentient puppets, fingers dancing on the keyboard to give them life -- a real 1337 h4x0r Geppetto. Yet, after 5 *solid* years of writing object-oriented code, I was never quite satisfied with the outcome. It just never worked out well for me. I felt like a lousy programmer. I even lost faith that a simple, flexible codebase of decent scale was possible. +我并不总是这样…我曾经痴迷于物品。我喜欢做“真实世界”的模特。我是人造机器人的发明者,在夜间以高超的精确度修修补补。有知觉的木偶的创造者,手指在键盘上跳舞,赋予它们生命——一个真正的1337 h4x0r格培多。然而,在写了5年面向对象的代码之后,我从来没有对结果感到非常满意。这对我来说从来都不太好。我觉得自己是个差劲的程序员。我甚至不再相信一个简单、灵活的代码库是可能的。 -I figured I'd try something different: Functional Programming. I began to dabble with functional ideas in my everyday codebase, and much to my coworkers' dismay, hadn't the slightest clue what I was doing. The code I wrote in those days was awful. Atrocious. Digital sewage. The reason was a lack of clear vision or goal on what I was even trying to accomplish. My Jiminy-Coding-Cricket, if you like, was not there to guide me. It took a long time and a lot of garbage programs to figure out how to FP. +我想我会尝试一些不同的方法:函数式编程。我开始在日常的代码库中涉猎功能性的想法,令同事们非常沮丧的是,我一点也不知道自己在做什么。那时候我写的代码很糟糕。。原因是我对自己想要完成的事情缺乏清晰的愿景或目标。我是花了很长时间和很多垃圾程序才弄清楚如何进行函数式编程。 -Now, after all that messy exploration, I feel that pure Functional Programming has delivered on its promise. Readable programs do exist! Reuse does exist! I no longer invent, but rather discover my model. I've become a rogue detective uncovering a vast conspiracy, cork board pinned full of mathematical evidence. A digital-age Cousteau logging the characteristics of this bizarre land in the name of science! It's not perfect and I still have a lot to learn, but I've never been more satisfied in my work and pleased with the outcome. +现在,在所有这些混乱的探索之后,我觉得纯函数编程已经实现了它的承诺。可读程序确实存在!重用确实存在!我不再创造,而是发现我的模型。我变成了一个流氓侦探,揭露了一个巨大的阴谋,软木塞钉满了数学证据。一个数字时代的库斯托以科学的名义记录下这片奇异土地的特征!它并不完美,我还有很多要学,但我从来没有像现在这样对我的工作和结果感到满意。 -Had this book existed when I was starting out, my transition into the world of Functional Programming would have been much easier and less destructive. This book is two-fold (right and left): it will not only teach you how to use various constructs from FP effectively in your daily code, but more importantly, provide you with an aim; guiding principles that will keep you on track. +如果在我刚开始写这本书的时候就有这本书的话,我过渡到函数式编程的世界就会容易得多,破坏性也会小得多。这本书是双重的:它不仅会教你如何在日常代码中有效地使用函数式编程中的各种构造,更重要的是,它为你提供了一个目标;指导原则将保持你在正确的轨道上。 -You will learn Functional-Light: A paradigm that Kyle has pioneered to enable declarative, Functional Programming while providing balance and interop with the rest of the JavaScript world. You will understand the foundation which pure FP is built upon without having to subscribe to the paradigm in its entirety. You will gain the skills to practice and explore FP without having to rewrite existing code for it to work well together. You can take a step forward in your software career without backtracking and wandering aimlessly as I did years ago. Coworkers and colleagues rejoice! +您将学习 Kyle首创的一个轻量函数范例,它支持声明式、函数式编程,同时提供与JavaScript世界其他部分的平衡和互操作。您将理解纯函数编程所建立的基础,而不必完全遵循范式。您将获得实践和探索函数编程的技能,而不需要重写现有的代码来很好地协同工作。您可以在您的软件生涯中向前迈进一步,而不需要像我多年前那样倒退和漫无目的地徘徊。为此欢呼吧! -Kyle is a great teacher known for his relentless pursuit of the whole picture, leaving no nook or cranny unexplored, yet he maintains an empathy for the learner's plight. His style has resonated with the industry, leveling us all up as a whole. His work has a solid place in JavaScript’s history and most people's bookmarks bar. You are in good hands. +Kyle是一位伟大的老师,他以对整体的不懈追求而闻名,没有一个角落或缝隙是未经探索的,但他对学习者的困境保持着同理心。他的风格与整个行业产生了共鸣,使我们所有人都得到了提升。他的作品在JavaScript的历史和大多数人的书签栏中占有稳固的位置。对你来说是很好的帮助。 -Functional Programming has many different definitions. A Lisp programmer's definition is vastly different from a Haskell perspective. OCaml's FP bears little resemblance to the paradigm seen in Erlang. You will even find several competing definitions in JavaScript. Yet there is a tie that binds -- some blurry know-it-when-I-see-it definition, much like obscenity (indeed, some do find FP obscene!) and this book certainly captures it. The end result might not be considered idiomatic in certain circles, but the knowledge acquired here directly applies to any flavor of FP. +函数式编程有许多不同的定义。lisp程序员的定义与haskell的定义大不相同。OCaml(关于OCaml,最早称为Objective Caml,是Caml编程语言的主要实现,开发工具包含交互式顶层解释器,字节码编译器以及最优本地代码编译器)的函数编程定义与erlang(Erlang是一种通用的面向并发的编程语言)中的范例几乎没有相似之处。甚至可以在javascript中找到几个相互竞争的定义。然而,有一种联系——一些模糊的“当我看到它时就知道”的定义,很像说瞎话(事实上,有些人确实发现函数式编程在敷衍!)这本书确实抓住了这一点。在某些圈子里,最终的结果可能不被认为是惯用的,但这里获得的知识直接适用于任何形式的函数式编程。 -This book is a terrific place to begin your FP journey. Take it away, Kyle... +这本书是一个极好的地方开始你的函数式编程之旅。拿走,Kyle… *-Brian Lonsdorf (@drboolean)* From 52ad56ef03c18171390e9666c01c0776ea1ddfce Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 12 Sep 2019 09:41:11 +0800 Subject: [PATCH 57/63] Update apA.md --- manuscript/apA.md | 48 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/manuscript/apA.md b/manuscript/apA.md index 73f424fc..a31a2a33 100644 --- a/manuscript/apA.md +++ b/manuscript/apA.md @@ -1,21 +1,20 @@ -# Functional-Light JavaScript -# Appendix A: Transducing +# 附言 A: 转换 -Transducing is a more advanced technique than we've covered in this book. It extends many of the concepts from [Chapter 9](ch9.md) on list operations. +转换是一种比我们在本书中介绍的更为先进的技术。它扩展了[第9章](ch9.md)关于列表操作的许多概念。 -I wouldn't necessarily call this topic strictly "Functional-Light", but more like a bonus on top. I've presented this as an appendix because you might very well need to skip the discussion for now and come back to it once you feel fairly comfortable with -- and make sure you've practiced! -- the main book concepts. +我并不一定要严格地将这个主题称为“功能轻量级”,但更像是一个附加功能。我将此作为附录介绍,因为您很可能需要暂时跳过讨论,待您感到相当舒服时再回过头来讨论书籍主要概念。 -To be honest, even after teaching transducing many times, and writing this chapter, I am still trying to fully wrap my brain around this technique. So don't feel bad if it twists you up. Bookmark this appendix and come back when you're ready. +说实话,即使在教授了很多次关于转换的概念,并写了这一章之后,我仍然试图完全理解这种技术。所以,如果它使你陷入困境,不要感到难过。把这个附录标上书签,准备好了再来。 -Transducing means transforming with reduction. +转换是指还原转化。 -I know that may sound like a jumble of words that confuses more than it clarifies. But let's take a look at how powerful it can be. I actually think it's one of the best illustrations of what you can do once you grasp the principles of Functional-Light Programming. +我知道这听起来像是一堆乱七八糟的字眼,让人困惑不止。但让我们看看它有多强大。实际上,我认为这是一个最好的例证,说明一旦你掌握了函数轻编程的原理,你可以做你要做的事情。 -As with the rest of this book, my approach is to first explain *why*, then *how*, then finally boil it down to a simplified, repeatable *what*. That's often the reverse of how many teach, but I think you'll learn the topic more deeply this way. +和本书的其他部分一样,我的方法是先解释为什么,然后解释如何,最后将其归结为一个简化的、可重复的“什么”。这通常与教多少人相反,但我认为你会通过这种方式更深入地学习这个话题。 -## Why, First +## 首先弄清楚“为什么” -Let's start by extending a [scenario we covered back in Chapter 3](ch3.md/#user-content-shortlongenough), testing words to see if they're short enough and/or long enough: +让我们从扩展[我们在第3章中讨论过的场景](ch3.md/#user-content-shortlongenough)开始,测试单词是否足够短和/或足够长: ```js function isLongEnough(str) { @@ -26,8 +25,7 @@ function isShortEnough(str) { return str.length <= 10; } ``` - -In [Chapter 3, we used these predicate functions](ch3.md/#user-content-shortlongenough) to test a single word. Then in Chapter 9, we learned how to repeat such tests [using list operations like `filter(..)`](ch9.md/#filter). For example: +在[第3章,我们使用这些谓词函数](ch3.md/#user-content-shortlongenough)来测试单个单词。然后在第9章,我们学习了如何重复这样的测试[使用列表操作,如`filter(..)`](ch9.md/#filter)。例如: ```js var words = [ "You", "have", "written", "something", "very", @@ -39,13 +37,13 @@ words // ["written","something"] ``` -It may not be obvious, but this pattern of separate adjacent list operations has some non-ideal characteristics. When we're dealing with only a single array of a small number of values, everything is fine. But if there were lots of values in the array, each `filter(..)` processing the list separately can slow down a bit more than we'd like. +这可能不明显,但是这种分离相邻列表操作的模式具有一些不理想的特性。当我们只处理一个由少量值组成的数组时,一切正常。但是如果数组中有很多值,每个`filter(..)`单独处理列表的速度会比我们希望的慢一些。 -A similar performance problem arises when our arrays are async/lazy (aka Observables), processing values over time in response to events (see [Chapter 10](ch10.md)). In this scenario, only a single value comes down the event stream at a time, so processing that discrete value with two separate `filter(..)`s function calls isn't really such a big deal. +当我们的数组是异步/延迟的时,也会出现类似的性能问题,即随着时间的推移处理值以响应事件(参见[章节 10](ch10.md))。在这个场景中,一次只有一个值从事件流中下来,所以使用两个单独的`filter(..)`函数调用来处理这个不连续值并不是什么大问题。 -But what's not obvious is that each `filter(..)` method produces a separate observable. The overhead of pumping a value out of one observable into another can really add up. That's especially true since in these cases, it's not uncommon for thousands or millions of values to be processed; even such small overhead costs add up quickly. +但不明显的是,每个`filter(..)`方法都会产生一个单独的可观察值。将一个值从一个可观察对象注入到另一个可观察对象的开销确实会增加。这一点尤其正确,因为在这些情况下,处理成千上万的值并不罕见;即使如此小的日常开支也会迅速增加。 -The other downside is readability, especially when we need to repeat the same series of operations against multiple lists (or Observables). For example: +另一个缺点是可读性,特别是当我们需要对多个列表(或可观察对象)重复相同的系列操作时。例如: ```js zip( @@ -55,9 +53,9 @@ zip( ) ``` -Repetitive, right? +看起来重复了,是吧? -Wouldn't it be better (both for readability and performance) if we could combine the `isLongEnough(..)` predicate with the `isShortEnough(..)` predicate? You could do so manually: +如果我们能将`isLongEnough(..)`与`isShortEnough(..)`结合起来,岂不是更好(在可读性和性能方面)?你可以手动操作: ```js function isCorrectLength(str) { @@ -65,9 +63,9 @@ function isCorrectLength(str) { } ``` -But that's not the FP way! +但这不是函数式编程的方式! -In [Chapter 9, we talked about fusion](ch9.md/#fusion) -- composing adjacent mapping functions. Recall: +在[第9章,我们讨论了fusion](ch9.md/#fusion)——组合相邻的映射函数。回忆一下: ```js words @@ -76,15 +74,15 @@ words ); ``` -Unfortunately, combining adjacent predicate functions doesn't work as easily as combining adjacent mapping functions. To understand why, think about the "shape" of the predicate function -- a sort of academic way of describing the signature of inputs and output. It takes a single value in, and it returns a `true` or a `false`. +不幸的是,组合相邻的谓词函数不如组合相邻的映射函数那么容易。要理解原因,请考虑谓词函数的“形状”——一种描述输入和输出签名的学术方法。它接受一个值,并返回一个`true`或`false`。 -If you tried `isShortEnough(isLongEnough(str))`, it wouldn't work properly. `isLongEnough(..)` will return `true`/`false`, not the string value that `isShortEnough(..)` is expecting. Bummer. +如果你试了`isShortEnough(isLongEnough(str))`,它不会正常工作。`isLongEnough(..)`将返回`true`/`false`,而不是`isShortEnough(..)`期望的字符串值。结果有点让人失望。 -A similar frustration exists trying to compose two adjacent reducer functions. The "shape" of a reducer is a function that receives two values as input, and returns a single combined value. The output of a reducer as a single value is not suitable for input to another reducer expecting two inputs. +试图组合两个相邻的reducer函数也存在类似的问题。reducer的“shape”是一个函数,它接收两个值作为输入,并返回单个组合值。一个reducer的输出作为一个单独的值,不适合输入到另一个期望有两个输入的reducer函数。 -Moreover, the `reduce(..)` helper takes an optional `initialValue` input. Sometimes this can be omitted, but sometimes it has to be passed in. That even further complicates composition, since one reduction might need one `initialValue` and the other reduction might seem like it needs a different `initialValue`. How can we possibly do that if we only make one `reduce(..)` call with some sort of composed reducer? +此外,`reduce(..)`的helper函数接受一个可选的`initialValue`输入。有时这可以省略,但有时必须传入。这甚至使组合更加复杂,因为一个缩减可能需要一个`initialValue`,而另一个缩减可能需要一个不同的`initialValue`。如果我们只使用某种组合的reduce调用一个`reduce(..)`调用,我们怎么可能做到这一点呢? -Consider a chain like this: +考虑一下这样的链式: ```js words From 8ccb18bdf9cc6439f8341ff0394adac8ce56a80d Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 12 Sep 2019 09:42:19 +0800 Subject: [PATCH 58/63] Update README.md --- manuscript/README.md | 49 +++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/manuscript/README.md b/manuscript/README.md index 3f506721..412fc951 100644 --- a/manuscript/README.md +++ b/manuscript/README.md @@ -1,14 +1,14 @@ -# Functional-Light JavaScript +# JavaScript轻量级函数式编程 [![Buy on Leanpub](https://img.shields.io/badge/Buy-Leanpub-yellow.svg)](https://leanpub.com/fljs) [![Buy on Amazon](https://img.shields.io/badge/Buy-Amazon-yellow.svg)](http://amazon.fljsbook.com) -## Table of Contents +## 目录 -* [Foreword](foreword.md/#foreword) -* [Preface](preface.md/#preface) -* [Chapter 1: Why Functional Programming?](ch1.md/#chapter-1-why-functional-programming) +* [前言](foreword.md/#foreword) +* [序言](preface.md/#preface) +* [章节 1: 为什么要函数式编程?](ch1.md/#chapter-1-why-functional-programming) * [At a Glance](ch1.md/#at-a-glance) * [Confidence](ch1.md/#confidence) * [Communication](ch1.md/#communication) @@ -16,7 +16,7 @@ * [Perspective](ch1.md/#perspective) * [How to Find Balance](ch1.md/#how-to-find-balance) * [Resources](ch1.md/#resources) -* [Chapter 2: The Nature Of Functions](ch2.md/#chapter-2-the-nature-of-functions) +* [章节 2: 函数的本质](ch2.md/#chapter-2-the-nature-of-functions) * [What Is A Function?](ch2.md/#what-is-a-function) * [Function Input](ch2.md/#function-input) * [Named Arguments](ch2.md/#named-arguments) @@ -24,41 +24,41 @@ * [Functions Of Functions](ch2.md/#functions-of-functions) * [Syntax](ch2.md/#syntax) * [What's This?](ch2.md/#whats-this) -* [Chapter 3: Managing Function Inputs](ch3.md/#chapter-3-managing-function-inputs) +* [章节 3: 管理函数输入](ch3.md/#chapter-3-managing-function-inputs) * [All For One](ch3.md/#all-for-one) * [Adapting Arguments to Parameters](ch3.md/#adapting-arguments-to-parameters) * [Some Now, Some Later](ch3.md/#some-now-some-later) * [One At A Time](ch3.md/#one-at-a-time) * [Order Matters](ch3.md/#order-matters) * [No Points](ch3.md/#no-points) -* [Chapter 4: Composing Functions](ch4.md/#chapter-4-composing-functions) +* [章节 4: 组合函数](ch4.md/#chapter-4-composing-functions) * [Output To Input](ch4.md/#output-to-input) * [General Composition](ch4.md/#general-composition) * [Reordered Composition](ch4.md/#reordered-composition) * [Abstraction](ch4.md/#abstraction) * [Revisiting Points](ch4.md/#revisiting-points) -* [Chapter 5: Reducing Side Effects](ch5.md/#chapter-5-reducing-side-effects) +* [章节 5: 减少副作用影响](ch5.md/#chapter-5-reducing-side-effects) * [Effects On The Side, Please](ch5.md/#effects-on-the-side-please) * [Once Is Enough, Thanks](ch5.md/#once-is-enough-thanks) * [Pure Bliss](ch5.md/#pure-bliss) * [There Or Not](ch5.md/#there-or-not) * [Purifying](ch5.md/#purifying) -* [Chapter 6: Value Immutability](ch6.md/#chapter-6-value-immutability) +* [章节 6: 值的不变性质](ch6.md/#chapter-6-value-immutability) * [Primitive Immutability](ch6.md/#primitive-immutability) * [Value To Value](ch6.md/#value-to-value) * [Reassignment](ch6.md/#reassignment) * [Performance](ch6.md/#performance) * [Treatment](ch6.md/#treatment) -* [Chapter 7: Closure vs Object](ch7.md/#chapter-7-closure-vs-object) +* [章节 7: 闭包与对象](ch7.md/#chapter-7-closure-vs-object) * [The Same Page](ch7.md/#the-same-page) * [Look Alike](ch7.md/#look-alike) * [Two Roads Diverged In A Wood...](ch7.md/#two-roads-diverged-in-a-wood) -* [Chapter 8: Recursion](ch8.md/#chapter-8-recursion) +* [章节 8: 递归](ch8.md/#chapter-8-recursion) * [Definition](ch8.md/#definition) * [Declarative Recursion](ch8.md/#declarative-recursion) * [Stack](ch8.md/#stack) * [Rearranging Recursion](ch8.md/#rearranging-recursion) -* [Chapter 9: List Operations](ch9.md/#chapter-9-list-operations) +* [章节 9: 列表的操作](ch9.md/#chapter-9-list-operations) * [Non-FP List Processing](ch9.md/#non-fp-list-processing) * [Map](ch9.md/#map) * [Filter](ch9.md/#filter) @@ -68,28 +68,39 @@ * [Looking For Lists](ch9.md/#looking-for-lists) * [Fusion](ch9.md/#fusion) * [Beyond Lists](ch9.md/#beyond-lists) -* [Chapter 10: Functional Async](ch10.md/#chapter-10-functional-async) +* [章节 10: 函数的异步](ch10.md/#chapter-10-functional-async) * [Time As State](ch10.md/#time-as-state) * [Eager vs Lazy](ch10.md/#eager-vs-lazy) * [Reactive FP](ch10.md/#reactive-fp) -* [Chapter 11: Putting It All Together](ch11.md/#chapter-11-putting-it-all-together) +* [章节 11: 汇总](ch11.md/#chapter-11-putting-it-all-together) * [Setup](ch11.md/#setup) * [Stock Events](ch11.md/#stock-events) * [Stock Ticker UI](ch11.md/#stock-ticker-ui) -* [Appendix A: Transducing](apA.md/#appendix-a-transducing) - * [Why, First](apA.md/#why-first) +* [附言 A: 转换](apA.md/#appendix-a-transducing) + * [首先弄清楚“为什么”](apA.md/#why-first) * [How, Next](apA.md/#how-next) * [What, Finally](apA.md/#what-finally) -* [Appendix B: The Humble Monad](apB.md/#appendix-b-the-humble-monad) +* [附言 B: 卑微的单元](apB.md/#appendix-b-the-humble-monad) * [Type](apB.md/#type) * [Loose Interface](apB.md/#loose-interface) * [Just a Monad](apB.md/#just-a-monad) * [Maybe](apB.md/#maybe) * [Humble](apB.md/#humble) -* [Appendix C: FP Libraries](apC.md/#appendix-c-fp-libraries) +* [附言 C: 函数式编程的库](apC.md/#appendix-c-fp-libraries) * [Stuff to Investigate](apC.md/#stuff-to-investigate) * [Ramda](apC.md/#ramda-0230) * [Lodash/fp](apC.md/#lodashfp-4174) * [Mori](apC.md/#mori-032) * [Bonus: FPO](apC.md/#bonus-fpo) * [Bonus #2: fasy](apC.md/#bonus-2-fasy) + + + + + + + + + + + From 0cc9be1f99aad1b1ae99d012ea6a0b1556b19ea9 Mon Sep 17 00:00:00 2001 From: Siming Date: Thu, 12 Sep 2019 21:49:41 +0800 Subject: [PATCH 59/63] Update apA.md --- manuscript/apA.md | 201 +++++++++++++++++++++++----------------------- 1 file changed, 101 insertions(+), 100 deletions(-) diff --git a/manuscript/apA.md b/manuscript/apA.md index a31a2a33..5ce31e0b 100644 --- a/manuscript/apA.md +++ b/manuscript/apA.md @@ -92,22 +92,21 @@ words .reduce( strConcat, "" ); // "WRITTENSOMETHING" ``` +你能想象一个包含所有这些步骤的构图吗:`map(strUppercase)`, `filter(isLongEnough)`, `filter(isShortEnough)`, `reduce(strConcat)`?每个运算符的用法不同,因此它们不会直接组合在一起。我们需要把它们的运算符稍微变动一下,使它们合二为一。 -Can you envision a composition that includes all of these steps: `map(strUppercase)`, `filter(isLongEnough)`, `filter(isShortEnough)`, `reduce(strConcat)`? The shape of each operator is different, so they won't directly compose together. We need to bend their shapes a little bit to fit them together. +希望这些观察结果已经说明了为什么简单的融合式合成不能胜任这项任务。我们需要一个更强大的技术,而转换就是这个工具。 -Hopefully these observations have illustrated why simple fusion-style composition isn't up to the task. We need a more powerful technique, and transducing is that tool. +## 下一步,怎么做? -## How, Next +让我们讨论如何派生 map映射、function谓词,更或者是reducer的组合。 -Let's talk about how we might derive a composition of mappers, predicates, and/or reducers. +不要过于不知所措:您不必经历编程中探索的这些心理。一旦您理解并认识到转换所解决的问题,就可以直接从FP库跳到使用 `transduce(..)`,然后继续处理应用程序的其他部分! -Don't get too overwhelmed: you won't have to go through all these mental steps we're about to explore in your own programming. Once you understand and can recognize the problem transducing solves, you'll be able to just jump straight to using a `transduce(..)` utility from a FP library and move on with the rest of your application! +我们开始吧。 -Let's jump in. +### 将Map/Filter表示为Reduce -### Expressing Map/Filter as Reduce - -The first trick we need to perform is expressing our `filter(..)` and `map(..)` calls as `reduce(..)` calls. Recall [how we did that in Chapter 9](ch9.md/#map-as-reduce): +我们需要执行的第一个技巧是将 `filter(..)`和`map(..)`调用表示为`reduce(..)`调用。回想一下[第9章我们是如何做到的](ch9.md/#map-as-reduce): ```js function strUppercase(str) { return str.toUpperCase(); } @@ -136,11 +135,11 @@ words // "WRITTENSOMETHING" ``` -That's a decent improvement. We now have four adjacent `reduce(..)` calls instead of a mixture of three different methods all with different shapes. We still can't just `compose(..)` those four reducers, however, because they accept two arguments instead of one. +这是一个不错的进步。我们现在有四个相邻的 `reduce(..)`调用,而不是三个不同方法的混合,它们都具有不同的基础方法。然而,我们仍然不能仅仅使用`compose(..)`组合这四个简化方法,因为它们接受两个而不是一个参数。 -In [Chapter 9, we sort of cheated](ch9.md/#user-content-reducecheating) and used `list.push(..)` to mutate as a side effect rather than creating a whole new array to concatenate onto. Let's step back and be a bit more formal for now: +在[第9章,我们有点受骗了](ch9.md/#user-content-reducecheating)中,并使用`list.push(..)`进行转换,而不是创建一个全新的数组来连接。现在让我们退一步,变得更正式一点: ```js function strUppercaseReducer(list,str) { @@ -158,11 +157,11 @@ function isShortEnoughReducer(list,str) { } ``` -Later, we'll revisit whether creating a new array (e.g., `[...list,str]`) to concatenate onto is necessary here or not. +稍后,我们将再次讨论是否需要在这里创建一个新数组(例如,`[...list,str]`)来连接到该数组。 -### Parameterizing the Reducers +### Reducer的参数化 -Both filter reducers are almost identical, except they use a different predicate function. Let's parameterize that so we get one utility that can define any filter-reducer: +除了使用不同的谓词函数外,这两个过滤器reducer程序几乎是相同的。让我们参数化它,我们得到一个实用程序,可以定义任何过滤器reducer程序: ```js function filterReducer(predicateFn) { @@ -176,7 +175,7 @@ var isLongEnoughReducer = filterReducer( isLongEnough ); var isShortEnoughReducer = filterReducer( isShortEnough ); ``` -Let's do the same parameterization of the `mapperFn(..)` for a utility to produce any map-reducer: +让我们做同样的参数化`mapperFn(..)`的实用程序,以产生reducer程序: ```js function mapReducer(mapperFn) { @@ -198,11 +197,11 @@ words .reduce( strConcat, "" ); ``` -### Extracting Common Combination Logic +### 提取公共组合逻辑 -Look very closely at the preceding `mapReducer(..)` and `filterReducer(..)` functions. Do you spot the common functionality shared in each? +仔细查看前面的`mapReducer(..)`和`filterReducer(..)`函数。您是否发现了它们共享的公共功能? -This part: +看这部分: ```js return [ ...list, .. ]; @@ -211,7 +210,8 @@ return [ ...list, .. ]; return list; ``` -Let's define a helper for that common logic. But what shall we call it? +让我们为这个公共逻辑定义一个helper程序。但我们该怎么称呼它呢? + ```js function WHATSITCALLED(list,val) { @@ -219,7 +219,7 @@ function WHATSITCALLED(list,val) { } ``` -If you examine what that `WHATSITCALLED(..)` function does, it takes two values (an array and another value) and it "combines" them by creating a new array and concatenating the value onto the end of it. Very uncreatively, we could name this `listCombine(..)`: +如果您检查那个`WHATSITCALLED(..)`函数的作用,它将接受两个值(一个数组和另一个值),并通过创建一个新数组并将该值连接到它的末尾来“组合”它们。名字可以非常没有创意性,我们可以把它命名为`listCombine(..)`: ```js function listCombine(list,val) { @@ -227,7 +227,7 @@ function listCombine(list,val) { } ``` -Let's now re-define our reducer helpers to use `listCombine(..)`: +现在让我们使用`listCombine(..)`重新定义我们的reducer函数: ```js function mapReducer(mapperFn) { @@ -244,11 +244,11 @@ function filterReducer(predicateFn) { } ``` -Our chain still looks the same (so we won't repeat it). +我们的链式看起来还是一样的(所以我们不会重复它)。 -### Parameterizing the Combination +### 参数化组合 -Our simple `listCombine(..)` utility is only one possible way that we might combine two values. Let's parameterize the use of it to make our reducers more generalized: +我们简单的`listCombine(..)`实用程序只是组合两个值的一种可能方法。让我们参数化它的使用,使我们的reducer程序更一般化: ```js function mapReducer(mapperFn,combinerFn) { @@ -265,7 +265,7 @@ function filterReducer(predicateFn,combinerFn) { } ``` -To use this form of our helpers: +用这种方式定义我们的helper程序: ```js var strToUppercaseReducer = mapReducer( strUppercase, listCombine ); @@ -273,7 +273,7 @@ var isLongEnoughReducer = filterReducer( isLongEnough, listCombine ); var isShortEnoughReducer = filterReducer( isShortEnough, listCombine ); ``` -Defining these utilities to take two arguments instead of one is less convenient for composition, so let's use our `curry(..)` approach: +将这些函数定义为使用两个参数而不是一个参数,这种方式更不方便进行组合,所以让我们使用`curry(..)`方法进行组合: ```js var curriedMapReducer = @@ -299,15 +299,15 @@ var isShortEnoughReducer = curriedFilterReducer( isShortEnough )( listCombine ); ``` -That looks a bit more verbose, and probably doesn't seem very useful. +这看起来有点啰嗦,而且可能不是很有用。 -But this is actually necessary to get to the next step of our derivation. Remember, our ultimate goal here is to be able to `compose(..)` these reducers. We're almost there. +但这对于推导的下一步是必要的。记住,我们这里的最终目标是能够使用`compose(..)`组合这些reducer函数。我们做的差不多了。 -### Composing Curried +### 组合柯里化 -This step is the trickiest of all to visualize. So read slowly and pay close attention here. +这一步太形象化了。所以慢点读,仔细听。 -Let's consider the curried functions from earlier, but without the `listCombine(..)` function having been passed in to each: +让我们考虑一下之前的函数,但是没有将`listCombine(..)`函数传递给每个函数: ```js var x = curriedMapReducer( strUppercase ); @@ -315,9 +315,9 @@ var y = curriedFilterReducer( isLongEnough ); var z = curriedFilterReducer( isShortEnough ); ``` -Think about the shape of all three of these intermediate functions, `x(..)`, `y(..)`, and `z(..)`. Each one expects a single combination function, and produces a reducer function with it. +考虑这三个中间函数的形式,`x(..)`, `y(..)`, 和 `z(..)`。每个函数都期望一个组合函数,并用它生成一个reducer函数。 -Remember, if we wanted the independent reducers from all these, we could do: +记住,如果我们想要这些独立的reducer函数,我们可以: ```js var upperReducer = x( listCombine ); @@ -325,7 +325,7 @@ var longEnoughReducer = y( listCombine ); var shortEnoughReducer = z( listCombine ); ``` -But what would you get back if you called `y(z)`, instead of `y(listCombine)`? Basically, what happens when passing `z` in as the `combinerFn(..)` for the `y(..)` call? That returned reducer function internally looks kinda like this: +但是如果您调用`y(z)`而不是`y(listCombine)`,会得到什么呢?基本上,当将 `z` 作为`combinerFn(..)`传递给 `y(..)`调用时会发生什么?返回的reducer函数内部看起来像这样: ```js function reducer(list,val) { @@ -334,18 +334,18 @@ function reducer(list,val) { } ``` -See the `z(..)` call inside? That should look wrong to you, because the `z(..)` function is supposed to receive only a single argument (a `combinerFn(..)`), not two arguments (`list` and `val`). The shapes don't match. That won't work. +看到里面的`z(..)`调用了吗?这看起来不对,因为`z(..)`函数应该只接收一个参数(`combinerFn(..)`),而不是两个参数(`list` 和 `val`)。形式不匹配。是行不通的。 -Let's instead look at the composition `y(z(listCombine))`. We'll break that down into two separate steps: +让我们看一下组合`y(z(listCombine))`。我们将其分为两个步骤: ```js var shortEnoughReducer = z( listCombine ); var longAndShortEnoughReducer = y( shortEnoughReducer ); ``` -We create `shortEnoughReducer(..)`, then we pass *it* in as the `combinerFn(..)` to `y(..)` instead of calling `y(listCombine)`; this new call produces `longAndShortEnoughReducer(..)`. Re-read that a few times until it clicks. +我们创建了`shortEnoughReducer(..)`,然后我们将它作为`combinerFn(..)`传递给 `y(..)`,而不是调用`y(listCombine)`;这个新调用生成`longAndShortEnoughReducer(..)`。再读几遍,直到它点击为止。 -Now consider: what do `shortEnoughReducer(..)` and `longAndShortEnoughReducer(..)` look like internally? Can you see them in your mind? +现在考虑一下:`shortEnoughReducer(..)` 和 `longAndShortEnoughReducer(..)`在内部是什么样子的?你能在脑海中看到它们吗? ```js // shortEnoughReducer, from calling z(..): @@ -361,11 +361,11 @@ function reducer(list,val) { } ``` -Do you see how `shortEnoughReducer(..)` has taken the place of `listCombine(..)` inside `longAndShortEnoughReducer(..)`? Why does that work? +您是否看到`shortEnoughReducer(..)` 如何取代`listCombine(..)`中的`longAndShortEnoughReducer(..)`?为什么会这样呢? -Because **the shape of a `reducer(..)` and the shape of `listCombine(..)` are the same.** In other words, a reducer can be used as a combination function for another reducer; that's how they compose! The `listCombine(..)` function makes the first reducer, then *that reducer* can be used as the combination function to make the next reducer, and so on. +因为`reducer(..)`的格式和`listCombine(..)`的格式是相同的。**换句话说,一个reducer函数可以作为另一个reducer函数的组合函数使用;他们就是这样组合的!`listCombine(..)`函数生成第一个reducer函数,然后可以用作组合函数来生成下一个reducer函数,以此类推。 -Let's test out our `longAndShortEnoughReducer(..)` with a few different values: +让我们用几个不同的值来测试我们的`longAndShortEnoughReducer(..)`: ```js longAndShortEnoughReducer( [], "nope" ); @@ -378,18 +378,18 @@ longAndShortEnoughReducer( [], "hello world" ); // [] ``` -The `longAndShortEnoughReducer(..)` utility is filtering out both values that are not long enough and values that are not short enough, and it's doing both these filterings in the same step. It's a composed reducer! +`longAndShortEnoughReducer(..)`实用程序过滤掉两个不够长和不够短的值,它在同一步骤中执行这两个过滤。它是一个复合reducer函数! -Take another moment to let that sink in. It still kinda blows my mind. +再花点时间让自己明白这一点。感觉还是让我震惊。 -Now, to bring `x(..)` (the uppercase reducer producer) into the composition: +现在,将`x(..)`添加到合成中: ```js var longAndShortEnoughReducer = y( z( listCombine) ); var upperLongAndShortEnoughReducer = x( longAndShortEnoughReducer ); ``` -As the name `upperLongAndShortEnoughReducer(..)` implies, it does all three steps at once -- a mapping and two filters! What it kinda look likes internally: +正如名称`upperLongAndShortEnoughReducer(..)`所暗示的,它一次完成所有三个步骤——一个映射和两个过滤器!看内部情况: ```js // upperLongAndShortEnoughReducer: @@ -398,11 +398,11 @@ function reducer(list,val) { } ``` -A string `val` is passed in, uppercased by `strUppercase(..)` and then passed along to `longAndShortEnoughReducer(..)`. *That* function only conditionally adds this uppercased string to the `list` if it's both long enough and short enough. Otherwise, `list` will remain unchanged. +传入一个字符串`val`,用`strUppercase(..)`大写,然后传递给`longAndShortEnoughReducer(..)`。函数只有在足够长和足够短的情况下才有条件地将这个大写字符串添加到`list`中。否则,`list`将保持不变。 -It took my brain weeks to fully understand the implications of that juggling. So don't worry if you need to stop here and re-read a few (dozen!) times to get it. Take your time. +我的大脑花了几周的时间才完全理解这种含义。所以,如果你需要在这里停下来,花你读几遍(甚至几十遍!)的时间。 -Now let's verify: +现在让我们来验证: ```js upperLongAndShortEnoughReducer( [], "nope" ); @@ -415,9 +415,9 @@ upperLongAndShortEnoughReducer( [], "hello world" ); // [] ``` -This reducer is the composition of the map and both filters! That's amazing! +这个reducer函数是映射函数和过滤函数的合成!那太神奇了! -Let's recap where we're at so far: +让我们回顾一下目前的进展: ```js var x = curriedMapReducer( strUppercase ); @@ -430,9 +430,9 @@ words.reduce( upperLongAndShortEnoughReducer, [] ); // ["WRITTEN","SOMETHING"] ``` -That's pretty cool. But let's make it even better. +这是很酷。让我们做得更好吧。 -`x(y(z( .. )))` is a composition. Let's skip the intermediate `x` / `y` / `z` variable names, and just express that composition directly: +`x(y(z( .. )))`是一个组合型函数。让我们跳过中间的`x` / `y` / `z` 变量名,直接表示这个组合: ```js var composition = compose( @@ -447,13 +447,13 @@ words.reduce( upperLongAndShortEnoughReducer, [] ); // ["WRITTEN","SOMETHING"] ``` -Think about the flow of "data" in that composed function: +想想组合函数中的“数据”流: -1. `listCombine(..)` flows in as the combination function to make the filter-reducer for `isShortEnough(..)`. -2. *That* resulting reducer function then flows in as the combination function to make the filter-reducer for `isLongEnough(..)`. -3. Finally, *that* resulting reducer function flows in as the combination function to make the map-reducer for `strUppercase(..)`. +1. `listCombine(..)`作为组合函数传入,从而生成用于`isShortEnough(..)`的filter-reducer函数。 +2. 生成的reducer函数然后作为组合函数传入,从而生成用于`isLongEnough(..)`的filter-reducer函数。 +3. 生成的reducer函数然后作为组合函数传入,从而生成用于`strUppercase(..)`的filter-reducer函数。 -In the previous snippet, `composition(..)` is a composed function expecting a combination function to make a reducer; `composition(..)` here has a special name: transducer. Providing the combination function to a transducer produces the composed reducer: +在前面的代码片段中,`composition(..)`是一个复合函数,它期望组合函数能够生成一个reducer函数;这里有一个特殊的名称: 转换器。为转换提供组合功能,产生组合reducer函数: ```js var transducer = compose( @@ -467,11 +467,11 @@ words // ["WRITTEN","SOMETHING"] ``` -**Note:** We should make an observation about the `compose(..)` order in the previous two snippets, which may be confusing. Recall that in our original example chain, we `map(strUppercase)` and then `filter(isLongEnough)` and finally `filter(isShortEnough)`; those operations indeed happen in that order. But in [Chapter 4](ch4.md/#user-content-generalcompose), we learned that `compose(..)` typically has the effect of running its functions in reverse order of listing. So why don't we need to reverse the order *here* to get the same desired outcome? The abstraction of the `combinerFn(..)` from each reducer reverses the effective applied order of operations under the hood. So counter-intuitively, when composing a transducer, you actually want to list them in desired order of execution! +**注意:**我们应该对前两个代码片段中的`compose(..)`顺序进行观察,这可能会让人混淆。回想一下,在最初的示例链中,我们从`map(strUppercase)`然后`filter(isLongEnough)`最后`filter(isShortEnough)`;这些操作确实是按这个顺序进行的。但是在[章节 4](ch4.md/#user-content-generalcompose)中,我们了解到`compose(..)`通常具有按列表的相反顺序运行函数的效果。那么为什么我们不需要颠倒顺序来得到相同的期望结果呢?从每个reducer中抽象出的 `combinerFn(..)` 颠倒了有效应用操作顺序。因此,与直觉相反的是,在组合转换函数时,您实际上希望按照所需的执行顺序列出它们! -#### List Combination: Pure vs. Impure +#### 列表组合: 纯函数与不纯函数 -As a quick aside, let's revisit our `listCombine(..)` combination function implementation: +顺便提一下,让我们重温一下我们的`listCombine(..)`组合函数实现: ```js function listCombine(list,val) { @@ -479,9 +479,9 @@ function listCombine(list,val) { } ``` -While this approach is pure, it has negative consequences for performance: for each step in the reduction, we're creating a whole new array to append the value onto, effectively throwing away the previous array. That's a lot of arrays being created and thrown away, which is not only bad for CPU but also GC memory churn. +虽然这种方法是纯的,但它对性能有负面影响:对于缩减中的每一步,我们都要创建一个全新的数组来追加值,从而有效地丢弃前面的数组。这需要创建和丢弃大量数组,这不仅不利于CPU,还会影响GC内存。 -By contrast, look again at the better-performing but impure version: +相比之下,再看看性能更好但不纯的版本: ```js function listCombine(list,val) { @@ -490,19 +490,19 @@ function listCombine(list,val) { } ``` -Thinking about `listCombine(..)` in isolation, there's no question it's impure and that's usually something we'd want to avoid. However, there's a bigger context we should consider. +如果单独考虑`listCombine(..)`,毫无疑问它是不纯的,这通常是我们希望避免的。然而,我们应该考虑一个更大的背景。 -`listCombine(..)` is not a function we interact with at all. We don't directly use it anywhere in the program; instead, we let the transducing process use it. +`listCombine(..)`根本不是我们交互的函数。我们不在程序的任何地方直接使用它;相反,我们在转换过程使用它。 -Back in [Chapter 5](ch5.md), we asserted that our goal with reducing side effects and defining pure functions was only that we expose pure functions to the API level of functions we'll use throughout our program. We observed that under the covers, inside a pure function, it can cheat for performance sake all it wants, as long as it doesn't violate the external contract of purity. +回到[第5章](ch5.md),我们断言减少副作用和定义纯函数的目标只是将纯函数暴露给我们将在整个程序中使用的函数的API级别。我们观察到,在一个纯函数的内部,它可以为了性能而欺骗所有它想要的东西,只要它不违反纯的外部契约。 -`listCombine(..)` is more an internal implementation detail of the transducing -- in fact, it'll often be provided by the transducing library for you! -- rather than a top-level method you'd interact with on a normal basis throughout your program. +`listCombine(..)` 更多的是转换的内部实现细节——事实上,它通常由转换库为您提供!——而不是在整个程序中与之正常交互的顶级方法。 -Bottom line: I think it's perfectly acceptable, and advisable even, to use the performance-optimal impure version of `listCombine(..)`. Just make sure you document that it's impure with a code comment! +我认为使用性能最佳的非纯版本`listCombine(..)`是完全可以接受的,甚至是可取的。只要确保您用代码注释来记录它是不纯的! -### Alternative Combination +### 替代组合 -So far, this is what we've derived with transducing: +到目前为止,这是我们通过转化得到的: ```js words @@ -511,11 +511,11 @@ words // WRITTENSOMETHING ``` -That's pretty good, but we have one final trick up our sleeve with transducing. And frankly, I think this part is what makes all this mental effort you've expended thus far, actually worth it. +这很好,但是我们还有最后一个关于转化的技巧。坦率地说,我认为这部分是你迄今为止所付出的所有精神努力的真正价值所在。 -Can we somehow "compose" these two `reduce(..)` calls to get it down to just one `reduce(..)`? Unfortunately, we can't just add `strConcat(..)` into the `compose(..)` call; because it's a reducer and not a combination-expecting function, its shape is not correct for the composition. +我们能否以某种方式“组合”这两个`reduce(..)`调用,将其简化为一个`reduce(..)`?不幸的是,我们不能`strConcat(..)`添加到`compose(..)`调用中;因为它是一个reducer函数,而不是一个期望组合的函数,所以它的形式不适合组合。 -But let's look at these two functions side by side: +但是让我们一起来看看这两个函数: ```js function strConcat(str1,str2) { return str1 + str2; } @@ -523,26 +523,26 @@ function strConcat(str1,str2) { return str1 + str2; } function listCombine(list,val) { list.push( val ); return list; } ``` -If you squint your eyes, you can almost see how these two functions are interchangeable. They operate with different data types, but conceptually they do the same thing: combine two values into one. +如果你仔细看,你几乎可以看到这两个功能是如何互换的。它们使用不同的数据类型进行操作,但在概念上它们做的是相同的事情:将两个值合并为一个值。 -In other words, `strConcat(..)` is a combination function! +换句话说,`strConcat(..)`是一个组合函数! -That means we can use *it* instead of `listCombine(..)` if our end goal is to get a string concatenation rather than a list: +这意味着如果我们的最终目标是得到一个字符串连接而不是列表,我们可以使用*it*而不是`listCombine(..)`: ```js words.reduce( transducer( strConcat ), "" ); // WRITTENSOMETHING ``` -Boom! That's transducing for you. I won't actually drop the mic here, but just gently set it down... +太棒啦!这就是转化。 -## What, Finally +## 最后是什么 -Take a deep breath. That was a lot to digest. +深呼吸。要消化的东西太多了。 -Clearing our brains for a minute, let's turn our attention back to just using transducing in our applications without jumping through all those mental hoops to derive how it works. +先清理一下我们的大脑,让我们把注意力转回到仅仅在应用程序中使用转换函数上,而不是跳过所有的思维障碍来推导它是如何工作的。 -Recall the helpers we defined earlier; let's rename them for clarity: +回想一下我们前面定义的函数;为了清晰起见,我们重新命名一下: ```js var transduceMap = @@ -561,7 +561,7 @@ var transduceFilter = } ); ``` -Also recall that we use them like this: +还记得我们这样使用它们: ```js var transducer = compose( @@ -571,9 +571,9 @@ var transducer = compose( ); ``` -`transducer(..)` still needs a combination function (like `listCombine(..)` or `strConcat(..)`) passed to it to produce a transduce-reducer function, which can then be used (along with an initial value) in `reduce(..)`. +`transducer(..)`仍然需要传递给它一个组合函数(如`listCombine(..)`或`strConcat(..)`)来生成一个reducer函数,然后可以在`reduce(..)`中使用该函数。 -But to express all these transducing steps more declaratively, let's make a `transduce(..)` utility that does these steps for us: +但是为了更明确地表达所有这些转换步骤,让我们创建一个`transduce(..)` 实用程序,它为我们完成这些步骤: ```js function transduce(transducer,combinerFn,initialValue,list) { @@ -582,7 +582,7 @@ function transduce(transducer,combinerFn,initialValue,list) { } ``` -Here's our running example, cleaned up: +这是我们的运行例子: ```js var transducer = compose( @@ -598,11 +598,11 @@ transduce( transducer, strConcat, "", words ); // WRITTENSOMETHING ``` -Not bad, huh!? See the `listCombine(..)` and `strConcat(..)` functions used interchangeably as combination functions? +还不赖,是吧! ?看一下`listCombine(..)`和`strConcat(..)`函数作为组合函数互换使用吗? -### Transducers.js +### Transducers.js 库 -Finally, let's illustrate our running example using the [`transducers-js` library](https://github.com/cognitect-labs/transducers-js): +最后,让我们使用 [`transducers-js` 库](https://github.com/cognitect-labs/transducers-js): ```js var transformer = transducers.comp( @@ -618,15 +618,16 @@ transducers.transduce( transformer, strConcat, "", words ); // WRITTENSOMETHING ``` -Looks almost identical to above. +看起来几乎和上面一样。 + +**注意:**前面的代码片段使用了`transformers.comp(..)`,因为库提供了它,但是在本例中,来自第4章的[`compose(..)`](ch4.md/#user-content-generalcompose)将产生相同的结果。换句话说,合成本身并不是一个敏锐转换的操作。 -**Note:** The preceding snippet uses `transformers.comp(..)` because the library provides it, but in this case our [`compose(..)` from Chapter 4](ch4.md/#user-content-generalcompose) would produce the same outcome. In other words, composition itself isn't a transducing-sensitive operation. +这个代码片段中的组合函数名为`transformer`,而不是`transducer`。这是因为如果我们调用`transformer( listCombine )`(或`transformer( strConcat )`),我们将不会像前面那样得到一个直接的转换的reducer函数。 -The composed function in this snippet is named `transformer` instead of `transducer`. That's because if we call `transformer( listCombine )` (or `transformer( strConcat )`), we won't get a straight-up transduce-reducer function as earlier. +`transducers.map(..)`和`transducers.filter(..)`是特殊的函数,它们将常规谓词或映射函数转换为生成特殊转换对象的函数;库使用这些转换对象进行转换。这种转换对象抽象的额外功能超出了我们将探讨的范围,因此请参考库文档以获得更多信息。 -`transducers.map(..)` and `transducers.filter(..)` are special helpers that adapt regular predicate or mapper functions into functions that produce a special transform object (with the transducer function wrapped underneath); the library uses these transform objects for transducing. The extra capabilities of this transform object abstraction are beyond what we'll explore, so consult the library's documentation for more information. +因为调用`transformer(..)` 会生成一个转换对象,而不是一个典型的二进制换向器函数,所以库还提供了`toFn(..)`来调整转换对象,使其可被原生数组中`reduce(..)`使用: -Because calling `transformer(..)` produces a transform object and not a typical binary transduce-reducer function, the library also provides `toFn(..)` to adapt the transform object to be useable by native array `reduce(..)`: ```js words.reduce( @@ -636,7 +637,7 @@ words.reduce( // WRITTENSOMETHING ``` -`into(..)` is another provided helper that automatically selects a default combination function based on the type of empty/initial value specified: +`into(..)`是另一个函数,它可以根据指定的空/初值类型自动选择默认组合函数: ```js transducers.into( [], transformer, words ); @@ -646,16 +647,16 @@ transducers.into( "", transformer, words ); // WRITTENSOMETHING ``` -When specifying an empty `[]` array, the `transduce(..)` called under the covers uses a default implementation of a function like our `listCombine(..)` helper. But when specifying an empty `""` string, something like our `strConcat(..)` is used. Cool! +当指定一个空的`[]`数组时,在内部调用的`transduce(..)`使用一个函数的默认实现,比如我们的`listCombine(..)` 。但是,当指定一个空的`""`字符串时,使用类似于我们的`strConcat(..)`的东西。这样太酷了! -As you can see, the `transducers-js` library makes transducing pretty straightforward. We can very effectively leverage the power of this technique without getting into the weeds of defining all those intermediate transducer-producing utilities ourselves. +如您所见,`transducers-js` 库使换能器非常简单。我们可以非常有效地利用这种技术的力量,而不必自己定义所有那些产生中间换能器的实用程序。 -## Summary +## 总结 -To transduce means to transform with a reduce. More specifically, a transducer is a composable reducer. +转换指的是用reduce进行变换。更具体地说,转换函数是一种可组合的reducer函数。 -We use transducing to compose adjacent `map(..)`, `filter(..)`, and `reduce(..)` operations together. We accomplish this by first expressing `map(..)`s and `filter(..)`s as `reduce(..)`s, and then abstracting out the common combination operation to create unary reducer-producing functions that are easily composed. +我们使用转换来组合相邻的 `map(..)`, `filter(..)`, 和 `reduce(..)`操作。我们首先将 `map(..)`和`filter(..)`表示为`reduce(..)`,然后抽象出常见的组合操作来创建易于组合的一元生成reducer函数,从而实现这一点。 -Transducing primarily improves performance, which is especially obvious if used on an observable. +转化主要是为了提高性能,如果使用在一个纯函数是特别明显的。 -But more broadly, transducing is how we express a more declarative composition of functions that would otherwise not be directly composable. The result, if used appropriately as with all other techniques in this book, is clearer, more readable code! A single `reduce(..)` call with a transducer is easier to reason about than tracing through multiple `reduce(..)` calls. +但更广泛地说,转换是我们表达更声明性的函数组合的方式,否则这些函数将不能直接组合。如果像本书中所有其他技术一样使用得当,其结果将是更清晰、更可读的代码!使用转换函数的调用单个`reduce(..)`比跟踪多个`reduce(..)` 调用更容易推理。 From 1a9cade6a9c1e2aa3a63cd4627852372ce6cca1d Mon Sep 17 00:00:00 2001 From: Siming Date: Sun, 15 Sep 2019 22:37:55 +0800 Subject: [PATCH 60/63] Update apB.md --- manuscript/apB.md | 199 +++++++++++++++++++++++----------------------- 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/manuscript/apB.md b/manuscript/apB.md index b22f19e1..7f253c7b 100644 --- a/manuscript/apB.md +++ b/manuscript/apB.md @@ -1,57 +1,56 @@ -# Functional-Light JavaScript -# Appendix B: The Humble Monad +# 附言 B: 卑微的monoid(单子) -Let me just start off this appendix by admitting: I did not know much about what a monad was before starting to write this appendix. And it took a lot of mistakes to get something sensible. If you don't believe me, go look at the commit history of this appendix in the [Github repository for this book](https://github.com/getify/Functional-Light-JS)! +从这个附录我开始承认:在开始编写这个附录之前,我对单子是什么并不了解。我们犯了很多错误才了解到。如果您不相信我的话,请访问[这本书在github](https://github.com/getify/Functional-Light-JS)查看这个附录的提交历史! -I am including the topic of monads in the book because it's part of the journey that every developer will encounter while learning FP, just as I have in this book writing. +我在书中包含了单子的主题,因为它是每个开发人员在学习FP时都会遇到的过程的一部分,正如我在本书中所写的那样。 -We're basically ending this book with a brief glimpse at monads, whereas most other FP literature kinda almost starts with monads! I do not encounter in my "Functional-Light" programming much of a need to think explicitly in terms of monads, so that's why this material is more bonus than main core. But that's not to say monads aren't useful or prevalent -- they very much are. +我们基本上是以简短地浏览一下单子来结束这本书,而大多数FP文献几乎都是以单子开始的!在我的“轻量函数”编程中,我没有遇到需要显式地按照单子进行思考的情况,所以这就是为什么本文比主体核心更有价值。但这并不是说单子没有用处或不流行——它们非常流行。 -There's a bit of a joke around the JavaScript FP world that pretty much everybody has to write their own tutorial or blog post on what a monad is, like the writing of it alone is some rite of passage. Over the years, monads have variously been depicted as burritos, onions, and all sorts of other wacky conceptual abstractions. I hope there's none of that silly business going on here! +在JavaScript 函数编程世界里有一个笑话,几乎每个人都必须编写自己的教程或博客文章来介绍单子是什么,就像单独编写单子是一种仪式一样。多年来,单子被各种各样地描述为墨西哥卷饼、洋葱和其他各种古怪的概念抽象。我希望这里不要发生那种愚蠢的事情! -> A monad is just a monoid in the category of endofunctors. +> 单子说白了不过就是自函子范畴上的一个幺半群而已 -We started the preface with this quote, so it seems fitting we come back to it here. But no, we won't be talking about monoids, endofunctors, or category theory. That quote is not only condescending, but totally unhelpful. +我们以这句引言作为序言,所以回到这里似乎是合适的。但不,我们不会讨论单子,内函子,或范畴理论。这句话不仅羞涩难懂,而且毫无帮助。 -My only hope for what you get out of this discussion is to not be scared of the term monad or the concept anymore -- I have been, for years! -- and to be able to recognize them when you see them. You might, just maybe, even use them on occasion. +我希望你们从这次讨论中得到的是不要再害怕‘monad’这个词或者这个概念——我已经害怕了很多年了!——当你看到他们的时候能够认出他们。你可能,只是可能,偶尔会用到它们。 -## Type +## 类型 -There's a huge area of interest in FP that we've basically stayed entirely away from throughout this book: type theory. I'm not going to get very deep into type theory, because quite frankly I'm not qualified to do so. And you wouldn't appreciate it even if I did. +FP中有一个很大的领域是我们在整本书中都没有涉及的:类型理论。我不打算深入研究类型理论,因为坦白地说,我没有资格这么做。即使我做了,你也不会感激的。 -But what I will say is that a monad is basically a value type. +但我要说的是monad基本上是一种值类型。 -The number `42` has a value type (number!) that brings with it certain characteristics and capabilities that we rely on. The string `"42"` may look very similar, but it has a different purpose in our program. +数字`42`有一个值类型(数字!),它带来了我们所依赖的某些特性和功能。字符串`42`可能看起来非常相似,但是在我们的程序中它有不同的用途。 -In object-oriented programming, when you have a set of data (even a single discrete value) and you have some behavior you want to bundle with it, you create an object/class to represent that "type". Instances are then members of that type. This practice generally goes by the name "data structures". +在面向对象编程中,当您有一组数据(甚至是单个离散值),并且您有一些想要与之绑定的行为时,您将创建一个对象/类来表示该“类型”。实例就是该类型的成员。这种实践通常被称为“数据结构”。 -I'm going to use the notion of data structures very loosely here, and assert that we may find it useful in a program to define a set of behaviors and constraints for a certain value, and bundle them together with that value into a single abstraction. That way, as we work with one or more of those kinds of values in our program, their behaviors come along for free and will make working with them more convenient. And by convenient, I mean more declarative and approachable for the reader of your code! +我将在这里宽松地使用数据结构的概念,我们可能会发现,在程序中为某个值定义一组行为和约束,并将它们与该值捆绑到一个抽象中,是非常有用的。这样,当我们在程序中处理一个或多个这样的值时,它们的行为是免费的,并且会使处理它们更加方便。所谓方便,我的意思是对代码的读者来说更具有声明性和可接近性! -A monad is a data structure. It's a type. It's a set of behaviors that are specifically designed to make working with a value predictable. +monad是一种数据结构。这是一个类型。它是一组专门设计用来使使用值的工作变得可预测的行为。 -Recall in [Chapter 9 that we talked about functors](ch9.md/#a-word-functors): a value along with a map-like utility to perform an operation on all its constitute data members. A monad is a functor that includes some additional behavior. +回想一下在[第9章,我们讨论了函数变量](ch9.md/#a-word-functors):一个值和一个类似于map的实用程序,用于对其所有构成数据成员执行操作。monad(单子)是包含一些附加行为的函数变量。 -## Loose Interface +## 零散接口 -Actually, a monad isn't a single data type, it's really more like a related collection of data types. It's kind of an interface that's implemented differently depending on the needs of different values. Each implementation is a different type of monad. +实际上,monad并不是一种单一的数据类型,它更像是一组相关的数据类型。它是一种根据不同值的需要实现不同的接口。每个实现都是不同类型的单子。 -For example, you may read about the "Identity Monad", the "IO Monad", the "Maybe Monad", the "Either Monad", or a variety of others. Each of these has the basic monad behavior defined, but it extends or overrides the interactions according to the use cases for each different type of monad. +例如,您可能会读到关于"Identity Monad"、"IO Monad"、"Maybe Monad"、"Either Monad"或其他各种Monad。它们都定义了基本的单子行为,但是它根据每种单子类型的用例扩展或覆盖了交互。 -It's a little more than an interface though, because it's not just the presence of certain API methods that makes an object a monad. There's a certain set of guarantees about the interactions of these methods that is necessary, to be monadic. These well-known invariants are critical to usage of monads improving readability by familiarity; otherwise, it's just an ad hoc data structure that must be fully read to be understood by the reader. +它不仅仅是一个接口,因为使一个对象成为monad的不仅仅是某些API方法的存在。这些方法的相互作用有一定的保证,这是monadic的。这些众所周知的不变量对于使用单子是至关重要的,通过熟悉提高可读性;否则,它只是一个特殊的数据结构,必须被完全读取才能被读者理解。 -As a matter of fact, there's not even just one single unified agreement on the names of these monadic methods, the way a true interface would mandate; a monad is more like a loose interface. Some people call a certain method `bind(..)`, some call it `chain(..)`, some call it `flatMap(..)`, and so on. +事实上,对于这些单子方法的名称,甚至没有一个统一的协议,这是一个真正的接口所要求的;monad更像是一个松散的接口。有些人将某个方法称为`bind(..)`,有些人将其称为`chain(..)`,有些人将其称为`flatMap(..)`,依此类推。 -So a monad is an object data structure with sufficient methods (of practically any name or sort) that at a minimum satisfy the main behavioral requirements of the monad definition. Each kind of monad has a different kind of extension above the minimum. But, because they all have an overlap in behavior, using two different kinds of monads together is still straightforward and predictable. +因此monad是一个对象数据结构,具有足够的方法(实际上是任何名称或类型的方法),至少满足monad定义的主要行为需求。每一种单子在最小值以上都有一种不同的扩展。但是,因为它们在行为上都有重叠,所以将两种不同的单子放在一起使用仍然是直接和可预测的。 -It's in that sense that monads are sort of like an interface. +在这个意义上,单子有点像一个接口。 -## Just a Monad +## Just monad -A basic primitive monad underlying many other monads you will run across is called Just. It's *just* a simple monadic wrapper for any regular (aka, non-empty) value. +您将遇到的许多其他单子下面的基本单子称为Just。它只是一个简单的单子包装任何常规(又名,非空)的值。 -Since a monad is a type, you might think we'd define `Just` as a class to be instantiated. That's a valid way of doing it, but it introduces `this`-binding issues in the methods that I don't want to juggle; instead, I'm going to stick with just a simple function approach. +由于monad是一种类型,您可能认为我们应该将`Just`定义为要实例化的类。这是一种有效的方法,但它在我不想处理的方法中引入了“this”绑定问题;相反,我将坚持使用一个简单的函数方法。 -Here's a basic implementation: +下面是一个基本的实现: ```js function Just(val) { @@ -72,17 +71,17 @@ function Just(val) { } ``` -**Note:** The `inspect(..)` method is included here only for our demonstration purposes. It serves no direct role in the monadic sense. +**注:**`inspect(..)`方法仅用于演示目的。它在单子意义上没有直接作用。 -You'll notice that whatever `val` value a `Just(..)` instance holds, it's never changed. All monad methods create new monad instances instead of mutating the monad's value itself. +您将注意到,无论`val`值是多少,实例`Just(..)`都会保持不变。所有monad方法都创建新的monad实例,而不是修改monad的值本身。 -Don't worry if most of this doesn't make sense right now. We're not gonna obsess too much over the details or the math/theory behind the design of the monad. Instead, we'll focus more on illustrating what we can do with them. +如果现在这些都没有意义,不要担心。我们不会过分关注monad设计背后的细节或数学/理论。相反,我们将更专注于说明我们可以用它们做什么。 -### Working with Monad Methods +### 使用Monad(单子)方法工作 -All monad instances will have `map(..)`, `chain(..)` (also called `bind(..)` or `flatMap(..)`), and `ap(..)` methods. The purpose of these methods and their behavior is to provide a standardized way of multiple monad instances interacting with each other. +所有monad实例都有`map(..)`, `chain(..)`(也称为`bind(..)`或`flatMap(..)`)和`ap(..)`方法。这些方法及其行为的目的是提供一种标准化的方法来实现多个monad实例之间的交互。 -Let's look first at the monadic `map(..)` function. Like `map(..)` on an array (see [Chapter 9](ch9.md/#map)) that calls a mapper function with its value(s) and produces a new array, a monad's `map(..)` calls a mapper function with the monad's value, and whatever is returned is wrapped in a new Just monad instance: +让我们首先看看单子`map(..)`函数。就像数组上的`map(..)`(参见[第9章](ch9.md/#map))用它的值调用mapper函数并生成一个新的数组一样,monad的`map(..)`用monad的值调用mapper函数,无论返回什么都被包装在一个新的Just monad实例中: ```js var A = Just( 10 ); @@ -91,7 +90,7 @@ var B = A.map( v => v * 2 ); B.inspect(); // Just(20) ``` -Monadic `chain(..)` kinda does the same thing as `map(..)`, but then it sort of unwraps the resulting value from its new monad. However, instead of thinking informally about "unwrapping" a monad, the more formal explanation would be that `chain(..)` flattens the monad. Consider: +单子`chain(..)`函数和`map(..)`做的是一样的,但它会从它的新单子中打开结果值。然而,相对于非正式地考虑“展开”monad,更正式的解释应该是`chain(..)`将monad扁平易懂。考虑: ```js var A = Just( 10 ); @@ -101,9 +100,9 @@ eleven; // 11 typeof eleven; // "number" ``` -`eleven` is the actual primitive number `11`, not a monad holding that value. +`eleven`是实际的原始数字`11`,而不是包含该值的单子。 -To connect this `chain(..)` method conceptually to stuff we've already learned, we'll point out that many monad implementations name this method `flatMap(..)`. Now, recall from [Chapter 9 what `flatMap(..)`](ch9.md/#user-content-flatmap) does (as compared to `map(..)`) with an array: +为了从概念上将这个`chain(..)`方法与我们已经学过的东西联系起来,我们将指出,许多monad实现将这个方法命名为`flatMap(..)`。现在,回想一下[第9章`flatMap(..)`](ch9.md/#user-content-flatmap)(与`map(..)`相比)使用数组做了什么: ```js var x = [3]; @@ -112,11 +111,11 @@ map( v => [v,v+1], x ); // [[3,4]] flatMap( v => [v,v+1], x ); // [3,4] ``` -See the difference? The mapper function `v => [v,v+1]` results in a `[3,4]` array, which ends up in the single first position of the outer array, so we get `[[3,4]]`. But `flatMap(..)` flattens out the inner array into the outer array, so we get just `[3,4]` instead. +看出不同了吗?mapper函数`v => [v,v+1]`产生一个`[3,4]` 数组,它最终位于外部数组的第一个位置,因此我们得到`[[3,4]]`。但是`flatMap(..)`将内部数组展平到外部数组中,因此我们得到的只是 `[3,4]` 。 -That's the same kind of thing going on with a monad's `chain(..)` (often referred to as `flatMap(..)`). Instead of getting a monad holding the value as `map(..)` does, `chain(..)` additionally flattens the monad into the underlying value. Actually, instead of creating that intermediate monad only to immediately flatten it, `chain(..)` is generally implemented more performantly to just take a shortcut and not create the monad in the first place. Either way, the end result is the same. +monad的`chain(..)`(通常称为`flatMap(..)`)也是如此。不是像`map(..)`那样获得一个monad来保存值,而是`chain(..)`将monad扁平化到基础值中。实际上,`chain(..)`通常实现得更高效,而不是创建中间的单子只是为了立即使它变扁平化,只是走捷径而不是首先创建单子。无论哪种方式,最终结果都是一样的。 -One way to illustrate `chain(..)` in this manner is in combination with the `identity(..)` utility (see [Chapter 3](ch3.md/#one-on-one)), to effectively extract a value from a monad: +以这种方式说明 `chain(..)`的一种方法是结合`identity(..)`实用程序(参见[第3章](ch3.md/#one-on-one)),以便有效地从单子中提取一个值: ```js var identity = v => v; @@ -124,19 +123,19 @@ var identity = v => v; A.chain( identity ); // 10 ``` -`A.chain(..)` calls `identity(..)` with the value in `A`, and whatever value `identity(..)` returns (`10` in this case) just comes right out without any intervening monad. In other words, from that earlier `Just(..)` code listing, we wouldn't actually need to include that optional `inspect(..)` helper, as `chain(identity)` accomplishes the same goal; it's purely for ease of debugging as we learn monads. +`A.chain(..)`使用`A`中的值调用`identity(..)`,而不管`identity(..)`返回什么值(在本例中是`10`),都会直接返回,不需要任何中间的单子。换句话说,从前面的`Just(..)`代码清单中,我们实际上不需要包含可选的`inspect(..)` ,因为`chain(identity)`实现了相同的目标;这纯粹是为了便于调试,因此我们学习单子。 -At this point, hopefully both `map(..)` and `chain(..)` feel fairly reasonable to you. +在这一点上,希望`map(..)`和`chain(..)`对您来说都是合理的。 -By contrast, a monad's `ap(..)` method will likely be much less intuitive at first glance. It will seem like a strange contortion of interaction, but there's deep and important reasoning behind the design. Let's take a moment to break it down. +相比之下,monad的`ap(..)`方法乍一看可能没有那么直观。这看起来像是一种奇怪的交互扭曲,但设计背后有深刻而重要的理由。让我们花点时间把它分解一下。 -`ap(..)` takes the value wrapped in a monad and "applies" it to another monad using that other monad's `map(..)`. OK, fine so far. +`ap(..)`获取一个monad中包装的值,并使用另一个monad的 `map(..)`将其“应用”到另一个monad。到目前为止还好。 -However, `map(..)` always expects a function. So that means the monad you call `ap(..)` on has to actually contain a function as its value, to pass to that other monad's `map(..)`. +然而,`map(..)` 总是需要一个函数。这意味着你调用的monad`ap(..)`必须包含一个函数作为它的值,以传递给另一个monad`map(..)`。 -Confused? Yeah, not what you might have expected. We'll try to briefly illuminate, but just expect that these things will be fuzzy for a while until you've had a lot more exposure and practice with monads. +困惑吗?是啊,不是你想的那样。我们将尝试简要说明,但只是希望这些事情困惑你的一段时间,直到你有了更多的探索了解和monads练习。 -We'll define `A` as a monad that contains a value `10`, and `B` as a monad that contains the value `3`: +我们将`A`定义为包含值 `10`的单子, `B`定义为包含值`3`的单子: ```js var A = Just( 10 ); @@ -146,11 +145,11 @@ A.inspect(); // Just(10) B.inspect(); // Just(3) ``` -Now, how could we make a new monad where the values `10` and `3` had been added together, say via a `sum(..)` function? Turns out `ap(..)` can help. +现在,我们如何创建一个新的monad,其中的值`10`和`3`已经添加在一起,比如通过`sum(..)`函数?事实证明, `ap(..)`可以提供帮助。 -To use `ap(..)`, we said we first need to construct a monad that holds a function. Specifically, we need one that holds a function that itself holds (remembers via closure) the value in `A`. Let that sink in for a moment. +为了使用`ap(..)`,我们说首先需要构造一个包含函数的单子。具体地说,我们需要一个函数本身包含`A`中的值(通过闭包记住)。让我们先理解一下。 -To make a monad from `A` that holds a value-containing function, we call `A.map(..)`, giving it a curried function that "remembers" that extracted value (see [Chapter 3](ch3.md/#one-at-a-time)) as its first argument. We'll call this new function-containing monad `C`: +要从`A`生成一个包含值函数的单子,我们调用`A.map(..)`,给它一个curried函数“记住”提取的值(参见[Chapter 3](ch3.md/#one-at-a-time)作为它的第一个参数。我们将这个新的功能包含单子`C`: ```js function sum(x,y) { return x + y; } @@ -161,9 +160,9 @@ C.inspect(); // Just(function curried...) ``` -Think about how that works. The curried `sum(..)` function is expecting two values to do its work, and we give it the first of those values by having `A.map(..)` extract `10` and pass it in. `C` now holds the function that remembers `10` via closure. +想想如何运行的。`sum(..)`函数期望有两个值来完成它的工作,我们通过`A.map(..)` 将`10`提取出来并传递给它,从而得到第一个值。`C`现在保存了通过闭包记住`10`的函数。 -Now, to get the second value (`3` inside `B`) passed to the waiting curried function in `C`: +现在,要获得第二个值(`3`内`B`)传递给在`C`等待的curried函数: ```js var D = C.ap( B ); @@ -171,7 +170,7 @@ var D = C.ap( B ); D.inspect(); // Just(13) ``` -The value `10` came out of `C`, and `3` came out of `B`, and `sum(..)` added them together to `13` and wrapped that in the monad `D`. Let's put the two steps together so you can see their connection more clearly: +值`10`来自`C`,`3`来自`B`,`sum(..)`将它们加到`13`中,并将其封装在monad`D`中。让我们把这两个步骤放在一起,这样你就能更清楚地看到他们之间的联系: ```js var D = A.map( curry( sum ) ).ap( B ); @@ -179,7 +178,7 @@ var D = A.map( curry( sum ) ).ap( B ); D.inspect(); // Just(13) ``` -To illustrate what `ap(..)` is helping us with, we could have achieved the same result this way: +为了说明`ap(..)` 正在帮助我们做什么,我们可以通过以下方法获得相同的结果: ```js var D = B.map( A.chain( curry( sum ) ) ); @@ -187,7 +186,7 @@ var D = B.map( A.chain( curry( sum ) ) ); D.inspect(); // Just(13); ``` -And that of course is just a composition (see [Chapter 4](ch4.md)): +当然,这只是一个组合(见[第4章](ch4.md)): ```js var D = compose( B.map, A.chain, curry )( sum ); @@ -195,17 +194,17 @@ var D = compose( B.map, A.chain, curry )( sum ); D.inspect(); // Just(13) ``` -Cool, huh!? +酷吧! ? -If the *how* of this discussion on monad methods is unclear so far, go back and re-read. If the *why* is elusive, just hang in there. Monads so easily confound developers, that's *just* how it is! +如果到目前为止关于monad方法的讨论还不清楚,请回去重新阅读。如果解释还是难懂,那就坚持看完上面的。monad很容易让开发者困惑,这就是它的本质! -## Maybe +## Maybe 单子实例 -It's very common in FP material to cover well-known monads like Maybe. Actually, the Maybe monad is a particular pairing of two other simpler monads: Just and Nothing. +在FP中很常见的是覆盖著名的单子,比如Maybe。实际上,Maybe monad是另外两个更简单的monad的特定组合:Just和Nothing。 -We've already seen Just; Nothing is a monad that holds an empty value. Maybe is a monad that either holds a Just or a Nothing. +我们已经看到Just;Nothing是一个包含空值的单子。也许是一个单子要么持有一个Just或一个Nothing。 -Here's a minimal implementation of Maybe: +下面是一个最简单的实现Maybe: ```js var Maybe = { Just, Nothing, of/* aka: unit, pure */: Just }; @@ -223,21 +222,21 @@ function Nothing() { } ``` -**Note:** `Maybe.of(..)` (sometimes referred to as `unit(..)` or `pure(..)`) is a convenience alias for `Just(..)`. +**注:** `Maybe.of(..)`(有时称为`unit(..)`或`pure(..)`)是`Just(..)`的别名。 -In contrast to `Just()` instances, `Nothing()` instances have no-op definitions for all monadic methods. So if such a monad instance shows up in any monadic operations, it has the effect of basically short-circuiting to have no behavior happen. Notice there's no imposition here of what "empty" means -- your code gets to decide that. More on that later. +与`Just()`实例不同,`Nothing()`实例对所有monadic方法都有无操作定义的含义。因此,如果这样一个单子实例出现在任何monadic运算中,它的作用基本上是短路而不发生任何行为。请注意,这里没有强制执行“空”的含义——您的代码将决定这一点。稍后会详细介绍。 -In Maybe, if a value is non-empty, it's represented by an instance of `Just(..)`; if it's empty, it's represented by an instance of `Nothing()`. +在Maybe中,如果一个值是非空的,它由`Just(..)`实例表示;如果它是空的,则由`Nothing()`实例表示。 -But the importance of this kind of monad representation is that whether we have a `Just(..)` instance or a `Nothing()` instance, we'll use the API methods the same. +但是这种monad表示的重要性在于,无论我们有一个`Just(..)`实例还是一个`Nothing()`实例,我们都将使用相同的API方法。 -The power of the Maybe abstraction is to encapsulate that behavior/no-op duality implicitly. +抽象的功能在于隐式地封装了行为/无操作对偶性。 -### Different Maybes +### 不同的Maybe -Many implementations of a JavaScript Maybe monad include a check (usually in `map(..)`) to see if the value is `null`/`undefined`, and skipping the behavior if so. In fact, Maybe is trumpeted as being valuable precisely because it sort of automatically short-circuits its behavior with the encapsulated empty-value check. +JavaScript的许多实现可能都包含一个检查(通常在`map(..)`中)来查看值是否为`null`/`undefined`,如果是,则跳过该行为。事实上,也许被鼓吹为有价值,正是因为它自动短路其行为与封装的空值检查。 -Here's how Maybe is usually illustrated: +下面是Maybe通常的表达方式: ```js // instead of unsafe `console.log( someObj.something.else.entirely )`: @@ -249,17 +248,17 @@ Maybe.of( someObj ) .map( console.log ); ``` -In other words, if at any point in the chain we get a `null`/`undefined` value, the Maybe magically switches into no-op mode -- it's now a `Nothing()` monad instance! -- and stops doing anything for the rest of the chain. That makes the nested-property access safe against throwing JS exceptions if some property is missing/empty. That's cool, and a nice helpful abstraction for sure! +换句话说,如果在链上的任何一点我们得到一个`null`/`undefined`值,那么可能会神奇地切换到无操作定义模式——现在是一个 `Nothing()`monad实例!——停止对链的其他部分做任何事情。这使得嵌套属性访问在某些属性丢失/为空时不会抛出JS异常。这很酷,而且是一个非常有用的抽象概念! -But... ***that approach to Maybe is not a pure monad.*** +但是…这种Maybe不是一个纯粹的单子 -The core spirit of a Monad says that it must be valid for all values and cannot do any inspection of the value, at all -- not even a null check. So those other implementations are cutting corners for the sake of convenience. It's not a huge deal, but when it comes to learning something, you should probably learn it in its purest form first before you go bending the rules. +Monad的核心是它必须对所有值都有效,并且不能对值进行任何检查——甚至不能进行空检查。所以其他的实现都是为了方便而偷工减料。这并不是什么大事,但是当涉及到学习一些东西的时候,你应该先以最纯粹的形式来学习,然后再去改变规则。 -The earlier implementation of the Maybe monad I provided differs from other Maybes primarily in that it does not have the empty-check in it. Also, we present `Maybe` merely as a loose pairing of `Just(..)`/`Nothing()`. +我提供的Maybe monad的早期实现与其他Maybe的主要区别在于它没有空检查。此外,我们将`Maybe`表示为`Just(..)`/`Nothing()`的松散组合。 -So wait. If we don't get the automatic short-circuiting, why is Maybe useful at all?!? That seems like its whole point. +所以等等。如果我们没有自动短路,为什么Maybe有用呢?!?这似乎就是它的全部意义。 -Never fear! We can simply provide the empty-check externally, and the rest of the short-circuiting behavior of the Maybe monad will work just fine. Here's how you could do the nested-property access (`someObj.something.else.entirely`) from before, but more "correctly": +不要害怕!我们可以简单地在外部提供空检查,而Maybe monad的其他短路行为将正常工作。下面是如何实现以前的嵌套属性访问(`someObj.something.else.entirely`),但更“正确”: ```js function isEmpty(val) { @@ -278,19 +277,19 @@ Maybe.of( someObj ) .map( console.log ); ``` -We made a `safeProp(..)` that does the empty-check, and selects either a `Nothing()` monad instance if so, or wraps the value in a `Just(..)` instance (via `Maybe.of(..)`). Then instead of `map(..)`, we use `chain(..)` which knows how to "unwrap" the monad that `safeProp(..)` returns. +我们创建了一个`safeProp(..)`来执行空检查,如果是空检查,则选择`Nothing()` monad实例,或者将值包装在`Just(..)`实例中(通过`Maybe.of(..)`)。然后,我们不再使用`map(..)`,而是使用`chain(..)`,它知道如何“打开”`safeProp(..)`返回的单子。 -We get the same chain short-circuiting upon encountering an empty value. We just don't embed that logic into the Maybe. +当遇到空值时,我们会得到相同的链短路。我们只是不把这种逻辑嵌入Maybe中。 -The benefit of the monad, and Maybe specifically, is that our `map(..)` and `chain(..)` methods have a consistent and predictable interaction regardless of which kind of monad comes back. That's pretty cool! +monad的好处,可能更具体地说,是我们的 `map(..)`和`chain(..)`方法具有一致的和可预测的交互,不管哪种monad返回。这都很酷! -## Humble +## Humble 单子实例 -Now that we have a little more understanding of Maybe and what it does, I'm going to put a little twist on it -- and add some self-deferential humor to our discussion -- by inventing the Maybe+Humble monad. Technically, `MaybeHumble(..)` is not a monad itself, but a factory function that produces a Maybe monad instance. +既然我们对“Maybe”和它的作用有了更多的了解,我将对它进行一点小小的改动——并在我们的讨论中加入一些自我幽默——通过发明Maybe+Humble monad。从技术上讲,`MaybeHumble(..)`本身不是一个单子,而是一个工厂函数,它生成一个可能的单子实例。 -Humble is an admittedly contrived data structure wrapper that uses Maybe to track the status of an `egoLevel` number. Specifically, `MaybeHumble(..)`-produced monad instances only operate affirmatively if their ego-level value is low enough (less than `42`!) to be considered humble; otherwise it's a `Nothing()` no-op. That should sound a lot like Maybe; it's pretty similar! +Humble是一个公认的人为设计的数据结构包装器,它可能用于跟踪`egoLevel`数字的状态。具体地说,`MaybeHumble(..)`—生成的monad实例只有在其自我级别值足够低(小于`42`!)到被认为是humble时才会进行肯定的操作;否则它就是一个`Nothing()`、无操作定义。这听起来很像Maybe;真的非常像! -Here's the factory function for our Maybe+Humble monad: +下面是我们的Maybe+Humble monad的工厂函数: ```js function MaybeHumble(egoLevel) { @@ -301,9 +300,9 @@ function MaybeHumble(egoLevel) { } ``` -You'll notice that this factory function is kinda like `safeProp(..)`, in that it uses a condition to decide if it should pick the `Just(..)` or the `Nothing()` part of the Maybe. +您会注意到,这个工厂函数有点像`safeProp(..)`,因为它使用一个条件来决定应该选择`Just(..)`还是 `Nothing()`作为Maybe的一部分。 -Let's illustrate some basic usage: +让我们来说明一些基本用法: ```js var bob = MaybeHumble( 45 ); @@ -313,7 +312,7 @@ bob.inspect(); // Nothing alice.inspect(); // Just(39) ``` -What if Alice wins a big award and is now a bit more proud of herself? +如果Alice赢得了一个大奖,现在对自己更自豪了呢? ```js function winAward(ego) { @@ -324,9 +323,9 @@ alice = alice.chain( winAward ); alice.inspect(); // Nothing ``` -The `MaybeHumble( 39 + 3 )` call creates a `Nothing()` monad instance to return back from the `chain(..)` call, so now Alice doesn't qualify as humble anymore. +`MaybeHumble( 39 + 3 )`调用创建一个`Nothing()`monad实例从 `chain(..)`调用返回,因此现在Alice不再符合humble的条件。 -Now, let's use a few monads together: +在,让我们一起使用几个单子: ```js var bob = MaybeHumble( 41 ); @@ -340,11 +339,11 @@ bob.map( teamMembers ).ap( alice ); // Our humble team's egos: 41 39 ``` -Recalling the usage of `ap(..)` from earlier, we can now explain how this code works. +回顾前面`ap(..)`的用法,我们现在可以解释这段代码是如何工作的。 -Because `teamMembers(..)` is curried, the `bob.map(..)` call passes in the `bob` ego level (`41`), and creates a monad instance with the remaining function wrapped up. Calling `ap(alice)` on *that* monad calls `alice.map(..)` and passes to it the function from the monad. The effect is that both the `bob` and `alice` monad's numeric values have been provided to `teamMembers(..)` function, printing out the message as shown. +因为`teamMembers(..)`是curried,所以`bob.map(..)`调用传递到`bob`,并创建一个monad实例,其中封装了剩余的函数。在monad调用`ap(alice)`,调用`alice.map(..)` ,并将函数从monad传递给它。其效果是,`bob`和`alice`monad的数值都提供给了`teamMembers(..)`函数,打印出了如下所示的消息。 -However, if either or both monads are actually `Nothing()` instances (because their ego level was too high): +然而,如果其中一个或两个单子实际上是`Nothing()` 实例: ```js var frank = MaybeHumble( 45 ); @@ -356,11 +355,11 @@ frank.map( teamMembers ).ap( bob ); // ..no output.. ``` -`teamMembers(..)` never gets called (and no message is printed), because `frank` is a `Nothing()` instance. That's the power of the Maybe monad, and our `MaybeHumble(..)` factory allows us to select based on the ego level. Cool! +`teamMembers(..)`永远不会被调用(也不会打印消息),因为`frank`是一个`Nothing()`实例。这就是Maybe monad的力量,而我们的`MaybeHumble(..)`允许我们进行选择。太酷了! -### Humility +### Humility实例 -One more example to illustrate the behaviors of our Maybe+Humble data structure: +再举一个例子来说明我们的Maybe+Humble数据结构的行为: ```js function introduction() { @@ -388,9 +387,9 @@ learner // ..nothing else.. ``` -Unfortunately, the learning process seems to have been cut short. You see, I've found that learning a bunch of stuff without sharing with others inflates your ego too much and is not good for your skills. +不幸的是,学习过程似乎被缩短了。你看,我发现学习一堆东西而不与他人分享会让你的自我膨胀得太厉害,对你的技能没有好处。 -Let's try a better approach to learning: +让我们尝试一个更好的学习方法: ```js var share = egoChange( -2 ); @@ -416,12 +415,12 @@ learner // I'm just a learner like you! :) ``` -Sharing while you learn. That's the best way to learn more and learn better. +学习时分享。这是学得更多、学得更好的最好方法。 -## Summary +## 总结 -What is a monad, anyway? A monad is a value type, an interface, an object data structure with encapsulated behaviors. +什么是单子?monad是具有封装行为的值类型、接口和对象数据结构。 -But none of those definitions are particularly useful. Here's an attempt at something better: **a monad is how you organize behavior around a value in a more declarative way.** +但这些定义都不是特别有用。这里有一个更好的尝试:** monad是您如何以更声明的方式围绕一个值去组织行为方式。 -As with everything else in this book, use monads where they are helpful but don't use them just because everyone else talks about them in FP. Monads aren't a universal silver bullet, but they do offer some utility when used conservatively. +就像这本书里的其他内容一样,在有用的地方使用单子,但不要仅仅因为别人在FP中谈论它们就使用单子。monad并不是万能的灵丹妙药,但如果使用得比较保守,它们确实提供了一些实用功能。 From aa961b2c41fd26e01ff4812aad16434b06952c82 Mon Sep 17 00:00:00 2001 From: Siming Date: Sun, 15 Sep 2019 22:38:36 +0800 Subject: [PATCH 61/63] Update README.md --- manuscript/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuscript/README.md b/manuscript/README.md index 412fc951..90a846bd 100644 --- a/manuscript/README.md +++ b/manuscript/README.md @@ -80,7 +80,7 @@ * [首先弄清楚“为什么”](apA.md/#why-first) * [How, Next](apA.md/#how-next) * [What, Finally](apA.md/#what-finally) -* [附言 B: 卑微的单元](apB.md/#appendix-b-the-humble-monad) +* [附言 B: 卑微的单子](apB.md/#appendix-b-the-humble-monad) * [Type](apB.md/#type) * [Loose Interface](apB.md/#loose-interface) * [Just a Monad](apB.md/#just-a-monad) From 0135824eaaed843dfd5e1e81cfa28286cb2ce316 Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 16 Sep 2019 09:16:45 +0800 Subject: [PATCH 62/63] Update apC.md --- manuscript/apC.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/manuscript/apC.md b/manuscript/apC.md index e6a10deb..3034846d 100644 --- a/manuscript/apC.md +++ b/manuscript/apC.md @@ -8,7 +8,7 @@ ## 需关注的工具库(注:我的理解) -让我们展开第1章 [list of FP libraries to be aware of, from Chapter 1](ch1.md/#libraries),我们不可能涵盖所有这些内容(可能有很多相似),但以下应该是你要关注的库: +让我们展开第1章 [需要注意的FP库列表,从第1章开始](ch1.md/#libraries),我们不可能涵盖所有这些内容(可能有很多相似),但以下应该是你要关注的库: * [Ramda](http://ramdajs.com): 通用的FP工具库 * [Sanctuary](https://github.com/sanctuary-js/sanctuary): 类似Ramda的FP工具库 @@ -30,7 +30,7 @@ Fantasy Land (FL)与“轻量函数式编程”的概念几乎完全相反 ## Ramda (0.23.0) (注:目前最新版本是0.26.1) -来自 [Ramda documentation](http://ramdajs.com/): +来自 [Ramda 文件](http://ramdajs.com/): > Ramda函数是自动被柯里化的. > @@ -38,7 +38,7 @@ Fantasy Land (FL)与“轻量函数式编程”的概念几乎完全相反 我发现合理设计是Ramda的优势之一。还需要注意的是,Ramda的柯里化形式(似乎与大多数库一样)是 [我们第3章讨论的“松散柯里化”](ch3.md/#user-content-loosecurry)。 -回想一下,[在第3章最后的示例](ch3.md/#user-content-finalshortlong),我们定义一个无点函数`printif(..) ` -- 在Ramda中可以这样定义: +回想一下,[在第3章最后的示例](ch3.md/#user-content-finalshortlong),我们定义一个无参函数`printif(..) ` -- 在Ramda中可以这样定义: ```js function output(msg) { @@ -246,13 +246,13 @@ Promise.all( images ) 不幸的是,这个“技巧”只有在你要同时执行所有异步步骤(而不是串行,一个接一个)时才有效,并且只有当操作是一个`map(..)`调用时才有效。 如果你想要串行异步操作,或者你想同事使用`filter(..)`方法,这将可能返回错乱结果。 -And some operations naturally require serial asynchrony, like for example an asynchronous `reduce(..)`, which clearly needs to work left-to-right one at a time; those steps can't be run concurrently and have that operation make any sense. +有些操作自然需要串行异步,例如异步`reduce(..)`,它显然需要一次从左到右工作;这些步骤不能同时运行,并且不能让该操作有任何意义。 -As I said, Observables (see [Chapter 10](ch10.md/#observables)) aren't the answer to these kinds of tasks. The reason is, an Observable's coordination of asynchrony is between separate operations, not between steps/iterations at a single level of operation. +正如我所说,可观察性(参见[第10章](ch10.md/#observables))不是这类任务的答案。原因是,一个可观察对象的异步协调是在单独的操作之间进行的,而不是在单个操作级别的步骤/迭代之间进行的。 -Another way to visualize this distinction is that Observables support "vertical asynchrony", whereas what I'm talking about would be "horizontal asynchrony". +另一种可视化这种区别的方法是,可观测支持“垂直异步”,而我所说的是“水平异步”。 -Consider: +考虑: ```js var obsv = Rx.Observable.from( [1,2,3,4,5] ); @@ -270,15 +270,13 @@ obsv // 11 ``` -If for some reason I wanted to ensure that there was a delay of 100 ms between when `1` was processed by the first `map(..)` and when `2` was processed, that would be the "horizontal asynchrony" I'm referring to. There's not really a clean way to model that. +如果出于某种原因,我想确保从第一个`map(..)`处理`1`到处理`2`之间有100毫秒的延迟,那么这就是我所指的“水平异步”。没有一种明朗的方法来模拟它。 -And of course, I'm using an arbitrary delay in that description, but in practice that would more likely be serial-asynchrony like an asynchronous reduce, where each step in that reduction iteration could take some time before it completes and lets the next step be processed. +那么,我们如何跨异步操作同时支持串行迭代和并发迭代呢? -So, how do we support both serial and concurrent iteration across asynchronous operations? +**fasy**(发音与“Tracy”相似,但带有“f”)是我为支持这类任务而构建的一个小实用程序库。你可以在这里找到更多关于它的[信息](https://github.com/getify/fasy)。 -**fasy** (pronounced like "Tracy" but with an "f") is a little utility library I built for supporting exactly those kinds of tasks. You can find more information about it [here](https://github.com/getify/fasy). - -To illustrate **fasy**, let's consider a concurrent `map(..)` versus a serial `map(..)`: +为了说明**fasy**,让我们考虑一个并发的`map(..)`与一个串行的`map(..)`: ```js FA.concurrent.map( fetchImage, imageURLs ) @@ -292,15 +290,15 @@ FA.serial.map( fetchImage, imageURLs ) } ); ``` -In both cases, the `then(..)` handler will only be invoked once all the fetches have fully completed. The difference is whether the fetches will all initiate concurrently (aka, "in parallel") or go out one at a time. +在这两种情况下,`then(..)`处理程序只会在所有获取完全完成后调用。不同之处在于,所有的获取是同时启动(也就是“并行”),还是一次发出一个的。 -Your instinct might be that concurrent would always be preferable, and while that may be common, it's not always the case. +你的直觉可能是同时进行总是更好的,虽然这可能是常见的,但并不总是这样。 -For example, what if `fetchImage(..)` maintains a cache of fetched images, and it checks the cache before making the actual network request? What if, in addition to that, the list of `imageURLs` could have duplicates in it? You'd certainly want the first fetch of an image URL to complete (and populate the cache) before doing the check on the duplicate image URL later in the list. +例如,如果 `fetchImage(..)`维护一个获取图像的缓存,并在发出实际的网络请求之前检查缓存,结果会怎样?除此之外,如果“imageURLs”列表中可以有多个副本呢?在检查列表中稍后的重复图像URL之前,您肯定希望完成图像URL的第一次获取(并填充缓存)。 -Again, there will inevitably be cases where concurrent or serial asynchrony will be called for. Asynchronous reductions will always be serial, whereas asynchronous mappings may likely tend to be more concurrent but can also need to be serial in some cases. That's why **fasy** supports all these options. +同样,在某些情况下不可避免地需要并发或串行异步。异步缩减总是串行的,而异步映射可能更倾向于并发,但在某些情况下也可能需要串行。这就是为什么**fasy**支持所有这些选项。 -Along with Observables, **fasy** will help you extend more FP patterns and principles to your asynchronous operations. +除了Observables,**fasy**将帮助您将更多fp模式和原则扩展到异步操作。 ## 总结 From 81aee73698af5c3f143d3d749d1d884a66b7fe4e Mon Sep 17 00:00:00 2001 From: Siming Date: Mon, 30 Sep 2019 16:59:41 +0800 Subject: [PATCH 63/63] update C5 (#26) * Update ch4.md * Update preface.md * Update foreword.md * Update apA.md * Update README.md * Update apA.md * Update apB.md * Update README.md * Update apC.md * Update ch5.md * Update ch5.md * Update ch6.md * Update ch6.md * Update ch7.md * Update ch8.md * Update ch8.md --- manuscript/ch5.md | 484 +++++++++++++++++++++++----------------------- manuscript/ch6.md | 270 +++++++++++++------------- manuscript/ch7.md | 185 +++++++++--------- manuscript/ch8.md | 317 +++++++++++++++--------------- 4 files changed, 628 insertions(+), 628 deletions(-) diff --git a/manuscript/ch5.md b/manuscript/ch5.md index 256850d5..86a18f31 100644 --- a/manuscript/ch5.md +++ b/manuscript/ch5.md @@ -1,23 +1,22 @@ -# Functional-Light JavaScript -# Chapter 5: Reducing Side Effects +# 章节 5: 减少副作用影响 -In [Chapter 2](ch2.md), we discussed how a function can have outputs besides its `return` value. By now you should be very comfortable with the FP definition of a function, so the idea of such side outputs -- side effects! -- should smell. +在[第2章](ch2.md)中,我们讨论了一个函数除了它的`return`值之外,还可以有哪些输出。到目前为止,您应该对函数的FP定义非常熟悉了,所以对这种副作用应该有印象。 -We're going to examine the various different forms of side effects and see why they are harmful to our code's quality and readability. +我们将研究各种不同形式的副作用,并看看它们为什么对代码的质量和可读性有害。 -But let me not bury the lede here. The punchline to this chapter: it's impossible to write a program with no side effects. Well, not impossible; you certainly can. But that program won't do anything useful or observable. If you wrote a program with zero side effects, you wouldn't be able to tell the difference between it and an empty program. +本章的重点是:不可能编写一个没有副作用的程序。并非不可能;你当然可以。但这个程序不会做任何有用或可观察的事情。如果你写了一个没有副作用的程序,你就不能分辨出它和一个空程序的区别。 -The FPer doesn't eliminate all side effects. Rather, the goal is to limit them as much as possible. To do that, we first need to fully understand them. +FP函数编程人员并不能消除所有副作用。相反,我们的目标是尽可能地限制它们。要做到这一点,我们首先需要完全理解它们。 ## Effects on the Side, Please -Cause and effect: one of the most fundamental, intuitive observations we humans can make about the world around us. Push a book off the edge of a table, it falls to the ground. You don't need a physics degree to know that the cause was you pushing the book and the effect was gravity pulling it to the ground. There's a clear and direct relationship. +因果关系:人类对周围世界最基本、最直观的观察之一。把一本书从桌子边上推下去,它掉到地上了。你不需要物理学位就能知道原因是你推了书,结果是重力把书拖到地上。这是一种明确而直接的关系。 -In programming, we also deal entirely in cause and effect. If you call a function (cause), it displays a message on the screen (effect). +在编程中,我们也处理因果关系。如果您调用一个函数(原因),它将在屏幕上显示一条消息(效果)。 -When reading a program, it's supremely important that the reader be able to clearly identify each cause and each effect. To any extent where a direct relationship between cause and effect cannot be seen readily upon a read-through of the program, that program's readability is degraded. +当阅读一个程序时,读者能够清楚地识别每一个原因和每一个结果是极其重要的。如果在程序的通读过程中不能很容易地看出因果之间的直接关系,那么程序的可读性就会下降。 -Consider: +考虑: ```js function foo(x) { @@ -27,9 +26,9 @@ function foo(x) { var y = foo( 3 ); ``` -In this trivial program, it is immediately clear that calling foo (the cause) with value `3` will have the effect of returning the value `6` that is then assigned to `y` (the effect). There's no ambiguity here. +在这个简单的程序中,很明显,用值`3`调用foo (原因)会产生返回值`6` 的效果,然后将值赋给`y` (结果)。这里没有歧义。 -But now: +现在改变下: ```js function foo(x) { @@ -41,21 +40,21 @@ var y; foo( 3 ); ``` -This program has the exact same outcome. But there's a very big difference. The cause and the effect are disjoint. The effect is indirect. The setting of `y` in this way is what we call a side effect. +这个程序有完全相同的结果。但是有一个很大的区别。因果是不相交的。这种影响是间接的。这样设置 `y`就是我们所说的副作用。 -**Note:** When a function makes a reference to a variable outside itself, this is called a free variable. Not all free variable references will be bad, but we'll want to be very careful with them. +注:函数在自身外部引用变量时,称为自由变量。并不是所有的自由变量引用都是不好的,但是我们要非常小心地使用它们。 -What if I gave you a reference to call a function `bar(..)` that you cannot see the code for, but I told you that it had no such indirect side effects, only an explicit `return` value effect? +如果我给您一个引用来调用一个函数`bar(..)`,但是您看不到它的代码,但是我告诉您它没有这种间接的副作用,只有一个显式的`return`值效果,那会怎么样呢? ```js bar( 4 ); // 42 ``` -Because you know that the internals of `bar(..)` do not create any side effects, you can now reason about any `bar(..)` call like this one in a much more straightforward way. But if you didn't know that `bar(..)` had no side effects, to understand the outcome of calling it, you'd have to go read and dissect all of its logic. This is extra mental tax burden for the reader. +因为您知道`bar(..)`的内部机制不会产生任何副作用,所以现在您可以以一种更直接的方式推断任何`bar(..)`调用。但如果你不知道`bar(..)`没有副作用,要理解调用它的结果,你就必须阅读并剖析它的所有逻辑。这对读者来说是额外的精神负担。 -**The readability of a side effecting function is worse** because it requires more reading to understand the program. +**副作用函数的可读性较差**,因为它需要更多的阅读来理解程序。 -But the problem goes deeper than that. Consider: +但问题远不止于此。考虑: ```js var x = 1; @@ -73,19 +72,19 @@ baz(); console.log( x ); ``` -How sure are you which values are going to be printed at each `console.log(x)`? +您如何确定在每个`console.log(x)`上打印哪些值? -The correct answer is: not at all. If you're not sure whether `foo()`, `bar()`, and `baz()` are side-effecting or not, you cannot guarantee what `x` will be at each step unless you inspect the implementations of each, **and** then trace the program from line 1 forward, keeping track of all the changes in state as you go. +正确答案是:一点也不。如果你不确定是否`foo()`, `bar()`, 和 `baz()`的副作用,你不能保证每一步打印的`x`具体值,除非你检查从第1行开始跟踪程序,跟踪状态的所有变化。 -In other words, the final `console.log(x)` is impossible to analyze or predict unless you've mentally executed the whole program up to that point. +换句话说,最后的`console.log(x)`是不可能分析或预测的,除非您已经在心里执行了整个程序。 -Guess who's good at running your program? The JS engine. Guess who's not as good at running your program? The reader of your code. And yet, your choice to write code (potentially) with side effects in one or more of those function calls means that you've burdened the reader with having to mentally execute your program in its entirety up to a certain line, for them to read and understand that line. +猜猜谁最擅长运行你的程序?JS引擎。猜猜谁不擅长运行你的程序?读你代码的人。然而,您选择在一个或多个函数调用中编写(潜在的)具有副作用的代码,这意味着您必须在一定程度上在读者的脑海中完整地执行到某一行,以便他们阅读和理解这一行。 -If `foo()`, `bar()`, and `baz()` were all free of side effects, they could not affect `x`, which means we do not need to execute them to mentally trace what happens with `x`. This is less mental tax, and makes the code more readable. +如果`foo()`, `bar()`和`baz()`都没有副作用,那么它们就不能影响`x`”,这意味着我们不需要从心理上跟踪执行`x`发生了什么。这减少了脑力劳动,并使代码更具可读性。 -### Hidden Causes +### 隐藏的原因 -Outputs, changes in state, are the most commonly cited manifestation of side effects. But another readability-harming practice is what some refer to as side causes. Consider: +输出,状态的变化,是副作用最常见的表现形式。但另一种损害可读性的做法是一些人所说的副作用。考虑: ```js function foo(x) { @@ -97,7 +96,8 @@ var y = 3; foo( 1 ); // 4 ``` -`y` is not changed by `foo(..)`, so it's not the same kind of side effect as we saw before. But now, the calling of `foo(..)` actually depends on the presence and current state of a `y`. If later, we do: +`y`没有被`foo(..)`改变,所以它的副作用和我们之前看到的不一样。但是现在,`foo(..)`的调用实际上取决于`y`的存在和当前状态。如果稍后,我们这样做: + ```js y = 5; @@ -107,17 +107,17 @@ y = 5; foo( 1 ); // 6 ``` -Might we be surprised that the call to `foo(1)` returned different results from call to call? +对于`foo(1)`的调用在不同的调用之间返回不同的结果,我们可能会感到惊讶吗? -`foo(..)` has an indirection of cause that is harmful to readability. The reader cannot see, without inspecting `foo(..)`'s implementation carefully, what causes are contributing to the output effect. It *looks* like the argument `1` is the only cause, but it turns out it's not. +`foo(..)`有一个间接的原因,这对可读性是有害的。如果没有仔细检查`foo(..)`的实现,读者无法看到是什么原因导致了输出效果。看起来参数`1`是唯一的原因,但事实并非如此。 -To aid readability, all of the causes that will contribute to determining the effect output of `foo(..)` should be made as direct and obvious inputs to `foo(..)`. The reader of the code will clearly see the cause(s) and effect. +为了提高可读性,所有决定`foo(..)`输出效果的因素都应该作为`foo(..)`的直接且明显的输入。代码的读者将清楚地看到原因和结果。 -#### Fixed State +#### 固定状态 -Does avoiding side causes mean the `foo(..)` function cannot reference any free variables? +避免副作用是否意味着`foo(..)` 函数不能引用任何自由变量? -Consider this code: +考虑这段代码: ```js function foo(x) { @@ -130,14 +130,13 @@ function bar(x) { foo( 3 ); // 9 ``` +很明显,对于`foo(..)`和 `bar(..)`,唯一的直接原因是`x`参数。但是`bar(x)`调用呢?`bar`只是一个标识符,在JS中它甚至不是一个常量(也就是不可重分配的变量)。`foo(..)`函数依赖于`bar`的值——一个引用第二个函数的变量——作为一个自由变量。 -It's clear that for both `foo(..)` and `bar(..)`, the only direct cause is the `x` parameter. But what about the `bar(x)` call? `bar` is just an identifier, and in JS it's not even a constant (aka, non-reassignable variable) by default. The `foo(..)` function is relying on the value of `bar` -- a variable that references the second function -- as a free variable. - -So is this program relying on a side cause? +那么,这段代码是否有一个副作用呢? -I say no. Even though it is *possible* to overwrite the `bar` variable's value with some other function, I am not doing so in this code, nor is it a common practice of mine or precedent to do so. For all intents and purposes, my functions are constants (never reassigned). +没有。尽管可以用其他函数覆盖`bar`变量的值,但在这段代码中我没有这样做,这也不是我的常见做法或先例。实际上,我的函数是常量(从不重新赋值)。 -Consider: +考虑: ```js const PI = 3.141592; @@ -149,45 +148,45 @@ function foo(x) { foo( 3 ); // 9.424776000000001 ``` -**Note:** JavaScript has `Math.PI` built-in, so we're only using the `PI` example in this text as a convenient illustration. In practice, always use `Math.PI` instead of defining your own! +注:JavaScript有内置函数`Math.PI`,所以我们只是使用`PI`的例子在这篇文章作为一个方便的说明。在实践中,总是使用`Math.PI`。而不是使用自定义变量! -How about the preceding code snippet? Is `PI` a side cause of `foo(..)`? +那么前面的代码片段呢?`PI`是`foo(..)`的一个副作用吗? -Two observations will help us answer that question in a reasonable way: +有两项观察将有助于我们以合理的方式回答这个问题: -1. Think about every call you might ever make to `foo(3)`. Will it always return that `9.424..` value? **Yes.** Every single time. If you give it the same input (`x`), it will always return the same output. +1. 想想你可能给 `foo(3)`做的每一个回调。它总是返回`9.424..`值? 是的。每一次。如果您给它相同的输入(`x`),它总是返回相同的输出。 -2. Could you replace every usage of `PI` with its immediate value, and could the program run **exactly** the same as it did before? **Yes.** There's no part of this program that relies on being able to change the value of `PI` -- indeed since it's a `const`, it cannot be reassigned -- so the `PI` variable here is only for readability/maintenance sake. Its value can be inlined without any change in program behavior. +2. 你能不能把PI的每一个用法都替换成它的直接值,程序能不能像以前一样运行呢?能。本程序没有任何部分依赖于能够更改`PI`的值——实际上,由于它是一个`const`声明变量,所以不能重新分配它——所以这里的`PI`'变量只是为了可读性/维护性。它的值可以内联而不改变程序行为。 -My conclusion: `PI` here is not a violation of the spirit of minimizing/avoiding side effects (or causes). Nor is the `bar(x)` call in the previous snippet. +我的结论是:这里的`PI`变量并没有违反最小化/避免副作用的精神。前面代码片段中的`bar(x)`调用也没有。 -In both cases, `PI` and `bar` are not part of the state of the program. They're fixed, non-reassigned references. If they don't change throughout the program, we don't have to worry about tracking them as changing state. As such, they don't harm our readability. And they cannot be the source of bugs related to variables changing in unexpected ways. +在这两种情况下,`PI`和`bar`都不是程序状态的一部分。它们是固定的、非重分配的引用。如果它们在整个程序中没有变化,我们就不必担心跟踪它们作为变化状态。因此,它们不会损害我们的可读性。而且它们不可能是与以意想不到的方式变化的变量相关的bug的来源。 -**Note:** The use of `const` here does not, in my opinion, make the case that `PI` is absolved as a side cause; `var PI` would lead to the same conclusion. The lack of reassigning `PI` is what matters, not the inability to do so. We'll discuss [`const` in Chapter 6](ch6.md/#reassignment). +注:此处使用`const`一词,在我看来,并不能说明`PI`可以避免副作用;`var PI`也会得出同样的结论。重要的不是不能重新分配`PI`,而是不能重新分配`PI`。我们将在第6章讨论[const](ch6.md/#reassignment)。 -#### Randomness +#### 随机性 -You may never have considered it before, but randomness is a side cause. A function that uses `Math.random()` cannot have predictable output based on its input. So any code that generates unique random IDs/etc. will by definition be considered reliant on the program's side causes. +你可能从来没有考虑过,但随机性是一个副作用。使用`Math.random()`的函数不能根据其输入获得可预测的输出。任何生成唯一随机id的代码。根据定义,将被认为是程序的副作用。 -In computing, we use what's called pseudo-random algorithms for generation. Turns out true randomness is pretty hard, so we just kinda fake it with complex algorithms that produce values that seem observably random. These algorithms calculate long streams of numbers, but the secret is, the sequence is actually predictable if you know the starting point. This starting point is referred to as a seed. +在计算中,我们使用伪随机算法来生成。事实证明,真正的随机性是相当困难的,所以我们只是用一些复杂的算法来伪造它,这些算法产生的值看起来明显是随机的。这些算法计算长串的数字,但秘密是,如果你知道起始点,序列实际上是可以预测的。这个起点称为seed(种子)。 -Some languages let you specify the seed value for the random number generation. If you always specify the same seed, you'll always get the same sequence of outputs from subsequent "pseudo-random number" generations. This is incredibly useful for testing purposes, for example, but incredibly dangerous for real-world application usage. +有些语言允许指定随机数生成的种子值。如果总是指定相同的种子,那么从后续的“伪随机数”中总会得到相同的输出序列。这对于测试目的非常有用,但是对于实际应用程序的使用非常危险。 -In JS, the randomness of `Math.random()` calculation is based on an indirect input, because you cannot specify the seed. As such, we have to treat built-in random number generation as a side cause. +在JS中,`Math.random()`计算的随机性是基于间接输入的,因为不能指定种子。因此,我们必须把内置随机数生成当作一个副作用。 -### I/O Effects +### I/O 作用 -The most common (and essentially unavoidable) form of side cause/effect is input/output (I/O). A program with no I/O is totally pointless, because its work cannot be observed in any way. Useful programs must at a minimum have output, and many also need input. Input is a side cause and output is a side effect. +最常见的(本质上也是不可避免的)副作用形式是输入/输出(I/O)。没有I/O的程序是完全没有意义的,因为它的工作不能以任何方式被观察到。有用的程序必须至少有输出,而且许多程序还需要输入。输入是副作用,输出也是副作用。 -The typical input for the browser JS programmer is user events (mouse, keyboard), and for output is the DOM. If you work more in Node.js, you may more likely receive input from, and send output to, the file system, network connections, and/or the `stdin`/`stdout` streams. +浏览器的JS程序典型输入是用户事件(鼠标、键盘),输出是DOM。如果您在Node中工作得更多。您可能更有可能从文件系统、网络连接和/或`stdin`/`stdout`流接收输入,并将输出发送到这些流。 -As a matter of fact, these sources can be both input and output, both cause and effect. Take the DOM, for example. We update (side effect) a DOM element to show text or an image to the user, but the current state of the DOM is an implicit input (side cause) to those operations as well. +事实上,这些来源既可以是输入,也可以是输出,既有因果关系。以DOM为例。我们更新(副作用)DOM元素以向用户显示文本或图像,但是DOM的当前状态也是这些操作的隐式输入(副作用)。 -### Side Bugs +### 副作用 Bug -The scenarios where side causes and side effects can lead to bugs are as varied as the programs in existence. But let's examine a scenario to illustrate these hazards, in hopes that they help us recognize similar mistakes in our own programs. +可能导致bug的副作用和副作用的场景与现有的程序一样多种多样。但是让我们检查一个场景来说明这些危险,希望它们能帮助我们在自己的程序中识别出类似的错误。 -Consider: +考虑: ```js var users = {}; @@ -204,7 +203,7 @@ function fetchOrders(userId) { `http://some.api/orders/${userId}`, function onOrders(orders){ for (let order of orders) { - // keep a reference to latest order for each user + // 为每个用户保留对最新订单的引用 users[userId].latestOrder = order; userOrders[order.orderId] = order; } @@ -216,7 +215,7 @@ function deleteOrder(orderId) { var user = users[ userOrders[orderId].userId ]; var isLatestOrder = (userOrders[orderId] == user.latestOrder); - // deleting the latest order for a user? + // 删除用户的最新订单? if (isLatestOrder) { hideLatestOrderDisplay(); } @@ -225,7 +224,7 @@ function deleteOrder(orderId) { `http://some.api/delete/order/${orderId}`, function onDelete(success){ if (success) { - // deleted the latest order for a user? + // 删除用户的最新订单? if (isLatestOrder) { user.latestOrder = null; } @@ -240,13 +239,13 @@ function deleteOrder(orderId) { } ``` -I bet for some readers one of the potential bugs here is fairly obvious. If the callback `onOrders(..)` runs before the `onUserData(..)` callback, it will attempt to add a `latestOrder` property to a value (the `user` object at `users[userId]`) that's not yet been set. +我敢打赌,对于一些读者来说,其中一个潜在的bug是相当明显的。如果回调`onOrders(..)`运行在`onUserData(..)`回调之前,它将尝试向尚未设置的值(位于`users[userId]`的`user`对象)添加一个`latestOrder`属性。 -So one form of "bug" that can occur with logic that relies on side causes/effects is the race condition of two different operations (async or not!) that we expect to run in a certain order but under some cases may run in a different order. There are strategies for ensuring the order of operations, and it's fairly obvious that order is critical in that case. +因此,依赖于副作用的逻辑可能出现的一种形式的“bug”是两个不同操作的竞态条件(异步或不异步!),我们希望这两个操作以特定的顺序运行,但在某些情况下可能以不同的顺序运行。有一些策略可以确保操作的顺序,在这种情况下,顺序非常重要。 -Another more subtle bug can bite us here. Did you spot it? +在这会出现另外一种难以发现的bug,你会发现吗? -Consider this order of calls: +考虑这个调用顺序: ```js fetchUserData( 123 ); @@ -262,33 +261,33 @@ onOrders(..); onDelete(..); ``` -Do you see the interleaving of `fetchOrders(..)` and `onOrders(..)` with the `deleteOrder(..)` and `onDelete(..)` pair? That potential sequencing exposes a weird condition with our side causes/effects of state management. +您是否看到`fetchOrders(..)`和`onOrders(..)`与`deleteOrder(..)`和`onDelete(..)`交织在一起?这种潜在的排序暴露了一种奇怪的情况,即状态管理的副作用。 -There's a delay in time (because of the callback) between when we set the `isLatestOrder` flag and when we use it to decide if we should empty the `latestOrder` property of the user data object in `users`. During that delay, if `onOrders(..)` callback fires, it can potentially change which order value that user's `latestOrder` references. When `onDelete(..)` then fires, it will assume it still needs to unset the `latestOrder` reference. +在设置`isLatestOrder`标志和使用它来决定是否清空`users`中用户数据对象的`latestOrder` 属性之间存在时间延迟(由于回调)。在延迟期间,如果`onOrders(..)`回调触发,它可能会更改该用户的`latestOrder`引用的订单值。当`onDelete(..)`触发时,它将假定仍然需要取消`latestOrder`引用的设置。 -The bug: the data (state) *might* now be out of sync. `latestOrder` will be unset, when potentially it should have stayed pointing at a newer order that came in to `onOrders(..)`. +存在bug:数据(状态)*现在可能*不同步。`latestOrder`可能没有被设置,此时它可能应该一直指向`onOrders(..)`的新订单。 -The worst part of this kind of bug is that you don't get a program-crashing exception like we did with the other bug. We just simply have state that is incorrect; our application's behavior is "silently" broken. +这类bug最糟糕的部分是,您不会像我们处理另一个bug那样获得一个程序崩溃异常。我们只是有不正确的状态;我们的应用程序的行为被“悄悄地”破坏了。 -The sequencing dependency between `fetchUserData(..)` and `fetchOrders(..)` is fairly obvious, and straightforwardly addressed. But the potential sequencing dependency between `fetchOrders(..)` and `deleteOrder(..)` is far less obvious. These two seem to be more independent. And ensuring that their order is preserved is more tricky, because you don't know in advance (before the results from `fetchOrders(..)`) whether that sequencing really must be enforced. +`fetchUserData(..)`和`fetchOrders(..)`之间的顺序依赖关系相当明显,并且可以直接处理。但是`fetchOrders(..)`和`deleteOrder(..)`之间潜在的排序依赖关系就不那么明显了。这两者似乎更独立。而确保它们的顺序被保留则更加棘手,因为您事先不知道(在`fetchOrders(..)`的结果出现之前)是否真的必须强制执行排序。 -Yes, you can recompute the `isLatestOrder` flag once `deleteOrder(..)` fires. But now you have a different problem: your UI state can be out of sync. +是的,一旦`deleteOrder(..)`触发,您可以重新计算`isLatestOrder`标志。但现在有一个不同的问题:UI状态可能不同步。 -If you had called the `hideLatestOrderDisplay()` previously, you'll now need to call the function `showLatestOrderDisplay()`, but only if a new `latestOrder` has in fact been set. So you'll need to track at least three states: was the deleted order the "latest" originally, and is the "latest" set, and are those two orders different? These are solvable problems, of course. But they're not obvious by any means. +如果您以前调用过 `hideLatestOrderDisplay()` ,你现在需要调用`showLatestOrderDisplay()`函数,但前提是一个新的`latestOrder`实际上已经被设置好了。所以你至少需要跟踪三种状态:被删除的订单是最初的“最新的”吗,是“最新的”集合吗,这两个订单不同吗?当然,这些都是可以解决的问题。但无论如何都不明显。 -All of these hassles are because we decided to structure our code with side causes/effects on a shared set of state. +所有这些麻烦都是因为我们决定在代码结构中使用共享状态集上的副作用。 -Functional programmers detest these sorts of side cause/effect bugs because of how much it hurts our ability to read, reason about, validate, and ultimately **trust** the code. That's why they take the principle to avoid side causes/effects so seriously. +函数式程序员讨厌这些副作用,因为它极大地损害了我们阅读、推理、验证和最终信任代码的能力。这就是为什么他们如此认真地对待避免副作用的原则。 -There are multiple different strategies for avoiding/fixing side causes/effects. We'll talk about some later in this chapter, and others in later chapters. I'll say one thing for certain: **writing with side causes/effects is often of our normal default** so avoiding them is going to require careful and intentional effort. +有多种不同的策略可以避免/修复副作用。我们将在本章后面讨论一些,其他的将在后面的章节中讨论。我可以肯定的说:带有副作用的写作通常是我们默认的写作方式,所以避免它们需要小心和有意识的努力。 -## Once Is Enough, Thanks +## 一次就够了,谢谢 -If you must make side effect changes to state, one class of operations that's useful for limiting the potential trouble is idempotence. If your update of a value is idempotent, then data will be resilient to the case where you might have multiple such updates from different side effect sources. +如果必须对状态进行副作用更改,对于限制潜在问题有用的一类操作是幂等性。如果值的更新是幂等的,那么数据将能够适应来自不同副作用源的多个此类更新。 -If you try to research it, the definition of idempotence can be a little confusing; mathematicians use a slightly different meaning than programmers typically do. However, both perspectives are useful for the functional programmer. +如果你试图研究它,幂等性的定义可能有点令人困惑;数学家使用的含义与程序员通常使用的含义略有不同。但是,这两个观点对函数式程序员都很有用。 -First, let's give a counter example that is neither mathematically nor programmingly idempotent: +首先,让我们给出一个反例,它既不是数学上的幂等,也不是程序上的幂等: ```js function updateCounter(obj) { @@ -301,13 +300,13 @@ function updateCounter(obj) { } ``` -This function mutates an object via reference by incrementing `obj.count`, so it produces a side effect on that object. If `updateCounter(o)` is called multiple times -- while `o.count` is less than `10`, that is -- the program state changes each time. Also, the output of `updateCounter(..)` is a Boolean, which is not suitable to feed back into a subsequent call of `updateCounter(..)`. +这个函数通过增加`obj.count`来通过引用修改对象。这会对这个对象产生副作用。如果多次调用`updateCounter(o)`,则`o.count`小于`10`,即程序状态每次都在变化。此外,`updateCounter(..)`的输出是一个布尔值,不适合反馈到`updateCounter(..)`的后续调用。 -### Mathematical Idempotence +### 数学幂等性 -From the mathematical point of view, idempotence means an operation whose output won't ever change after the first call, if you feed that output back into the operation over and over again. In other words, `foo(x)` would produce the same output as `foo(foo(x))` and `foo(foo(foo(x)))`. +从数学的角度来看,幂等性意味着一个操作,它的输出在第一次调用之后永远不会改变,如果您将该输出一次又一次地反馈到该操作中。换句话说,`foo(x)`将产生与`foo(foo(x))`和`foo(foo(foo(x)))`相同的输出。 -A typical mathematical example is `Math.abs(..)` (absolute value). `Math.abs(-2)` is `2`, which is the same result as `Math.abs(Math.abs(Math.abs(Math.abs(-2))))`. Other idempotent mathematical utilities include: +一个典型的数学例子是`Math.abs(..)`(绝对值)。`Math.abs(-2)`等于`2`,这与`Math.abs(Math.abs(Math.abs(Math.abs(-2))))`的结果相同。其他幂等数学工具包括: * `Math.min(..)` * `Math.max(..)` @@ -315,7 +314,7 @@ A typical mathematical example is `Math.abs(..)` (absolute value). `Math.abs(-2) * `Math.floor(..)` * `Math.ceil(..)` -Some custom mathematical operations we could define with this same characteristic: +我们可以用同样的特征定义一些自定义数学运算: ```js function toPower0(x) { @@ -331,7 +330,7 @@ toPower0( 3 ) == toPower0( toPower0( 3 ) ); // true snapUp3( 3.14 ) == snapUp3( snapUp3( 3.14 ) ); // true ``` -Mathematical-style idempotence is **not** restricted to mathematical operations. Another place we can illustrate this form of idempotence is with JavaScript primitive type coercions: +数学形式的幂等性并不局限于数学运算。我们可以用JavaScript基本类型强制来说明这种形式的幂等性: ```js var x = 42, y = "hello"; @@ -341,13 +340,13 @@ String( x ) === String( String( x ) ); // true Boolean( y ) === Boolean( Boolean( y ) ); // true ``` -Earlier in the text, we explored a common FP tool that fulfills this form of idempotence: +在本文的前面,我们探讨了一种常见的FP工具,它可以实现这种形式的幂等性: ```js identity( 3 ) === identity( identity( 3 ) ); // true ``` -Certain string operations are also naturally idempotent, such as: +某些字符串操作也是自然幂等的,比如: ```js function upper(x) { @@ -365,7 +364,7 @@ upper( str ) == upper( upper( str ) ); // true lower( str ) == lower( lower( str ) ); // true ``` -We can even design more sophisticated string formatting operations in an idempotent way, such as: +我们甚至可以用幂等的方式设计更复杂的字符串格式化操作,比如: ```js function currency(val) { @@ -381,54 +380,54 @@ currency( -3.1 ); // "-$3.10" currency( -3.1 ) == currency( currency( -3.1 ) ); // true ``` -`currency(..)` illustrates an important technique: in some cases the developer can take extra steps to normalize an input/output operation to ensure the operation is idempotent where it normally wouldn't be. +`currency(..)`说明了一种重要的技术:在某些情况下,开发人员可以采取额外的步骤对输入/输出操作进行规范化,以确保该操作在通常不会出现的情况下是幂等的。 -Wherever possible, restricting side effects to idempotent operations is much better than unrestricted updates. +在可能的情况下,将副作用限制为幂等操作要比不受限制的更新好得多。 -### Programming Idempotence +### 编程幂等性 -The programming-oriented definition for idempotence is similar, but less formal. Instead of requiring `f(x) === f(f(x))`, this view of idempotence is just that `f(x);` results in the same program behavior as `f(x); f(x);`. In other words, the result of calling `f(x)` subsequent times after the first call doesn't change anything. +面向编程的幂等性定义类似,但不太正式。不需要 `f(x) === f(f(x))`,这种幂等性的观点就是`f(x);`导致与`f(x); f(x);`相同的程序行为;`f(x)`换句话说,在第一次调用之后的后续调用`f(x)`的结果不会改变任何东西。 -That perspective fits more with our observations about side effects, because it's more likely that such an `f(..)` operation creates an idempotent side effect rather than necessarily returning an idempotent output value. +这种观点更符合我们对副作用的观察,因为这种`f(..)`操作更有可能产生幂等副作用,而不一定返回幂等输出值。 -This idempotence-style is often cited for HTTP operations (verbs) such as GET or PUT. If an HTTP REST API is properly following the specification guidance for idempotence, PUT is defined as an update operation that fully replaces a resource. As such, a client could either send a PUT request once or multiple times (with the same data), and the server would have the same resultant state regardless. +这种幂等样式经常用于HTTP操作(动词),如GET或PUT。如果HTTP REST API正确地遵循了幂等性的规范指导,那么PUT被定义为一个完全替换资源的更新操作。因此,客户机可以发送一次或多次PUT请求(使用相同的数据),无论如何,服务器都将具有相同的结果状态。 -Thinking about this in more concrete terms with programming, let's examine some side effect operations for their idempotence (or lack thereof): +用编程的更具体的术语来考虑这个问题,让我们检查一些副作用操作的幂等性(或缺幂等性): ```js -// idempotent: +// 幂等性: obj.count = 2; a[a.length - 1] = 42; person.name = upper( person.name ); -// non-idempotent: +// 无幂等性: obj.count++; a[a.length] = 42; person.lastUpdated = Date.now(); ``` -Remember: the notion of idempotence here is that each idempotent operation (like `obj.count = 2`) could be repeated multiple times and not change the program state beyond the first update. The non-idempotent operations change the state each time. +记住:这里幂等性的概念是指每个幂等操作(如`obj.count = 2`)可以重复多次,并且在第一次更新之后不会更改程序状态。非幂等运算每次都会改变状态。 -What about DOM updates? +DOM更新吗? ```js var hist = document.getElementById( "orderHistory" ); -// idempotent: +// 幂等性: hist.innerHTML = order.historyText; -// non-idempotent: +// 无幂等性: var update = document.createTextNode( order.latestUpdate ); hist.appendChild( update ); ``` -The key difference illustrated here is that the idempotent update replaces the DOM element's content. The current state of the DOM element is irrelevant, because it's unconditionally overwritten. The non-idempotent operation adds content to the element; implicitly, the current state of the DOM element is part of computing the next state. +这里说明的关键区别是,幂等更新替换了DOM元素的内容。DOM元素的当前状态无关紧要,因为它被无条件地覆盖。非幂等操作向元素添加内容;隐式地,DOM元素的当前状态是计算下一个状态的一部分。 -It won't always be possible to define your operations on data in an idempotent way, but if you can, it will definitely help reduce the chances that your side effects will crop up to break your expectations when you least expect it. +以幂等的方式定义数据操作并不总是可能的,但如果可以,它肯定有助于减少在您最不期望的时候突然出现的副作用打破预期的可能性。 -## Pure Bliss +## 纯函数的美好 -A function with no side causes/effects is called a pure function. A pure function is idempotent in the programming sense, because it cannot have any side effects. Consider: +没有副作用的函数称为纯函数。纯函数在编程意义上是幂等的,因为它不会有任何副作用。考虑: ```js function add(x,y) { @@ -436,9 +435,9 @@ function add(x,y) { } ``` -All the inputs (`x` and `y`) and outputs (`return ..`) are direct; there are no free variable references. Calling `add(3,4)` multiple times would be indistinguishable from only calling it once. `add(..)` is pure and programming-style idempotent. +所有输入(`x`和`y`)和输出(`return ..`)都是直接的;没有自由变量引用。多次调用`add(3,4)`与只调用一次没有什么区别。`add(..)`是纯的、编程风格的幂等函数。 -However, not all pure functions are idempotent in the mathematical sense, because they don't have to return a value that would be suitable for feeding back in as their own input. Consider: +然而,并非所有纯函数在数学意义上都是幂等的,因为它们不必返回一个适合作为它们自己的输入进行反馈的值。考虑: ```js function calculateAverage(nums) { @@ -452,11 +451,11 @@ function calculateAverage(nums) { calculateAverage( [1,2,4,7,11,16,22] ); // 9 ``` -The output `9` is not an array, so you cannot pass it back in: `calculateAverage(calculateAverage( .. ))`. +输出`9`不是一个数组,因此不能将它传递回:`calculateAverage(calculateAverage( .. ))`。 -As we discussed earlier, a pure function *can* reference free variables, as long as those free variables aren't side causes. +正如我们前面讨论的,纯函数*可以*引用自由变量,只要这些自由变量不是副作用。 -Some examples: +一些例子: ```js const PI = 3.141592; @@ -470,9 +469,10 @@ function cylinderVolume(radius,height) { } ``` -`circleArea(..)` references the free variable `PI`, but it's a constant so it's not a side cause. `cylinderVolume(..)` references the free variable `circleArea`, which is also not a side cause because this program treats it as, in effect, a constant reference to its function value. Both these functions are pure. +`circleArea(..)`引用自由变量`PI,但它是一个常数,所以它不是一个次要原因。`cylinderVolume(..)`引用自由变量`circleArea`,这也不是一个副作用,因为这个程序实际上把它当作一个常量,引用它的函数值。这两个函数都是纯函数。 -Another example where a function can still be pure but reference free variables is with closure: + +另一个例子,一个函数仍然可以是纯的,但引用自由变量是闭包: ```js function unary(fn) { @@ -482,25 +482,25 @@ function unary(fn) { } ``` -`unary(..)` itself is clearly pure -- its only input is `fn` and its only output is the `return`ed function -- but what about the inner function `onlyOneArg(..)`, which closes over the free variable `fn`? +`unary(..)`本身显然是纯的——它惟一的输入是`fn`,惟一的输出是`return`函数——但是内部函数`onlyOneArg(..)`又如何呢? -It's still pure because `fn` never changes. In fact, we have full confidence in that fact because lexically speaking, those few lines are the only ones that could possibly reassign `fn`. +它仍然是纯的,因为`fn`永远不会改变。事实上,我们对这个事实充满信心,因为从词汇上讲,这几行是唯一可能重新分配`fn`的行。 -**Note:** `fn` is a reference to a function object, which is by default a mutable value. Somewhere else in the program *could*, for example, add a property to this function object, which technically "changes" the value (mutation, not reassignment). However, because we're not relying on anything about `fn` other than our ability to call it, and it's not possible to affect the callability of a function value, `fn` is still effectively unchanging for our reasoning purposes; it cannot be a side cause. +注:`fn`是对函数对象的引用,默认情况下,函数对象是一个可变值。例如,在程序*could*的其他地方,向这个函数对象添加一个属性,该属性在技术上“改变”了值(突变,而不是重新分配)。然而,因为我们不依赖于`fn`的任何东西,除了我们调用它的能力,而且它不可能影响函数值的可调用性,对于我们的推理目的来说,`fn`仍然是有效不变的;它不可能是一个次要原因。 -Another common way to articulate a function's purity is: **given the same input(s), it always produces the same output.** If you pass `3` to `circleArea(..)`, it will always output the same result (`28.274328`). +表达函数纯度的另一种常见方法是:给定相同的输入,它总是产生相同的输出。如果您将`3`传递给`circleArea(..)`,它将始终输出相同的结果(`28.274328`)。 -If a function *can* produce a different output each time it's given the same inputs, it is impure. Even if such a function always `return`s the same value, if it produces an indirect output side effect, the program state is changed each time it's called; this is impure. +如果一个函数在每次给定相同的输入时都能产生不同的输出,那么它就是不纯的。即使这样一个函数总是`return`相同的值,如果它产生间接输出副作用,程序状态每次被调用时都会改变;这是不纯的。 -Impure functions are undesirable because they make all of their calls harder to reason about. A pure function's call is perfectly predictable. When someone reading the code sees multiple `circleArea(3)` calls, they won't have to spend any extra effort to figure out what its output will be *each time*. +不纯函数是不受欢迎的,因为它们使所有调用都难以推理。一个纯函数的调用是完全可预测的。当阅读代码的人看到多个`circleArea(3)`调用时,他们就不需要花费额外的精力来计算每次的输出是什么了。 -**Note:** An interesting thing to ponder: is the heat produced by the CPU while performing any given operation an unavoidable side effect of even the most pure functions/programs? What about just the CPU time delay as it spends time on a pure operation before it can do another one? +注:值得思考的一件有趣的事情是:CPU在执行任何给定操作时产生的热量,即使是最纯粹的函数/程序,也会产生不可避免的副作用吗?那么CPU的时间延迟呢,因为它在一个纯操作上花费了时间,然后它才能执行另一个操作吗? -### Purely Relative +### 相对的纯 -We have to be very careful when talking about a function being pure. JavaScript's dynamic value nature makes it all too easy to have non-obvious side causes/effects. +当我们讨论纯函数时,我们必须非常小心。JavaScript的动态值特性使得它很容易产生不明显的副作用。 -Consider: +考虑: ```js function rememberNumbers(nums) { @@ -514,9 +514,9 @@ var list = [1,2,3,4,5]; var simpleList = rememberNumbers( list ); ``` -`simpleList(..)` looks like a pure function, as it's a reference to the inner function `caller(..)`, which just closes over the free variable `nums`. However, there's multiple ways that `simpleList(..)` can actually turn out to be impure. +`simpleList(..)`看起来像一个纯函数,因为它是对内部函数`caller(..)`的引用,后者只是在自由变量`nums`上关闭。然而,`simpleList(..)`实际上有多种方法是不纯的。 -First, our assertion of purity is based on the array value (referenced both by `list` and `nums`) never changing: +首先,我们对纯度的判断是基于数组值(list和nums都引用了)不变: ```js function median(nums) { @@ -534,13 +534,13 @@ list.push( 6 ); simpleList( median ); // 3.5 ``` -When we mutate the array, the `simpleList(..)` call changes its output. So, is `simpleList(..)` pure or impure? Depends on your perspective. It's pure for a given set of assumptions. It could be pure in any program that didn't have the `list.push(6)` mutation. +当我们改变数组时,`simpleList(..)`调用会改变它的输出。那么,`simpleList(..)`是纯的还是不纯的呢?这取决于你的观点。对于给定的一组假设它是纯的。它可以在任何没有`list.push(6)`突变的程序中是纯的。 -We could guard against this kind of impurity by altering the definition of `rememberNumbers(..)`. One approach is to duplicate the `nums` array: +我们可以通过改变`rememberNumbers(..)`的定义来防止这种不纯。一种方法是复制`nums`数组: ```js function rememberNumbers(nums) { - // make a copy of the array + // 复制数组 nums = [...nums]; return function caller(fn){ @@ -549,12 +549,12 @@ function rememberNumbers(nums) { } ``` -But an even trickier hidden side effect could be lurking: +但一个更为棘手的隐藏副作用可能正在潜伏: ```js var list = [1,2,3,4,5]; -// make `list[0]` be a getter with a side effect +// 使`list[0]`成为具有副作用的getter Object.defineProperty( list, 0, @@ -570,7 +570,7 @@ var simpleList = rememberNumbers( list ); // [0] was accessed! ``` -A perhaps more robust option is to change the signature of `rememberNumbers(..)` to not receive an array in the first place, but rather the numbers as individual arguments: +一个可能更健壮的选项是改变`rememberNumbers(..)`的签名,首先不接收数组,而是将数字作为单独的参数: ```js function rememberNumbers(...nums) { @@ -583,14 +583,14 @@ var simpleList = rememberNumbers( ...list ); // [0] was accessed! ``` -The two `...`s have the effect of copying `list` into `nums` instead of passing it by reference. +两个`...`的作用是将`list`复制到`nums`中,而不是通过引用传递它。 -**Note:** The console message side effect here comes not from `rememberNumbers(..)` but from the `...list` spreading. So in this case, both `rememberNumbers(..)` and `simpleList(..)` are pure. +注:这里控制台消息的副作用不是来自于`rememberNumbers(..)`,而是来自于`...list`的扩展。因此,在这种情况下,`rememberNumbers(..)` 和`simpleList(..)`都是纯的。 -But what if the mutation is even harder to spot? Composition of a pure function with an impure function **always** produces an impure function. If we pass an impure function into the otherwise pure `simpleList(..)`, it's now impure: +但如果这种突变更难发现呢?纯函数与非纯函数的复合总是生成非纯函数。如果我们将一个不纯函数传递给纯的`simpleList(..)`,它现在是不纯的: ```js -// yes, a silly contrived example :) +// 看,一个愚蠢的人为的例子 :) function firstValue(nums) { return nums[0]; } @@ -606,22 +606,22 @@ list; // [1,2,3,4,5] -- OK! simpleList( lastValue ); // 1 ``` -**Note:** Despite `reverse()` looking safe (like other array methods in JS) in that it returns a reversed array, it actually mutates the array rather than creating a new one. +注:尽管`reverse()`看起来很安全(就像JS中的其他数组方法一样),因为它返回一个反向数组,但它实际上是在修改数组,而不是创建一个新的数组。 -We need a more robust definition of `rememberNumbers(..)` to guard against the `fn(..)` mutating its closed over `nums` via reference: +我们需要对`rememberNumbers(..)`有一个更强有力的定义,以防止`fn(..)`通过引用将它的关闭值更改为`nums`: ```js function rememberNumbers(...nums) { return function caller(fn){ - // send in a copy! + // 传递一个拷贝值! return fn( [...nums] ); }; } ``` -So is `simpleList(..)` reliably pure yet!? **Nope.** :( +`simpleList(..)`也是如此?不 :( -We're only guarding against side effects we can control (mutating by reference). Any function we pass that has other side effects will have polluted the purity of `simpleList(..)`: +我们只是在预防我们可以控制的副作用(通过引用进行变异)。我们传递的任何有其他副作用的函数都会污染`simpleList(..)`的纯度: ```js simpleList( function impureIO(nums){ @@ -629,21 +629,19 @@ simpleList( function impureIO(nums){ } ); ``` -In fact, there's no way to define `rememberNumbers(..)` to make a perfectly pure `simpleList(..)` function. - -Purity is about confidence. But we have to admit that in many cases, **any confidence we feel is actually relative to the context** of our program and what we know about it. In practice (in JavaScript) the question of function purity is not about being absolutely pure or not, but about a range of confidence in its purity. +事实上,没有办法定义`rememberNumbers(..)`来生成一个完全纯的`simpleList(..)`函数。 -The more pure, the better. The more effort you put into making a function pure(r), the higher your confidence will be when you read code that uses it, and that will make that part of the code more readable. +纯就是自信。但我们必须承认,在很多情况下,我们所感受到的任何自信,实际上都与我们所处的环境以及我们对它的了解有关。在实践中(在JavaScript中),函数纯度的问题不在于是否绝对纯净,而在于对函数纯度的一系列信心。 -## There or Not +越纯净越好。您在使函数 pure(r)方面投入的精力越多,当您阅读使用它的代码时,您的信心就会越高,这将使代码的这一部分更具可读性。 -So far, we've defined function purity both as a function without side causes/effects and as a function that, given the same input(s), always produces the same output. These are just two different ways of looking at the same characteristics. +## 有或者无 -But a third way of looking at function purity, and perhaps the most widely accepted definition, is that a pure function has referential transparency. +到目前为止,我们已经将函数纯度定义为一个没有副作用的函数,以及一个给定相同输入始终产生相同输出的函数。这只是看待相同特征的两种不同方式。 -Referential transparency is the assertion that a function call could be replaced by its output value, and the overall program behavior wouldn't change. In other words, it would be impossible to tell from the program's execution whether the function call was made or its return value was inlined in place of the function call. +引用透明性是这样一种断言,即函数调用可以被它的输出值替换,而整个程序行为不会发生变化。换句话说,不可能从程序的执行中看出函数调用是被执行的,还是它的返回值内联在函数调用的位置上。 -From the perspective of referential transparency, both of these programs have identical behavior as they are built with pure functions: +从引用透明性的角度来看,这两个程序都具有相同的行为,因为它们都是用纯函数构建的: ```js function calculateAverage(nums) { @@ -677,27 +675,27 @@ var avg = 9; console.log( "The average is:", avg ); // The average is: 9 ``` -The only difference between these two snippets is that in the latter one, we skipped the `calculateAverage(nums)` call and just inlined its output (`9`). Since the rest of the program behaves identically, `calculateAverage(..)` has referential transparency, and is thus a pure function. +这两个代码段之间唯一的区别是,在后一个代码段中,我们跳过了`calculateAverage(nums)`调用,而只是将它的输出内联起来(`9`)。由于程序的其他部分行为相同,`calculateAverage(..)`具有引用透明性,因此是一个纯函数。 -### Mentally Transparent +### 精神上的透明清晰 -The notion that a referentially transparent pure function *can be* replaced with its output does not mean that it *should literally be* replaced. Far from it. +引用透明的纯函数*可以用其输出替换*的概念并不意味着它*应该按字面意思替换。远非如此。 -The reasons we build functions into our programs instead of using pre-computed magic constants are not just about responding to changing data, but also about readability with proper abstractions. The function call to calculate the average of that list of numbers makes that part of the program more readable than the line that just assigns the value explicitly. It tells the story to the reader of where `avg` comes from, what it means, and so on. +我们在程序中构建函数而不是使用预先计算好的神奇常量,这不仅仅是为了响应不断变化的数据,还与适当抽象的可读性有关。计算该数字列表平均值的函数调用使程序的这一部分比仅显式赋值的行更具可读性。它向读者讲述了`avg`的由来、含义等等。 -What we're really suggesting with referential transparency is that as you're reading a program, once you've mentally computed what a pure function call's output is, you no longer need to think about what that exact function call is doing when you see it in code, especially if it appears multiple times. +我们真正表明引用透明性是,当你阅读程序,一旦你精神上计算纯函数调用的输出是什么,你不再需要思考,确切的函数调用是做什么当你看到它的代码,特别是如果它出现很多次了。 -That result becomes kinda like a mental `const` declaration, which as you're reading you can transparently swap in and not spend any more mental energy working out. +这个结果就像一个精神上的`const`声明,当你读它的时候,你可以透明地切换进去,而不需要花费更多的精神能量。 -Hopefully the importance of this characteristic of a pure function is obvious. We're trying to make our programs more readable. One way we can do that is to give the reader less work, by providing assistance to skip over the unnecessary stuff so they can focus on the important stuff. +希望这个纯函数特征的重要性是显而易见的。我们正努力使我们的程序更具可读性。我们可以做的一种方式是减少读者的工作量,通过提供帮助来跳过不必要的内容,这样他们就可以专注于重要的内容。 -The reader shouldn't need to keep re-computing some outcome that isn't going to change (and doesn't need to). If you define a pure function with referential transparency, the reader won't have to. +读者不应该不断地重新计算一些不会改变(也不需要)的结果。如果定义了具有引用透明性的纯函数,读者就不必这样做了。 -### Not So Transparent? +### 不透明的? -What about a function that has a side effect, but this side effect isn't ever observed or relied upon anywhere else in the program? Does that function still have referential transparency? +如果一个函数有副作用,但是这个副作用在程序的任何其他地方都没有被观察到或依赖,那该怎么办?该函数仍然具有引用透明性吗? -Here's one: +这有个例子: ```js function calculateAverage(nums) { @@ -714,26 +712,26 @@ var numbers = [1,2,4,7,11,16,22]; var avg = calculateAverage( numbers ); ``` -Did you spot it? +你发现了吗? -`sum` is an outer free variable that `calculateAverage(..)` uses to do its work. But, every time we call `calculateAverage(..)` with the same list, we're going to get `9` as the output. And this program couldn't be distinguished in terms of behavior from a program that replaced the `calculateAverage(nums)` call with the value `9`. No other part of the program cares about the `sum` variable, so it's an unobserved side effect. +`sum`是一个外部自由变量, `calculateAverage(..)`使用它来完成工作。但是,每次调用具有相同列表的 `calculateAverage(..)`时,我们将得到输出`9`。这个程序在行为方面无法与用值`9`替换`calculateAverage(nums)` 调用的程序区别开来。程序的其他部分不关心`sum`变量,所以这是一个未观察到的副作用。 -Is a side cause/effect that's unobserved like this tree: +是否像这棵树一样无法观察到的副作用: -> If a tree falls in the forest, but no one is around to hear it, does it still make a sound? +> 如果一棵树倒在森林里,但周围没有人听见,它还会发出声音吗(可以认为他发出声音了吗)? -By the narrowest definition of referential transparency, I think you'd have to say `calculateAverage(..)` is still a pure function. However, because we're trying to avoid a strictly academic approach in favor of balancing it with pragmatism, I also think this conclusion needs more perspective. Let's explore. +根据对引用透明性最狭义的定义,我认为您不得不说`calculateAverage(..)`仍然是一个纯函数。然而,因为我们试图避免一个严格的学术方法,以平衡它与实用主义,我也认为这个结论需要更多的视角。让我们探索。 -#### Performance Effects +#### 性能影响 -You'll generally find these kind of side-effects-that-go-unobserved being used to optimize the performance of an operation. For example: +您通常会发现这些副作用,这些副作用被用于优化操作的性能。例如: ```js var cache = []; function specialNumber(n) { - // if we've already calculated this special number, - // skip the work and just return it from the cache + // 如果我们已经计算过这个特殊的数, + // 跳过,从缓存中返回它 if (cache[n] !== undefined) { return cache[n]; } @@ -756,27 +754,27 @@ specialNumber( 1E6 ); // 500001 specialNumber( 987654321 ); // 493827162 ``` -This silly `specialNumber(..)` algorithm is deterministic and thus pure from the definition that it always gives the same output for the same input. It's also pure from the referential transparency perspective -- replace any call to `specialNumber(42)` with `22` and the end result of the program is the same. +这样没头脑的`specialNumber(..)`算法是确定性的,因此从定义上看,它总是为相同的输入提供相同的输出。从引用透明性的角度来看,它也是纯的——将任何对`specialNumber(42)`的调用替换为 `22`,程序的最终结果是相同的。 -However, the function has to do quite a bit of work to calculate some of the bigger numbers, especially the `987654321` input. If we needed to get that particular special number multiple times throughout our program, the `cache`ing of the result means that subsequent calls are far more efficient. +然而,该函数必须做相当多的工作来计算一些较大的数字,特别是`987654321`输入。如果我们需要在整个程序中多次获得这个特定的特殊数字,结果的“缓存”意味着后续调用的效率要高得多。 -Don't be so quick to assume that you could just run the calculation `specialNumber(987654321)` once and manually stick that result in some variable/constant. Programs are often highly modularized and globally accessible scopes are not usually the way you want to share state between those independent pieces. Having `specialNumber(..)` do its own caching (even though it happens to be using a global variable to do so!) is a more preferable abstraction of that state sharing. +不要急于假设您可以只运行一次计算`specialNumber(987654321)`,然后手动将结果粘贴到某个变量/常量中。程序通常是高度模块化的,全局可访问范围通常不是您希望在这些独立部分之间共享状态的方式。让`specialNumber(..)`执行自己的缓存(即使它恰好使用全局变量来执行缓存!)是对状态共享的更可取的抽象。 -The point is that if `specialNumber(..)` is the only part of the program that accesses and updates the `cache` side cause/effect, the referential transparency perspective observably holds true, and this might be seen as an acceptable pragmatic "cheat" of the pure function ideal. +关键是,如果`specialNumber(..)`是程序中唯一访问和更新“缓存”端因果关系的部分,那么引用透明透视图显然是正确的,这可能被视为对纯函数理想的一种可接受的实用“欺骗”。 -But should it? +但应该吗? -Typically, this sort of performance optimization side effecting is done by hiding the caching of results so they *cannot* be observed by any other part of the program. This process is referred to as memoization. I always think of that word as "memorization"; I have no idea if that's even remotely where it comes from, but it certainly helps me understand the concept better. +通常,这种性能优化的副作用是通过隐藏结果的缓存来实现的,这样它们就“不能”被程序的任何其他部分观察到。这个过程称为记忆。我一直认为这个词是“memorization”;我不知道它的来源,但它确实帮助我更好地理解这个概念。 -Consider: +考虑: ```js var specialNumber = (function memoization(){ var cache = []; return function specialNumber(n){ - // if we've already calculated this special number, - // skip the work and just return it from the cache + // 如果我们已经计算过这个特殊的数, + // 跳过,从缓存中返回它 if (cache[n] !== undefined) { return cache[n]; } @@ -795,27 +793,27 @@ var specialNumber = (function memoization(){ })(); ``` -We've contained the `cache` side causes/effects of `specialNumber(..)` inside the scope of the `memoization()` IIFE, so now we're sure that no other parts of the program *can* observe them, not just that they *don't* observe them. +我们已经将`specialNumber(..)`的`cache`副作用包含在`memoization()`IIFE(立即执行函数表达式)的范围内,所以现在我们确定程序的其他部分*不能*干扰它们,而不仅仅是它们*不*干扰它们。 -That last sentence may seem like a subtle point, but actually I think it might be **the most important point of the entire chapter**. Read it again. +最后这句话似乎是一个微妙的观点,但实际上我认为它可能是**整章最重要的一点**。可以再读一遍。 -Recall this philosophical musing: +回想一下这个哲学思考: -> If a tree falls in the forest, but no one is around to hear it, does it still make a sound? +> 如果一棵树倒在森林里,但周围没有人听到,它还会发出声音吗(可以认为他发出声音了吗)? -Going with the metaphor, what I'm getting at is: whether the sound is made or not, it would be better if we never create a scenario where the tree can fall without us being around; we'll always hear the sound when a tree falls. +用这个比喻,我想说的是:不管声音有没有发出来,如果我们不创造一个场景,让树在没有我们的情况下倒下,那就更好了;当树倒下时,我们总是能听到声音。 -The purpose of reducing side causes/effects is not per se to have a program where they aren't observed, but to design a program where fewer of them are possible, because this makes the code easier to reason about. A program with side causes/effects that *just happen* to not be observed is not nearly as effective in this goal as a program that *cannot* observe them. +减少副作用的目的本身并不是要让一个程序不被观察到,而是要设计一个可以减少副作用的程序,因为这使代码更容易推理。在这个目标中,一个带有“恰好”没有被观察到的“副作用”的程序远不如一个“不能”观察到它们的程序有效。 -If side causes/effects can happen, the writer and reader must mentally juggle them. Make it so they can't happen, and both writer and reader will find more confidence over what can and cannot happen in any part. +如果有可能产生副作用,作者和读者必须在心理上同时处理它们。让它们不可能发生,这样作家和读者都会对任何部分能发生和不能发生的事情有更多的信心。 -## Purifying +## 重构不纯函数 -The first best option in writing functions is that you design them from the beginning to be pure. But you'll spend plenty of time maintaining existing code, where those kinds of decisions were already made; you'll run across a lot of impure functions. +编写函数的第一个最佳选择是从头开始设计函数。但是您将花费大量的时间来维护现有的代码,这些类型的决策已经做出;你会遇到很多不纯函数。 -If possible, refactor the impure function to be pure. Sometimes you can just shift the side effects out of a function to the part of the program where the call of that function happens. The side effect wasn't eliminated, but it was made more obvious by showing up at the call-site. +如果可能,将不纯函数重构为纯函数。有时,您可以将副作用从函数转移到程序中函数调用发生的部分。副作用并没有被消除,但是在调用点出现的副作用更加明显,更容易发现与理解。 -Consider this trivial example: +考虑这个简单的例子: ```js function addMaxNum(arr) { @@ -830,7 +828,7 @@ addMaxNum( nums ); nums; // [4,2,7,3,8] ``` -The `nums` array needs to be modified, but we don't have to obscure that side effect by containing it in `addMaxNum(..)`. Let's move the `push(..)` mutation out, so that `addMaxNum(..)` becomes a pure function, and the side effect is now more obvious: +需要修改`nums`数组,但我们不必通过将它包含在`addMaxNum(..)`中来掩盖这种副作用。让我们将`push(..)`突变移出,这样`addMaxNum(..)`就变成了一个纯函数,副作用现在更明显了: ```js function addMaxNum(arr) { @@ -847,17 +845,17 @@ nums.push( nums; // [4,2,7,3,8] ``` -**Note:** Another technique for this kind of task could be to use an immutable data structure, which we cover in the next chapter. +注:这类任务的另一种技术可能是使用不可变的数据结构,我们将在下一章中讨论。 -But what can you do if you have an impure function where the refactoring is not as easy? +但是,如果您有一个不纯的函数,而重构并不容易,那么您可以做什么呢? -You need to figure what kind of side causes/effects the function has. It may be that the side causes/effects come variously from lexical free variables, mutations-by-reference, or even `this` binding. We'll look at approaches that address each of these scenarios. +你需要弄清楚函数有什么样的副作用。这可能是因为副作用来自不同的词汇自由变量,引用的变异,甚至`this`绑定。我们将研究处理这些场景的方法。 -### Containing Effects +### 包含影响 -If the nature of the concerned side causes/effects is with lexical free variables, and you have the option to modify the surrounding code, you can encapsulate them using scope. +如果副作用的问题性质是词法自由变量,并且您可以选择修改周围的代码,那么您可以使用作用域封装它们。 -Recall: +回顾: ```js var users = {}; @@ -869,23 +867,23 @@ function fetchUserData(userId) { } ``` -One option for purifying this code is to create a wrapper around both the variable and the impure function. Essentially, the wrapper has to receive as input "the entire universe" of state it can operate on. +重构这段代码的一个选项是在变量和非纯函数周围创建一个包装器。本质上,包装器必须接收它可以操作的状态的“所有引用参数”作为输入。 ```js function safer_fetchUserData(userId,users) { - // simple, naive ES6+ shallow object copy, could also - // be done w/ various libs or frameworks + // 简单、朴素的ES6+浅对象复制,也可以 + // 使用各种库或框架 users = Object.assign( {}, users ); fetchUserData( userId ); - // return the copied state + // 返回复制的状态 return users; // *********************** - // original untouched impure function: + // 原始未变动的不纯函数: function fetchUserData(userId) { ajax( `http://some.api/user/${userId}`, @@ -897,19 +895,19 @@ function safer_fetchUserData(userId,users) { } ``` -**Warning:** `safer_fetchUserData(..)` is *more* pure, but is not strictly pure in that it still relies on the I/O of making an Ajax call. There's no getting around the fact that an Ajax call is an impure side effect, so we'll just leave that detail unaddressed. +警告: `safer_fetchUserData(..)`更纯粹一些,但并不完全纯粹,因为它仍然依赖于发出Ajax调用的I/O操作。Ajax调用是一个不纯的副作用,这是无法回避的事实,因此我们将不处理这个细节。 -Both `userId` and `users` are input for the original `fetchUserData`, and `users` is also output. The `safer_fetchUserData(..)` takes both of these inputs, and returns `users`. To make sure we're not creating a side effect on the outside when `users` is mutated, we make a local copy of `users`. + `userId` 和`users` 都是原始`fetchUserData`的输入,`users`也是输出。 `safer_fetchUserData(..)`接受这两个输入,并返回 `users`。为了确保 `users`发生突变时不会对外部产生副作用,我们创建了`users`的本地副本。 -This technique has limited usefulness mostly because if you cannot modify a function itself to be pure, you're not that likely to be able to modify its surrounding code either. However, it's helpful to explore it if possible, as it's the simplest of our fixes. +这种技术的用处有限,主要是因为如果不能将函数本身修改为纯函数,那么也不太可能修改其周围的代码。然而,如果可能的话,研究它是有帮助的,因为它是我们的修复中最简单的。 -Regardless of whether this will be a practical technique for refactoring to pure functions, the more important take-away is that function purity only need be skin deep. That is, the **purity of a function is judged from the outside**, regardless of what goes on inside. As long as a function's usage behaves pure, it is pure. Inside a pure function, impure techniques can be used -- in moderation! -- for a variety of reasons, including most commonly, for performance. It's not necessarily, as they say, "turtles all the way down". +无论这是否是重构纯函数的实用技术,更重要的是函数的纯性只需要表面功夫。也就是说,函数的**纯度是从外部**判断的,而不管函数内部发生了什么。只要函数的使用行为是纯的,它就是纯的。在纯函数内部,可以使用不纯的技术——适度使用!——出于各种各样的原因,包括最常见的表现原因。正如他们所说,这并不一定是“turtles all the way down”原则(总是惊人的相似)。 -Be very careful, though. Any part of the program that's impure, even if it's wrapped with and only ever used via a pure function, is a potential source of bugs and confusion for readers of the code. The overall goal is to reduce side effects wherever possible, not just hide them. +不过要非常小心。程序中任何不纯的部分,即使是用纯函数封装的,并且只通过纯函数使用,也会给代码的读者带来潜在的bug和混淆。总的目标是尽可能减少副作用,而不仅仅是隐藏它们。 -### Covering Up Effects +### 掩盖副作用 -Many times you will be unable to modify the code to encapsulate the lexical free variables inside the scope of a wrapper function. For example, the impure function may be in a third-party library file that you do not control, containing something like: +很多时候,您将无法修改代码来将词法自由变量封装在包装器函数的范围内。例如,不纯函数可能位于您无法控制的第三方库文件中,其中包含如下内容: ```js var nums = []; @@ -932,50 +930,50 @@ function generateMoreRandoms(count) { } ``` -The brute-force strategy to *quarantine* the side causes/effects when using this utility in the rest of our program is to create an interface function that performs the following steps: +当我们在程序使用这个工具时,“隔离”副作用的暴力策略是创建一个执行以下步骤的接口函数: -1. Capture the to-be-affected current states -2. Set initial input states -3. Run the impure function -4. Capture the side effect states -5. Restore the original states -6. Return the captured side effect states +1. 捕获要受影响的当前状态 +2. 设置初始输入状态 +3. 运行非纯函数 +4. 捕捉副作用状态 +5. 恢复初始状态 +6. 返回捕获的副作用状态 ```js function safer_generateMoreRandoms(count,initial) { - // (1) Save original state + // (1) 保存原始状态 var orig = { nums, smallCount, largeCount }; - // (2) Set up initial pre-side effects state + // (2) 设置初始预副作用状态 nums = [...initial.nums]; smallCount = initial.smallCount; largeCount = initial.largeCount; - // (3) Beware impurity! + // (3) 当心不纯性! generateMoreRandoms( count ); - // (4) Capture side effect state + // (4) 俘获副作用状态 var sides = { nums, smallCount, largeCount }; - // (5) Restore original state + // (5) 恢复初始状态 nums = orig.nums; smallCount = orig.smallCount; largeCount = orig.largeCount; - // (6) Expose side effect state directly as output + // (6) 直接将副作用状态显示为输出 return sides; } ``` -And to use `safer_generateMoreRandoms(..)`: +然后使用 `safer_generateMoreRandoms(..)`: ```js var initialStates = { @@ -992,21 +990,21 @@ smallCount; // 0 largeCount; // 0 ``` -That's a lot of manual work to avoid a few side causes/effects; it'd be a lot easier if we just didn't have them in the first place. But if we have no choice, this extra effort is well worth it to avoid surprises in our programs. +这需要大量的手工工作来避免一些副作用;如果我们一开始就没有它们,事情会简单得多。但是如果我们别无选择,那么为了避免程序中出现意外,这种额外的努力是非常值得的。 -**Note:** This technique really only works when you're dealing with synchronous code. Asynchronous code can't reliably be managed with this approach because it can't prevent surprises if other parts of the program access/modify the state variables in the interim. +注意:这项技术只在处理同步代码时才有效。异步代码不能用这种方法可靠地进行管理,因为如果程序的其他部分在此期间访问/修改状态变量,它就不能防止意外。 -### Evading Effects +### 规避效应 -When the nature of the side effect to be dealt with is a mutation of a direct input value (object, array, etc.) via reference, we can again create an interface function to interact with instead of the original impure function. +当要处理的副作用的本质是通过引用直接输入值(对象、数组等)的突变时,我们可以再次创建一个接口函数来与之交互,而不是原始的不纯函数。 -Consider: +考虑: ```js function handleInactiveUsers(userList,dateCutoff) { for (let i = 0; i < userList.length; i++) { if (userList[i].lastLogin == null) { - // remove the user from the list + // 从列表中删除用户 userList.splice( i, 1 ); i--; } @@ -1017,31 +1015,31 @@ function handleInactiveUsers(userList,dateCutoff) { } ``` -Both the `userList` array itself, plus the objects in it, are mutated. One strategy to protect against these side effects is to do a deep (well, just not shallow) copy first: +`userList`数组本身以及其中的对象都发生了变化。防止这些副作用的一个策略是先做一个深度的(好吧,只是不要做浅的)复制: ```js function safer_handleInactiveUsers(userList,dateCutoff) { - // make a copy of both the list and its user objects + // 复制列表及其用户对象 let copiedUserList = userList.map( function mapper(user){ - // copy a `user` object + // 复制一个`user`对象 return Object.assign( {}, user ); } ); - // call the original function with the copy + // 使用副本调用原始函数 handleInactiveUsers( copiedUserList, dateCutoff ); - // expose the mutated list as a direct output + // 将修改后的列表作为直接输出公开 return copiedUserList; } ``` -The success of this technique will be dependent on the thoroughness of the *copy* you make of the value. Using `[...userList]` would not work here, since that only creates a shallow copy of the `userList` array itself. Each element of the array is an object that needs to be copied, so we need to take extra care. Of course, if those objects have objects inside them (they might!), the copying needs to be even more robust. +这项技术的成功将取决于您对值的“复制”是否彻底。使用`[...userList]` 这里不能实现,因为这只创建了一个浅拷贝的`userList`数组本身。数组中的每个元素都是一个需要复制的对象,因此我们需要格外小心。当然,如果这些对象中有对象(可能有!),那么复制需要更加健壮的方法。 -### `this` Revisited +### 重新审视`this` -Another variation of the via-reference side cause/effect is with `this`-aware functions having `this` as an implicit input. See [Chapter 2, "What's This"](ch2.md/#whats-this) for more info on why the `this` keyword is problematic for FPers. +另一个导致副作用的原因是函数将`this`作为隐式输入。参见[第2章,"This指的是什么"](ch2.md/#whats-this)了解更多关于为什么`this`关键字的信息。 -Consider: +考虑: ```js var ids = { @@ -1052,7 +1050,7 @@ var ids = { }; ``` -Our strategy is similar to the previous section's discussion: create an interface function that forces the `generate()` function to use a predictable `this` context: +我们的策略类似于上一节的讨论:创建一个接口函数,强制`generate()`函数使用一个可预测的`this`上下文: ```js function safer_generate(context) { @@ -1065,16 +1063,16 @@ safer_generate( { prefix: "foo" } ); // "foo0.8988802158307285" ``` -These strategies are in no way fool-proof; the safest protection against side causes/effects is to not do them. But if you're trying to improve the readability and confidence level of your program, reducing the side causes/effects wherever possible is a huge step forward. +这些策略绝不是万无一失的;对副作用最安全的保护就是不去做。但是,如果您试图提高程序的可读性和可信度,那么尽可能减少副作用是一个巨大的进步。 -Essentially, we're not really eliminating side causes/effects, but rather containing and limiting them, so that more of our code is verifiable and reliable. If we later run into program bugs, we know that the parts of our code still using side causes/effects are the most likely culprits. +本质上,我们并没有真正消除副作用,而是包含并限制它们,这样我们的代码中就有更多是可验证和可靠的。如果我们后来遇到程序错误,我们知道代码中仍然使用副作用的部分最有可能是罪魁祸首。 -## Summary +## 总结 -Side effects are harmful to code readability and quality because they make your code much harder to understand. Side effects are also one of the most common *causes* of bugs in programs, because juggling them is hard. Idempotence is a strategy for restricting side effects by essentially creating one-time-only operations. +副作用对代码的可读性和质量是有害的,因为它们使代码更难理解。副作用也是程序中bug最常见的“原因”之一,因为处理它们很困难。幂等性本质上是通过创建一次操作来限制副作用的策略。 -Pure functions are how we best avoid side effects. A pure function is one that always returns the same output given the same input, and has no side causes or side effects. Referential transparency further states that -- more as a mental exercise than a literal action -- a pure function's call could be replaced with its output and the program would not have altered behavior. +纯功能是我们避免副作用的最佳方式。一个纯函数总是在给定相同输入的情况下返回相同的输出,并且没有副作用。引用透明性进一步指出,纯函数的调用可以用它的输出替换,程序不会改变行为,这更像是一种脑力练习,而不是文字操作。 -Refactoring an impure function to be pure is the preferred option. But if that's not possible, try encapsulating the side causes/effects, or creating a pure interface against them. +将不纯函数重构为纯函数是首选。但是如果这是不可能的,尝试封装含有副作用的函数,或者创建一个纯接口来处理它们。 -No program can be entirely free of side effects. But prefer pure functions in as many places as that's practical. Collect impure functions side effects together as much as possible, so that it's easier to identify and audit these most likely culprits of bugs when they arise. +没有一个程序可以完全没有副作用。但在实际应用中更喜欢纯函数。尽可能多地收集不纯函数的副作用,以便在出现bug时更容易识别和审计这些最有可能的罪魁祸首。 diff --git a/manuscript/ch6.md b/manuscript/ch6.md index 57d45645..dd79e463 100644 --- a/manuscript/ch6.md +++ b/manuscript/ch6.md @@ -1,24 +1,23 @@ -# Functional-Light JavaScript -# Chapter 6: Value Immutability +# 章节 6: 值的不变性质 -In [Chapter 5](ch5.md), we talked about the importance of reducing side causes/effects: the ways that your application's state can change unexpectedly and cause surprises (bugs). The fewer places we have with such landmines, the more confidence we have over our code, and the more readable it will be. Our topic for this chapter follows directly from that same effort. +在[第5章](ch5.md)中,我们讨论了减少副作用的重要性:应用程序的状态可能意外改变并导致意外(bug)的方式。使用这种地雷的地方越少,我们对代码就越有信心,代码的可读性也就越好。我们这一章的主题也是做同样的努力去减少副作用。 -If programming-style idempotence is about defining a value change operation so that it can only affect state once, we now turn our attention to the goal of reducing the number of change occurrences from one to zero. +如果编程风格的幂等性是关于定义一个值更改操作,以便它只能影响状态一次,那么现在我们将注意力转向将发生更改的次数从1减少到0的目标。 -Let's now explore value immutability, the notion that in our programs we use only values that cannot be changed. +现在让我们来探讨值的不变性,即在程序中我们只使用不可更改的值。 -## Primitive Immutability +## 原始类型的不变性 -Values of the primitive types (`number`, `string`, `boolean`, `null`, and `undefined`) are already immutable; there's nothing you can do to change them: +js原始类型(`number`, `string`, `boolean`, `null`, and `undefined`)的值已经是不可变的;你无法改变他们: ```js -// invalid, and also makes no sense +// 无效,也没有任何意义 2 = 2.5; ``` -However, JS does have a peculiar behavior which seems like it allows mutating such primitive type values: "boxing". When you access a property on certain primitive type values -- specifically `number`, `string`, and `boolean` -- under the covers JS automatically wraps (aka "boxes") the value in its object counterpart (`Number`, `String`, and `Boolean`, respectively). +然而,js确实有一个特殊的行为,看起来它允许改变这样的原始类型值:“boxing”(boxing所谓的装箱,是指将基本数据类型转换为对应的引用类型的操作。而装箱又分为隐式装箱和显式装箱)。当您访问某些基本类型值(特别是`number`, `string`和`boolean`)的属性时,js会自动将该值包装在其对象对应项(分别为`number`, `string`和`boolean`)中。 -Consider: +考虑: ```js var x = 2; @@ -29,22 +28,22 @@ x; // 2 x.length; // undefined ``` -Numbers do not normally have a `length` property available, so the `x.length = 4` setting is trying to add a new property, and it silently fails (or is ignored/discarded, depending on your point-of-view); `x` continues to hold the simple primitive `2` number. +数字通常没有`length`属性可用。`x.length = 4`设置试图添加一个新属性,但它会自动失败(或根据您的观点被忽略/丢弃);`x` 继续保持简单的基本数字`2`。 -But the fact that JS allows the `x.length = 4` statement to run at all can seem troubling, if for no other reason than its potential confusion to readers. The good news is, if you use strict mode (`"use strict";`), such a statement will throw an error. +但js允许`x.length = 4`语句运行看起来很麻烦,如果不是因为其他原因,读者可能会感到困惑。好消息是,如果使用strict模式(`"use strict";`),这样的语句将抛出错误。 -What if you try to mutate the explicitly boxed object representation of such a value? +如果您尝试改变这样一个值的显式装箱对象表示,会怎么样? ```js var x = new Number( 2 ); -// works fine +// 运行正常 x.length = 4; ``` -`x` in this snippet is holding a reference to an object, so custom properties can be added and changed without issue. +这个代码段中的`x`保存了对对象的引用,因此可以添加和更改自定义属性而不存在任何问题。 -The immutability of simple primitives like `number`s probably seems fairly obvious. But what about `string` values? JS developers have a very common misconception that strings are like arrays and can thus be changed. JS syntax even hints at them being "array like" with the `[ ]` access operator. However, strings are also immutable: +像`number`这样的简单原语的不可变性可能看起来相当明显。但是`string`值呢?JS开发人员有一个非常普遍的误解,认为字符串就像数组,因此可以更改。JS语法甚至暗示他们是“数组一样”与`[ ]访问操作符。然而,字符串也是不可变的: ```js var s = "hello"; @@ -57,9 +56,9 @@ s.length = 10; s; // "hello" ``` -Despite being able to access `s[1]` like it's an array, JS strings are not real arrays. Setting `s[1] = "E"` and `s.length = 10` both silently fail, just as `x.length = 4` did before. In strict mode, these assignments will fail, because both the `1` property and the `length` property are read-only on this primitive `string` value. +尽管能够像访问数组一样访问`s[1]`,但JS字符串并不是真正的数组。设置 `s[1] = "E"`和`s.length = 10`和之前做过`x.length = 4`一样,都自动失败了。在严格模式下,这些赋值将失败,因为`1`属性和`length`属性在这个基本`string`值上都是只读的。 -Interestingly, even the boxed `String` object value will act (mostly) immutable as it will throw errors in strict mode if you change existing properties: +有趣的是,即使是被框起来的`String`对象值也会(基本上)不可变,因为如果您更改现有属性,它会在严格模式下抛出错误: ```js "use strict"; @@ -74,13 +73,13 @@ s[42] = "?"; // OK s; // "hello" ``` -## Value to Value +## 此值到彼值 -We'll unpack this idea more throughout the chapter, but just to start with a clear understanding in mind: value immutability does not mean we can't have values change over the course of our program. A program without changing state is not a very interesting one! It also doesn't mean that our variables can't hold different values. These are all common misconceptions about value immutability. +我们将在本章中进一步解释这个概念,但是首先要有一个清晰的理解:值的不变性并不意味着我们不能在程序的过程中改变值。一个没有改变状态的程序不是一个很有趣的程序!这也不意味着我们的变量不能有不同的值。这些都是关于价值不变性的普遍误解。 -Value immutability means that *when* we need to change the state in our program, we must create and track a new value rather than mutate an existing value. +值不变性意味着*当*我们需要更改程序中的状态时,我们必须创建并跟踪一个新值,而不是更改一个现有值。 -For example: +例如: ```js function addValue(arr) { @@ -91,17 +90,17 @@ function addValue(arr) { addValue( [1,2,3] ); // [1,2,3,4] ``` -Notice that we did not change the array that `arr` references, but rather created a new array (`newArr`) that contains the existing values plus the new `4` value. +注意,我们没有更改`arr`引用的数组,而是创建了一个新数组(`newArr`),其中包含现有值加上新的`4` 值。 -Analyze `addValue(..)` based on what we discussed in [Chapter 5](ch5.md) about side causes/effects. Is it pure? Does it have referential transparency? Given the same array, will it always produce the same output? Is it free of both side causes and side effects? **Yes.** +根据我们在[第5章](ch5.md)中讨论的关于副作用的原因/影响来分析`addValue(..)`。它是纯洁的吗?它是否具有参考透明性?给定相同的数组,它总是会产生相同的输出吗?它有没有副作用和副作用?答案为:是的 -Imagine the `[1,2,3]` array represents a sequence of data from some previous operations and we stored in some variable. It is our current state. If we want to compute what the next state of our application is, we call `addValue(..)`. But we want that act of next-state computation to be direct and explicit. So the `addValue(..)` operation takes a direct input, returns a direct output, and avoids the side effect of mutating the original array that `arr` references. +假设`[1,2,3]`数组表示来自以前一些操作的数据序列,并且我们存储在某个变量中。这是我们目前的状态。如果我们想计算应用程序的下一个状态,我们调用`addValue(..)`。但是我们希望下一个状态的计算是直接和明确的。因此,`addValue(..)`操作接受直接输入,返回直接输出,并避免了修改`arr`引用的原始数组的副作用。 -This means we can calculate the new state of `[1,2,3,4]` and be fully in control of that transition of states. No other part of our program can unexpectedly transition us to that state early, or to another state entirely, like `[1,2,3,5]`. By being disciplined about our values and treating them as immutable, we drastically reduce the surface area of surprise, making our programs easier to read, reason about, and ultimately trust. +这意味着我们可以计算`[1,2,3,4]`的新状态,并完全控制状态的转换。程序的任何其他部分都不能意外地将我们提前转换到那个状态,或者完全转换到另一个状态,比如`[1,2,3,5]`。通过对我们的值进行约束并将它们视为不可变的,我们大大减少了令人惊讶的表面区域,使我们的程序更易于阅读、推理和最终信任。 -The array that `arr` references is actually mutable. We just chose not to mutate it, so we practiced the spirit of value immutability. +`arr`引用的数组实际上是可变的。我们只是选择不去改变它,所以我们实践了价值不变的精神。 -We can use the copy-instead-of-mutate strategy for objects, too. Consider: +我们也可以对对象使用复制而不是变异的策略。思考下: ```js function updateLastLogin(user) { @@ -117,15 +116,15 @@ var user = { user = updateLastLogin( user ); ``` -### Non-Local +### 非局部 -Non-primitive values are held by reference, and when passed as arguments, it's the reference that's copied, not the value itself. +非原始值由引用持有,当作为参数传递时,复制的是引用,而不是值本身。 -If you have an object or array in one part of the program, and pass it to a function that resides in another part of the program, that function can now affect the value via this reference copy, mutating it in possibly unexpected ways. +如果在程序的一个部分中有一个对象或数组,并将其传递给在程序另一个部分中的函数,那么该函数现在可以通过这个引用副本影响值,并以可能意想不到的方式对其进行修改。 -In other words, if passed as arguments, non-primitive values become non-local. Potentially the entire program has to be considered to understand whether such a value will be changed or not. +换句话说,如果作为参数传递,非原始值将变为非本地值。可能需要考虑整个程序来理解是否会更改这样的值。 -Consider: +思考: ```js var arr = [1,2,3]; @@ -135,193 +134,194 @@ foo( arr ); console.log( arr[0] ); ``` -Ostensibly, you're expecting `arr[0]` to still be the value `1`. But is it? You don't know, because `foo(..)` *might* mutate the array using the reference copy you pass to it. +从表面上看,您期望`arr[0]`仍然是值`1`。但真的是这样吗?您不知道,因为`foo(..)` *可能*使用传递给它的引用副本更改了数组。 -We already saw a trick in the previous chapter to avoid such a surprise: +我们在前一章已经看到了一个避免这种意外的技巧: ```js var arr = [1,2,3]; -foo( [...arr] ); // ha! a copy! +foo( [...arr] ); // 拷贝一个数组 console.log( arr[0] ); // 1 ``` -In a little bit, we'll see another strategy for protecting ourselves from a value being mutated out from underneath us unexpectedly. +稍后,我们将看到另一种策略,用于保护不受来自下面的值意外突变的影响。 -## Reassignment +## 重赋值 -How would you describe what a "constant" is? Think about that for a moment before you move on to the next paragraph. +你如何描述“常量”?在你进入下一段之前想一下这个问题。

* * * *

-Some of you may have conjured descriptions like, "a value that can't change", "a variable that can't be changed", or something similar. These are all approximately in the neighborhood, but not quite at the right house. The precise definition we should use for a constant is: a variable that cannot be reassigned. +你们中的一些人可能会这么描述,“一个不能改变的值”,“一个不能改变的变量”,或者类似的东西。意思相近,但不完全正确。我们应该对常量使用的精确定义是:不能重新分配的变量。 -This nitpicking is really important, because it clarifies that a constant actually has nothing to do with the value, except to say that whatever value a constant holds, that variable cannot be reassigned any other value. But it says nothing about the nature of the value itself. +这种吹毛求疵是非常重要的,因为它阐明了一个常量实际上与这个值无关,只是说,无论一个常量持有什么值,这个变量都不能被重新分配任何其他值。但它没有说明价值本身的性质。 -Consider: +思考: ```js var x = 2; ``` -Like we discussed earlier, the value `2` is an unchangeable (immutable) primitive. If I change that code to: +如前所述,值`2`是一个不可更改(不可变)的原始值。如果我把代码改成: ```js const x = 2; ``` -The presence of the `const` keyword, known familiarly as a "constant declaration", actually does nothing at all to change the nature of `2`; it's already unchangeable, and it always will be. +`const`关键字的出现,通常被称为“常量声明”,实际上根本没有改变`2`的性质;它只是变得不可改变的,而且将永远不变。 -It's true that this later line will fail with an error: +这是真的,这后面的一行将报错失败: ```js -// try to change `x`, fingers crossed! +// 试着改变`x`看看! x = 3; // Error! ``` -But again, we're not changing anything about the value. We're attempting to reassign the variable `x`. The values involved are almost incidental. +但是,我们没有改变任何关于值的东西。我们试图重新分配变量`x`。所涉及的值几乎是偶然的。 -To prove that `const` has nothing to do with the nature of the value, consider: +要证明`const`与值的本质无关,思考下: ```js const x = [ 2 ]; ``` -Is the array a constant? **No.** `x` is a constant because it cannot be reassigned. But this later line is totally OK: +数组是常量吗?*不* `x`是一个常量,因为它不能被重新分配。但是下面这句话完全可以: ```js x[0] = 3; ``` -Why? Because the array is still totally mutable, even though `x` is a constant. +为什么?因为数组仍然是完全可变的,即使`x`是一个常量。 -The confusion around `const` and "constant" only dealing with assignments and not value semantics is a long and dirty story. It seems a high degree of developers in just about every language that has a `const` stumble over the same sorts of confusions. Java in fact deprecated `const` and introduced a new keyword `final` at least in part to separate itself from the confusion over "constant" semantics. +围绕`const`和“常量”的混淆只处理赋值而不处理值语义,可以长篇大论了。似乎每一种语言中都有相当多的开发人员遇到了相同类型的混淆。实际上,Java反对使用const,并引入了一个新的关键字final,至少在一定程度上是为了将自己从“常量”语义的混乱中分离出来。 -Setting aside the confusion detractions, what importance does `const` hold for the FPer, if not to have anything to do with creating an immutable value? +抛开混淆的影响,如果`const`与创建不可变值没有任何关系,那么它对于FPer有什么重要性呢? -### Intent +### 意图 -The use of `const` tells the reader of your code that *that* variable will not be reassigned. As a signal of intent, `const` is often highly lauded as a welcome addition to JavaScript and a universal improvement in code readability. +`const`的使用告诉代码的读者,*那个*变量不会被重新分配。作为意图的一个信号,`const`通常被高度赞扬为JavaScript的一个受欢迎的附加功能,并在代码可读性方面得到了普遍的改进。 -In my opinion, this is mostly hype; there's not much substance to these claims. I see only the mildest of faint benefit in signaling your intent in this way. And when you match that up against decades of precedent around confusion about it implying value immutability, I don't think `const` comes close to carrying its own weight. +在我看来,这主要是炒作;这些说法没有多少实质内容。我只看到用这种方式表示你的意图所带来的最轻微的好处。而当你把这个数字与几十年来围绕它的困惑(暗示着值的不变性)进行对比时,我不认为`const`有什么分量。 -To back up my assertion, let's consider scope. `const` creates a block scoped variable, meaning that variable only exists in that one localized block: +为了支持我的断言,让我们考虑作用域。`const`创建了一个块作用域的变量,这意味着变量只存在于一个本地化的块中: ```js -// lots of code +// 一些代码 { const x = 2; - // a few lines of code + // 几行代码 } -// lots of code +// 一些代码 ``` -Typically, blocks are considered best designed to be only a few lines long. If you have blocks of more than say 10 lines, most developers will advise you to refactor. So `const x = 2` only applies to those next nine lines of code at most. +通常,块被认为是最好的设计只有几行。如果您的代码块超过10行,大多数开发人员会建议您重构。所以`const x = 2`最多只适用于后面的9行代码。 -No other part of the program can ever affect the assignment of `x`. **Period.** +程序的任何其他部分都不能影响`x`的赋值。 -My claim is that program has basically the same magnitude of readability as this one: +我的主张是,程序的可读性基本上与这个相同: ```js -// lots of code +// 一些代码 { let x = 2; - // a few lines of code + // 几行代码 } -// lots of code +// 一些代码 ``` -If you look at the next few lines of code after `let x = 2;`, you'll be able to easily tell that `x` is in fact *not* reassigned. That to me is a **much stronger signal** -- actually not reassigning it! -- than the use of some confusable `const` declaration to say "won't reassign it". +如果您查看`let x = 2;`后面的几行代码,您将能够很容易地看出`x`实际上“没有”重新分配。对我来说,这是一个**更强的信号**——实际上不是重新分配它!——而不是使用一些容易混淆的 `const`声明来表示“不会重新分配它”。 -Moreover, let's consider what this code is likely to communicate to a reader at first glance: +此外,让我们考虑一下这段代码可能第一眼就传达给读者的信息: ```js const magicNums = [1,2,3,4]; ``` -Isn't it at least possible (probable?) that the reader of your code will assume (wrongly) that your intent is to never mutate the array? That seems like a reasonable inference to me. Imagine their confusion if later you do in fact allow the array value referenced by `magicNums` to be mutated. Might that surprise them? +难道您的代码的读者(错误地)认为您的意图是永远不修改数组,这至少是可能的(可能的)吗?对我来说,这似乎是一个合理的推论。想象一下他们的疑惑,如果稍后您实际上允许`magicNums`引用的数组值发生突变。这会让他们感到惊讶吗? -Worse, what if you intentionally mutate `magicNums` in some way that turns out to not be obvious to the reader? Subsequently in the code, they see a usage of `magicNums` and assume (again, wrongly) that it's still `[1,2,3,4]` because they read your intent as, "not gonna change this". +更糟的是,如果您故意以某种方式修改`magicNums`,结果却不为读者所知,该怎么办?随后,在代码中,他们看到了`magicNums`的用法,并假设(同样是错误的)它仍然是`[1,2,3,4]`,因为他们将您的意图理解为“不会更改这个”。 -I think you should use `var` or `let` for declaring variables to hold values that you intend to mutate. I think that actually is a **much clearer signal** of your intent than using `const`. +我认为您应该使用`var`或`let`来声明变量,以保存要进行更改的值。我认为这实际上比使用`const`更清楚地表达了你的意图。 + +但`const`的麻烦还不止于此。还记得我们在这一章的开头说过,要将值视为不可变的,就意味着当我们的状态需要更改时,我们必须创建一个新值,而不是对它进行修改吗?一旦你创建了这个新数组,你打算怎么处理它?如果使用`const`声明对它的引用,则不能重新分配它。 -But the troubles with `const` don't stop there. Remember we asserted at the top of the chapter that to treat values as immutable means that when our state needs to change, we have to create a new value instead of mutating it? What are you going to do with that new array once you've created it? If you declared your reference to it using `const`, you can't reassign it. ```js const magicNums = [1,2,3,4]; -// later: -magicNums = magicNums.concat( 42 ); // oops, can't reassign! +// 然后: +magicNums = magicNums.concat( 42 ); // 噢, 不能重新分配 ``` -So... what next? +那么,下一步怎么做? -In this light, I see `const` as actually making our efforts to adhere to FP harder, not easier. My conclusion: `const` is not all that useful. It creates unnecessary confusion and restricts us in inconvenient ways. I only use `const` for simple constants like: +从这个角度来看,我认为`const`实际上使我们更加努力地坚持FP,而不是更加容易。我的结论是:`const`并没有那么有用。它制造了不必要的混乱,并以不方便的方式限制我们。我只对一些简单的常量使用`const`,比如: ```js const PI = 3.141592; ``` -The value `3.141592` is already immutable, and I'm clearly signaling, "this `PI` will always be used as stand-in placeholder for this literal value." To me, that's what `const` is good for. And to be frank, I don't use many of those kinds of declarations in my typical coding. +值`3.141592`已经是不可变的,我明确地表示,这个`PI`将始终用作这个文字值的替代占位符。对我来说,这就是`const`的好处。坦白地说,在我的典型代码中,我没有使用很多这样的声明。 -I've written and seen a lot of JavaScript, and I just think it's an imagined problem that very many of our bugs come from accidental reassignment. +我写过很多JavaScript代码,也见过很多JavaScript代码,我只是认为这是一个想象中的问题,我们的很多bug都来自于意外的重新分配。 -One of the reasons FPers so highly favor `const` and avoid reassignment is because of equational reasoning. Though this topic is more related to other languages than JS and goes beyond what we'll get into here, it is a valid point. However, I prefer the pragmatic view over the more academic one. +FPers如此青睐`const`而避免重新分配的原因之一是由于等式推理。尽管这个主题与其他语言的关系比与JS的关系更密切,而且超出了我们将在这里讨论的范围,但它是一个有效的观点。然而,我更喜欢务实的观点,而不是更学术性的观点。 -For example, I've found measured use of variable reassignment can be useful in simplifying the description of intermediate states of computation. When a value goes through multiple type coercions or other transformations, I don't generally want to come up with new variable names for each representation: +例如,我发现对变量重新分配的度量使用对于简化计算中间状态的描述非常有用。当一个值经过多次类型强制转换或其他转换时,我通常不希望为每种表示形式都提供新的变量名: ```js var a = "420"; -// later +// 然后 a = Number( a ); -// later +// 然后 a = [ a ]; ``` -If after changing from `"420"` to `420`, the original `"420"` value is no longer needed, then I think it's more readable to reassign `a` rather than come up with a new variable name like `aNum`. +如果从字符串`"420"`更改为数字`420`后,不再需要原来的`"420"`值了,那么我认为重新分配的`a`比使用`aNum`这样的新变量名更易于阅读。 -The thing we really should worry more about is not whether our variables get reassigned, but **whether our values get mutated**. Why? Because values are portable; lexical assignments are not. You can pass an array to a function, and it can be changed without you realizing it. But a reassignment will never be unexpectedly caused by some other part of your program. +我们真正应该担心的不是变量是否被重新赋值,而是值是否发生了变化。为什么?因为值是可移植的;词汇赋值则不然。您可以将数组传递给函数,并且可以在您没有意识到的情况下更改它。但是,重新分配绝不会意外地由程序的其他部分引起。 -### It's Freezing in Here +### 冻结值 -There's a cheap and simple way to turn a mutable object/array/function into an "immutable value" (of sorts): +有一种既便宜又简单的方法可以将一个可变的对象/数组/函数转换成一个“不可变值”(而且有很多方法): ```js var x = Object.freeze( [2] ); ``` -The `Object.freeze(..)` utility goes through all the properties/indices of an object/array and marks them as read-only, so they cannot be reassigned. It's sorta like declaring properties with a `const`, actually! `Object.freeze(..)` also marks the properties as non-reconfigurable, and it marks the object/array itself as non-extensible (no new properties can be added). In effect, it makes the top level of the object immutable. +`Object.freeze(..)`实用程序遍历对象/数组的所有属性/索引,并将它们标记为只读,因此不能重新分配它们。实际上,这有点像用`const`声明属性!`Object.freeze(..)`还将属性标记为不可重构,并将对象/数组本身标记为不可扩展(不能添加新属性)。实际上,它使对象的顶层不可变。 -Top level only, though. Be careful! +不过,只有顶层不能重新分配。小心这种方式! ```js var x = Object.freeze( [ 2, 3, [4, 5] ] ); -// not allowed: +// 不允许: x[0] = 42; -// oops, still allowed: +// 允许: x[2][0] = 42; ``` -`Object.freeze(..)` provides shallow, naive immutability. You'll have to walk the entire object/array structure manually and apply `Object.freeze(..)` to each sub-object/array if you want a deeply immutable value. +`Object.freeze(..)`提供了浅的、单一的不变性。如果您想要一个非常不可变的值,就必须手动遍历整个对象/数组结构,并对每个子对象/数组应用`Object.freeze(..)`。 -But contrasted with `const` which can confuse you into thinking you're getting an immutable value when you aren't, `Object.freeze(..)` *actually* gives you an immutable value. +但与`const`不同的是,`Object.freeze(..)实际上给了您一个不可变的值,而`const`可能会让您误以为自己没有得到值时就得到了一个不可变的值。 -Recall the protection example from earlier: +回想一下前面的例子: ```js var arr = Object.freeze( [1,2,3] ); @@ -331,33 +331,33 @@ foo( arr ); console.log( arr[0] ); // 1 ``` -Now `arr[0]` is quite reliably `1`. +现在`arr[0]`是相当可靠的`1`。 -This is so important because it makes reasoning about our code much easier when we know we can trust that a value doesn't change when passed somewhere that we do not see or control. +这一点非常重要,因为当我们知道可以相信一个值在传递到某个我们看不到或无法控制的地方时不会发生变化时,它可以使我们更容易地对代码进行推理。 -## Performance +## 性能 -Whenever we start creating new values (arrays, objects, etc.) instead of mutating existing ones, the obvious next question is: what does that mean for performance? +每当我们开始创建新值(数组、对象等)而不是修改现有值时,下一个明显的问题是:这对性能意味着什么? -If we have to reallocate a new array each time we need to add to it, that's not only churning CPU time and consuming extra memory; the old values (if no longer referenced) are also being garbage collected. That's even more CPU burn. +如果每次需要添加新数组时都要重新分配新数组,这不仅会浪费CPU时间,还会消耗额外的内存;旧值(如果不再引用)也将被垃圾收集。这将消耗更多的CPU。 -Is that an acceptable trade-off? It depends. No discussion or optimization of code performance should happen **without context.** +这是一个可以接受的折中方案吗?这取决于在没有上下文的情况下,不应讨论或优化代码性能。 -If you have a single state change that happens once (or even a couple of times) in the whole life of the program, throwing away an old array/object for a new one is almost certainly not a concern. The churn we're talking about will be so small -- probably mere microseconds at most -- as to have no practical effect on the performance of your application. Compared to the minutes or hours you will save not having to track down and fix a bug related to unexpected value mutation, there's not even a contest here. +如果在程序的整个生命周期中只发生一次(甚至几次)状态更改,那么丢弃旧的数组/对象来替换新数组/对象几乎肯定不是问题。我们所讨论的搅动是如此之小——最多可能只有几微秒——以至于对应用程序的性能没有实际影响。与不必跟踪和修复与意外值突变相关的错误所节省的几分钟或几个小时相比,甚至是忽略不计的。 -Then again, if such an operation is going to occur frequently, or specifically happen in a *critical path* of your application, then performance -- consider both performance and memory! -- is a totally valid concern. +然后,如果这样的操作经常发生,或者特别发生在应用程序的“关键路径”中,那么性能——同时考虑性能和内存!担忧是完全合理的。 -Think about a specialized data structure that's like an array, but that you want to be able to make changes to and have each change behave implicitly as if the result was a new array. How could you accomplish this without actually creating a new array each time? Such a special array data structure could store the original value and then track each change made as a delta from the previous version. +考虑一个类似数组的专门化数据结构,但是您希望能够对其进行更改,并使每个更改的行为隐式,就像结果是一个新数组一样。如何在不每次都创建一个新数组的情况下实现这一点呢?这样一个特殊的数组数据结构可以存储原始值,然后跟踪作为前一个版本的增量所做的每个更改。 -Internally, it might be like a linked-list tree of object references where each node in the tree represents a mutation of the original value. Actually, this is conceptually similar to how **Git** version control works. +在内部,它可能类似于对象引用的链表树,其中树中的每个节点表示原始值的一个突变。实际上,这在概念上类似于**Git**版本控制的工作方式。

-In this conceptual illustration, an original array `[3,6,1,0]` first has the mutation of value `4` assigned to position `0` (resulting in `[4,6,1,0]`), then `1` is assigned to position `3` (now `[4,6,1,1]`), finally `2` is assigned to position `4` (result: `[4,6,1,1,2]`). The key idea is that at each mutation, only the change from the previous version is recorded, not a duplication of the entire original data structure. This approach is much more efficient in both memory and CPU performance, in general. +在这个概念图中,一个原始数组 `[3,6,1,0]` 首先具有分配给位置 `0` 的值`4`的突变(变成`[4,6,1,0]`),然后`1`被分配给位置`3`(现在成了`[4,6,1,1]`),最后`2`被分配给位置`4`(结果:`[4,6,1,1,2]`)。关键的思想是,在每次突变时,只记录与前一个版本的更改,而不是复制整个原始数据结构。通常,这种方法在内存和CPU性能方面都更有效。 -Imagine using this hypothetical specialized array data structure like this: +假设使用这个假设的专用数组数据结构,如下所示: ```js var state = specialArray( 4, 6, 1, 1 ); @@ -375,11 +375,11 @@ newState.get( 4 ); // 2 newState.slice( 2, 5 ); // [1,1,2] ``` -The `specialArray(..)` data structure would internally keep track of each mutation operation (like `set(..)`) as a *diff*, so it won't have to reallocate memory for the original values (`4`, `6`, `1`, and `1`) just to add the `2` value to the end of the list. But importantly, `state` and `newState` point at different versions (or views) of the array value, so **the value immutability semantic is preserved.** +`specialArray(..)`数据结构将在内部跟踪每个突变操作(如`set(..)`),将其作为一个*差异*,因此它不必为原始值(`4`, `6`, `1`和`1`)重新分配内存,只需将`2`值添加到列表的末尾。但重要的是,`state`和`newState`指向数组值的不同版本(或视图),因此保留了值的不变性语义 -Inventing your own performance-optimized data structures is an interesting challenge. But pragmatically, you should probably use a library that already does this well. One great option is [Immutable.js](http://facebook.github.io/immutable-js), which provides a variety of data structures, including `List` (like array) and `Map` (like object). +创建自己的性能优化的数据结构是一个有趣的挑战。但是从实用的角度来看,您可能应该使用已经做得很好的库。一个很好的选项是[Immutable.js](http://facebook.github.io/immutable-js),它提供了各种数据结构,包括`List`(类数组)和`Map`(类对象)。 -Consider the previous `specialArray` example but using `Immutable.List`: +考虑前面的`specialArray`示例,使用`Immutable.List`操作: ```js var state = Immutable.List.of( 4, 6, 1, 1 ); @@ -397,15 +397,15 @@ newState.get( 4 ); // 2 newState.toArray().slice( 2, 5 ); // [1,1,2] ``` -A powerful library like Immutable.js employs sophisticated performance optimizations. Handling all the details and corner-cases manually without such a library would be quite difficult. +像Immutable.js这样强大的库使用复杂的性能优化。在没有这样一个库的情况下手工处理所有的细节和基本情况将是非常困难的。 -When changes to a value are few or infrequent and performance is less of a concern, I'd recommend the lighter-weight solution, sticking with built-in `Object.freeze(..)` as discussed earlier. +当对值的更改很少或不频繁,性能也不那么重要时,我建议使用较轻量级的解决方案,坚持使用前面讨论过的内置`Object.freeze(..)`。 -## Treatment +## 处理 -What if we receive a value to our function and we're not sure if it's mutable or immutable? Is it ever OK to just go ahead and try to mutate it? **No.** As we asserted at the beginning of this chapter, we should treat all received values as immutable -- to avoid side effects and remain pure -- regardless of whether they are or not. +如果我们收到一个函数的值,但我们不确定它是可变的还是不可变的怎么办?你能不能继续尝试变异它?不。正如我们在本章开始时所断言的,我们应该将所有接收到的值都视为不可变的——以避免副作用并保持纯值——无论它们是或不是。 -Recall this example from earlier: +回想一下前面的例子: ```js function updateLastLogin(user) { @@ -415,7 +415,7 @@ function updateLastLogin(user) { } ``` -This implementation treats `user` as a value that should not be mutated; whether it *is* immutable or not is irrelevant to reading this part of the code. Contrast that with this implementation: +此实现将`user`视为不应发生突变的值;它是否是不可变的与读取这部分代码无关。与此实现相比: ```js function updateLastLogin(user) { @@ -424,11 +424,11 @@ function updateLastLogin(user) { } ``` -That version is a lot easier to write, and even performs better. But not only does this approach make `updateLastLogin(..)` impure, it also mutates a value in a way that makes both the reading of this code, as well as the places it's used, more complicated. +这个版本编写起来容易得多,甚至性能更好。但是,这种方法不仅使`updateLastLogin(..)`变得不纯,而且它还以某种方式修改了一个值,使得读取这段代码以及使用它的位置变得更加复杂。 -**We should treat `user` as immutable**, always, because at this point of reading the code we do not know where the value comes from, or what potential issues we may cause if we mutate it. +**我们应该始终将`user`视为不可变的**,因为在阅读代码时,我们不知道该值来自何处,或者如果对其进行修改,可能会导致什么潜在问题。 -Nice examples of this approach can be seen in various built-in methods of the JS array, such as `concat(..)` and `slice(..)`: +这种方法的好例子可以在JS数组的各种内置方法中看到,如`concat(..)`和 `slice(..)`: ```js var arr = [1,2,3,4,5]; @@ -444,25 +444,23 @@ arr2; // [1,2,3,4,5,6] arr3; // [2,3,4,5,6] ``` -Other array prototype methods that treat the value instance as immutable and return a new array instead of mutating: `map(..)` and `filter(..)`. The `reduce(..)`/`reduceRight(..)` utilities also avoid mutating the instance, though they don't by default return a new array. - -Unfortunately, for historical reasons, quite a few other array methods are impure mutators of their instance: `splice(..)`, `pop(..)`, `push(..)`, `shift(..)`, `unshift(..)`, `reverse(..)`, `sort(..)`, and `fill(..)`. +其他数组原型方法将值实例视为不可变的,并返回一个新数组,而不是进行修改:`map(..)`和`filter(..)`。 `filter(..)`. The `reduce(..)`/`reduceRight(..)`实用程序也避免对实例进行修改,不过它们在默认情况下不会返回一个新数组。 -It should not be seen as *forbidden* to use these kinds of utilities, as some claim. For reasons such as performance optimization, sometimes you will want to use them. But you should never use such a method on an array value that is not already local to the function you're working in, to avoid creating a side effect on some other remote part of the code. +不幸的是,由于历史原因,相当多的其他数组方法是其实例的非纯变异:`splice(..)`, `pop(..)`, `push(..)`, `shift(..)`, `unshift(..)`, `reverse(..)`, `sort(..)`, 和 `fill(..)`,这些都更改了原有数据值。 -Recall one of the implementations of [`compose(..)` from Chapter 4](ch4.md/#user-content-generalcompose): +回顾其中一个[章节4 `compose(..)` ](ch4.md/#user-content-generalcompose)的实现 : ```js function compose(...fns) { return function composed(result){ - // copy the array of functions + // 复制函数数组 var list = [...fns]; while (list.length > 0) { - // take the last function off the end of the list - // and execute it + // 把列表末尾的最后一个函数去掉 + // 并执行它 result = list.pop()( result ); } @@ -471,16 +469,16 @@ function compose(...fns) { } ``` -The `...fns` gather parameter is making a new local array from the passed-in arguments, so it's not an array that we could create an outside side effect on. It would be reasonable then to assume that it's safe for us to mutate it locally. But the subtle gotcha here is that the inner `composed(..)` which closes over `fns` is not "local" in this sense. +`...fns`的gather参数从传入的参数中创建一个新的本地数组,所以我们不能在这个数组上创建外部副作用。那么我们就有理由假设在局部进行变异是安全的。但这里的微妙问题是,在`fns`上关闭的内部`composed(..)`在这个意义上不是“局部”的。 -Consider this different version which doesn't make a copy: +考虑一下这个不复制的不同版本: ```js function compose(...fns) { return function composed(result){ while (fns.length > 0) { - // take the last function off the end of the list - // and execute it + // 把列表末尾的最后一个函数去掉 + // 并执行它 result = fns.pop()( result ); } @@ -492,19 +490,19 @@ var f = compose( x => x / 3, x => x + 1, x => x * 2 ); f( 4 ); // 3 -f( 4 ); // 4 <-- uh oh! +f( 4 ); // 4 <-- 变成这样了! ``` -The second usage of `f(..)` here wasn't correct, since we mutated that `fns` during the first call, which affected any subsequent uses. Depending on the circumstances, making a copy of an array like `list = [...fns]` may or may not be necessary. But I think it's safest to assume you need it -- even if only for readability sake! -- unless you can prove you don't, rather than the other way around. +这里第二次使用`f(..)`是不正确的,因为我们在第一次调用时修改了`fns`,这影响了后续的使用。根据具体情况,复制一个数组,如`list = [...fns]`可能有必要,也可能没有必要。但我认为,假设您需要它是最安全的——即使只是为了可读性!除非你能证明你没有,而不是相反。 -Be disciplined and always treat *received values* as immutable, whether they are or not. That effort will improve the readability and trustability of your code. +严格遵守规则,始终将*接收到的值*视为不可变的,不管它们是或不是。这将提高代码的可读性和可靠性。 -## Summary +## 总结 -Value immutability is not about unchanging values. It's about creating and tracking new values as the state of the program changes, rather than mutating existing values. This approach leads to more confidence in reading the code, because we limit the places where our state can change in ways we don't readily see or expect. +值的不变性不是关于不变的值。它是在程序状态更改时创建和跟踪新值,而不是修改现有值。这种方法使我们在阅读代码时更有信心,因为我们限制了状态可以以我们不容易看到或期望的方式更改的地方。 -`const` declarations (constants) are commonly mistaken for their ability to signal intent and enforce immutability. In reality, `const` has basically nothing to do with value immutability, and its usage will likely create more confusion than it solves. Instead, `Object.freeze(..)` provides a nice built-in way of setting shallow value immutability on an array or object. In many cases, this will be sufficient. +`const`声明(常量)通常会被误认为是它们发出意图信号和执行不变性的能力。实际上,`const`基本上与价值的不变性无关,它的使用可能会造成比它解决的更多的混乱。相反,`Object.freeze(..)`提供了一种很好的内置方法,可以在数组或对象上设置浅值不变性。在许多情况下,这就足够了。 -For performance-sensitive parts of the program, or in cases where changes happen frequently, creating a new array or object (especially if it contains lots of data) is undesirable, for both processing and memory concerns. In these cases, using immutable data structures from a library like **Immutable.js** is probably the best idea. +对于程序的性能敏感部分,或者在经常发生更改的情况下,创建一个新的数组或对象(特别是当它包含大量数据时)对于处理和内存问题都是不可取的。在这些情况下,使用像Immutable.js库中的不可变数据结构。可能是最好的主意。 -The importance of value immutability on code readability is less in the inability to change a value, and more in the discipline to treat a value as immutable. +值不变性对代码可读性的重要性不在于不能更改值,而在于将值视为不可变的原则。 diff --git a/manuscript/ch7.md b/manuscript/ch7.md index 303ff263..89261172 100644 --- a/manuscript/ch7.md +++ b/manuscript/ch7.md @@ -1,5 +1,4 @@ -# JavaScript轻量级函数式编程 -# 第7章: 闭包和对象 +# 章节7: 闭包和对象 ## 达成共识 @@ -370,7 +369,7 @@ function outer() { } ``` -We could imagine that the scope -- the set of all variables defined -- of `outer()` is implemented as an object with properties. So, conceptually, somewhere in memory, there's something like: +我们可以想象,`outer()`的作用域(定义的所有变量的集合)被实现为一个带有属性的对象。所以,从概念上讲,在内存的某个地方,类似有这样的东西: ```js scopeOfOuter = { @@ -378,46 +377,46 @@ scopeOfOuter = { }; ``` -And then for the `inner()` function, when created, it gets an (empty) scope object called `scopeOfInner`, which is linked via its `[[Prototype]]` to the `scopeOfOuter` object, sorta like this: +然后对于`inner()`函数,当创建时,它得到一个(空的)作用域对象叫做`scopeOfInner`,它通过它的`[[Prototype]]`链接到`scopeOfOuter`对象,大概是这样的: ```js scopeOfInner = {}; Object.setPrototypeOf( scopeOfInner, scopeOfOuter ); ``` -Then, inside `inner()`, when it makes reference to the lexical variable `x`, it's actually more like: +然后,在`inner()`内部,当它引用词法变量`x`时,它实际上更像是: ```js return scopeOfInner.x; ``` -`scopeOfInner` doesn't have an `x` property, but it's `[[Prototype]]`-linked to `scopeOfOuter`, which does have an `x` property. Accessing `scopeOfOuter.x` via prototype delegation results in the `1` value being returned. +`scopeOfInner`没有`x`属性,但它是[[Prototype]]` -链接到`scopeOfOuter`,它确实有`x`属性。通过原型委托访问`scopeOfOuter.x`将返回“1”值。 -In this way, we can sorta see why the scope of `outer()` is preserved (via closure) even after it finishes: because the `scopeOfInner` object is linked to the `scopeOfOuter` object, thereby keeping that object and its properties alive and well. +通过这种方式,我们可以看到为什么`outer()` 的作用域(通过闭包)在完成之后仍然保留:因为`scopeOfInner`对象与`scopeOfOuter`对象相链接,从而保持该对象及其属性的活动和状态。 -Now, this is all conceptual. I'm not literally saying the JS engine uses objects and prototypes. But it's entirely plausible that it *could* work similarly. +这些都是概念性的。我并不是说JS引擎使用对象和原型。但它完全有可能以类似的方式工作。 -Many languages do in fact implement closures via objects. And other languages implement objects in terms of closures. But we'll let the reader use their imagination on how that would work. +实际上,许多语言都通过对象实现闭包。其他语言使用闭包实现对象。但我们会让读者发挥他们的想象力来理解它是如何运作的。 -## Two Roads Diverged in a Wood... +## 两个观点 -So closures and objects are equivalent, right? Not quite. I bet they're more similar than you thought before you started this chapter, but they still have important differences. +闭包和对象是等价的?不完全是。我打赌它们比您在开始本章之前所认为的更相似,但是它们仍然有重要的区别。 -These differences should not be viewed as weaknesses or arguments against usage; that's the wrong perspective. They should be viewed as features and advantages that make one or the other more suitable (and readable!) for a given task. +这些差异不应被视为缺点或反对使用的理由;这是错误的观点。它们应该被看作是使其中一个或另一个更适合(和可读!)用于给定任务的特性和优势。 -### Structural Mutability +### 结构可变性 -Conceptually, the structure of a closure is not mutable. +从概念上讲,闭包的结构不是可变的。 -In other words, you can never add to or remove state from a closure. Closure is a characteristic of where variables are declared (fixed at author/compile time), and is not sensitive to any runtime conditions -- assuming you use strict mode and/or avoid using cheats like `eval(..)`, of course! +换句话说,您永远不能向闭包添加或删除状态。闭包是声明变量的一个特性(在作者/编译时固定),并且不敏感于任何运行时条件——当然,假设您使用严格模式和/或避免使用`eval(..)`之类的欺骗自己不会出错的方式! -**Note:** The JS engine could technically cull a closure to weed out any variables in its scope that are no longer going to be used, but this is an advanced optimization that's transparent to the developer. Whether the engine actually does these kinds of optimizations, I think it's safest for the developer to assume that closure is per-scope rather than per-variable. If you don't want it to stay around, don't close over it! +注: JS引擎可以在技术上剔除一个闭包,以剔除其范围内不再使用的任何变量,但这是一个高级优化,对开发人员来说是透明的。无论引擎实际上是否执行这些优化,我认为对于开发人员来说,假设闭包是针对范围而不是针对变量的是最安全的。如果你不想让它作用域影响,就不要对他修改! -However, objects by default are quite mutable. You can freely add or remove (`delete`) properties/indices from an object, as long as that object hasn't been frozen (`Object.freeze(..)`). +然而,对象在默认情况下是相当可变的。您可以自由地从对象中添加或删除(`delete`)属性/索引,只要该对象没有被冻结(`Object.freeze(..)`)。 -It may be an advantage of the code to be able to track more (or less!) state depending on the runtime conditions in the program. +根据程序中的运行时条件,可以跟踪更多(或更少)的状态,这可能是代码的一个优势。 -For example, let's imagine tracking the keypress events in a game. Almost certainly, you'll think about using an array to do this: +例如,让我们想象一下在游戏中跟踪按键事件。几乎可以肯定的是,您将考虑使用数组来完成以下操作: ```js function trackEvent(evt,keypresses = []) { @@ -429,11 +428,11 @@ var keypresses = trackEvent( newEvent1 ); keypresses = trackEvent( newEvent2, keypresses ); ``` -**Note:** Did you spot why I didn't `push(..)` directly to `keypresses`? Because in FP, we typically want to treat arrays as immutable data structures that can be re-created and added to, but not directly changed. We trade out the evil of side-effects for an explicit reassignment (more on that later). +注:你注意到为什么我没有直接将`push(..)`推到`keypresses`中了吗?因为在FP中,我们通常希望将数组视为不可变的数据结构,可以重新创建并添加到其中,但不能直接更改。我们用副作用的影响来换取明确的重新分配(稍后会详细介绍)。 -Though we're not changing the structure of the array, we could if we wanted to. More on this in a moment. +虽然我们没有改变数组的结构,但如果我们愿意,我们可以。稍后会详细介绍。 -But an array is not the only way to track this growing "list" of `evt` objects. We could use closure: +但是数组并不是跟踪不断增长的`evt`对象“列表”的唯一方法。我们可以使用闭包: ```js function trackEvent(evt,keypresses = () => []) { @@ -447,21 +446,23 @@ var keypresses = trackEvent( newEvent1 ); keypresses = trackEvent( newEvent2, keypresses ); ``` -Do you spot what's happening here? +你看到这里发生了什么吗? -Each time we add a new event to the "list", we create a new closure wrapped around the existing `keypresses()` function (closure), which captures the current `evt`. When we call the `keypresses()` function, it will successively call all the nested functions, building up an intermediate array of all the individually closed-over `evt` objects. Again, closure is the mechanism that's tracking all the state; the array you see is only an implementation detail of needing a way to return multiple values from a function. +每次我们向“列表”添加一个新事件时,我们都会创建一个新的闭包,它围绕着现有的`keypresses()`函数,该函数捕获当前的`evt`。当我们调用`keypresses()`函数时,它将依次调用所有嵌套函数,为所有单独关闭的`evt`对象构建一个中间数组。闭包是跟踪所有状态的机制;您看到的数组只是需要从函数返回多个值的方法的实现细节。 -So which one is better suited for our task? No surprise here, the array approach is probably a lot more appropriate. The structural immutability of a closure means our only option is to wrap more closure around it. Objects are by default extensible, so we can just grow the array as needed. +那么哪一个更适合我们的任务呢?毫无疑问,数组方法可能更合适。闭包的结构不变性意味着我们唯一的选择是在它周围封装更多的闭包。对象默认是可扩展的,因此我们可以根据需要增长数组。 -By the way, even though I'm presenting this structural (im)mutability as a clear difference between closure and object, the way we're using the object as an immutable value is actually more similar than not. +那么哪一个更适合我们的任务呢?毫无疑问,数组方法可能更合适。闭包的结构不变性意味着我们唯一的选择是在它周围封装更多的闭包。对象默认是可扩展的,因此我们可以根据需要增长数组。 -Creating a new array for each addition to the array is treating the array as structurally immutable, which is conceptually symmetrical to closure being structurally immutable by its very design. +顺便说一下,尽管我将这种结构可变性表示为闭包和对象之间的明显区别,但是我们将对象用作不可变值的方式实际上非常相似。 -### Privacy +为数组的每一个添加创建一个新数组就是将数组视为结构上不可变的,这在概念上与闭包是对称的,闭包的设计本身就是结构上不可变的。 -Probably one of the first differences you think of when analyzing closure vs. object is that closure offers "privacy" of state through nested lexical scoping, whereas objects expose everything as public properties. Such privacy has a fancy name: information hiding. +### 私有 -Consider lexical closure hiding: +在分析闭包和对象时,您首先想到的一个区别可能是闭包通过嵌套的词法范围提供了状态的“私有”,而对象则将所有内容公开为公共属性。这种隐私有一个奇特的名字:信息隐藏。 + +考虑词法闭包隐藏: ```js function outer() { @@ -477,7 +478,7 @@ var xHidden = outer(); xHidden(); // 1 ``` -Now the same state in public: +现在同样的状态在公共中: ```js var xPublic = { @@ -487,30 +488,30 @@ var xPublic = { xPublic.x; // 1 ``` -There are some obvious differences around general software engineering principles -- consider abstraction, the module pattern with public and private APIs, etc. -- but let's try to constrain our discussion to the perspective of FP; this is, after all, a book about functional programming! +在一般软件工程原则方面有一些明显的差异——考虑抽象、带有公共api和私有api的模块模式,等等——但是让我们将讨论限制在FP的视角;毕竟,这是一本关于函数式编程的书! -#### Visibility +#### 可见性 -It may seem that the ability to hide information is a desired characteristic of state tracking, but I believe the FPer might argue the opposite. +隐藏信息的能力似乎是状态跟踪的一个理想特性,但我认为FPer可能持相反的观点。 -One of the advantages of managing state as public properties on an object is that it's easier to enumerate (and iterate!) all the data in your state. Imagine you wanted to process each keypress event (from the earlier example) to save it to a database, using a utility like: +将状态管理为对象上的公共属性的优点之一是,枚举(并迭代)状态中的所有数据更容易。假设您希望处理每个按键事件(来自前面的示例),并使用以下实用程序将其保存到数据库: ```js function recordKeypress(keypressEvt) { - // database utility + // 数据库操作 DB.store( "keypress-events", keypressEvt ); } ``` -If you already have an array -- just an object with public numerically named properties -- this is very straightforward using a built-in JS array utility `forEach(..)`: +如果您已经有了一个数组——只是一个对象,它具有公共数字命名的属性——那么使用内置的JS数组实用程序`forEach(..)`就非常简单了 ```js keypresses.forEach( recordKeypress ); ``` -But if the list of keypresses is hidden inside closure, you'll have to expose a utility on the public API of the closure with privileged access to the hidden data. +但是,如果按键列表隐藏在闭包中,则必须在闭包的公共API上公开一个实用程序,该实用程序具有访问隐藏数据的特权。 -For example, we can give our closure-`keypresses` example its own `forEach`, like built-in arrays have: +例如,我们可以给我们的闭包的`keypresses`例子自己的`forEach`,就像内置数组有: ```js function trackEvent( @@ -538,41 +539,41 @@ keypresses.list(); // [ evt, evt, .. ] keypresses.forEach( recordKeypress ); ``` -The visibility of an object's state data makes using it more straightforward, whereas closure obscures the state making us work harder to process it. +对象状态数据的可见性使它的使用更加直观,而闭包模糊了状态,使我们更加努力地处理它。 -#### Change Control +#### 变更控制 -If the lexical variable `x` is hidden inside a closure, the only code that has the freedom to reassign it is also inside that closure; it's impossible to modify `x` from the outside. +如果词法变量`x`隐藏在闭包中,那么唯一可以自由重新分配它的代码也在闭包中;从外部修改`x`是不可能的。 -As we saw in [Chapter 6](ch6.md), that fact alone improves the readability of code by reducing the surface area that the reader must consider to predict the behavior of any given variable. +正如我们在[第6章](ch6.md)中所看到的,仅这一事实就通过减少读者必须考虑的预测任何给定变量行为来提高代码的可读性。 -The local proximity of lexical reassignment is a big reason why I don't find `const` as a feature that helpful. Scopes (and thus closures) should in general be pretty small, and that means there will only be a few lines of code that can affect reassignment. In `outer()` above, we can quickly inspect to see that no line of code reassigns `x`, so for all intents and purposes it's acting as a constant. +词法重新分配的局部相似性是我不认为`const`这个特性有帮助的一个重要原因。范围(以及闭包)通常应该非常小,这意味着只有几行代码可以影响重新分配。在上面的`outer()`中,我们可以快速检查是否有一行代码重新分配了`x`,因此它实际上是一个常量。 -This kind of guarantee is a powerful contributor to our confidence in the purity of a function, for example. +例如,这种确保证对我们对函数纯度的有强大的贡献。 -On the other hand, `xPublic.x` is a public property, and any part of the program that gets a reference to `xPublic` has the ability, by default, to reassign `xPublic.x` to some other value. That's a lot more lines of code to consider! +另一方面,`xPublic.x`是一个公共属性,程序中任何引用`xPublic`的部分在默认情况下都可以重新分配`xPublic.x`到另一个值。这需要考虑更多的代码行! -That's why in [Chapter 6, we looked at `Object.freeze(..)`](ch6.md/#its-freezing-in-here) as a quick-n-dirty means of making all of an object's properties read-only (`writable: false`), so that they can't be reassigned unpredictably. +这就是为什么在[第6章中`Object.freeze(..)`](ch6.md/#its-freezing-in-here)看作是一种快速的方法,它使对象的所有属性都是只读的(“writable: false”),因此不能不可预知地重新分配它们。 -Unfortunately, `Object.freeze(..)` is both all-or-nothing and irreversible. +不幸的是,`Object.freeze(..)`既是全有或全无的,也是不可逆转的。 -With closure, you have some code with the privilege to change, and the rest of the program is restricted. When you freeze an object, no part of the code will be able to reassign. Moreover, once an object is frozen, it can't be thawed out, so the properties will remain read-only for the duration of the program. +使用闭包,您可以修改一些代码,而程序的其他部分则受到限制。冻结对象时,代码的任何部分都无法重新分配。此外,一旦一个对象被冻结,它就不能被解冻,因此在程序执行期间属性将保持只读。 -In places where I want to allow reassignment but restrict its surface area, closures are a more convenient and flexible form than objects. In places where I want no reassignment, a frozen object is a lot more convenient than repeating `const` declarations all over my function. +在允许重新分配但限制其表现的地方,闭包比对象更方便、更灵活。在不需要重新分配的地方,冻结的对象比在函数中重复`const`声明要方便得多。 -Many FPers take a hard-line stance on reassignment: it shouldn't be used. They will tend to use `const` to make all closure variables read-only, and they'll use `Object.freeze(..)` or full immutable data structures to prevent property reassignment. Moreover, they'll try to reduce the amount of explicitly declared/tracked variables and properties wherever possible, preferring value transfer -- function chains, `return` value passed as argument, etc. -- instead of intermediate value storage. +许多函数编程者采取强硬立场在重新分配:它不应该使用。他们倾向于使用`const`将所有闭包变量设置为只读,并使用 `Object.freeze(..)`或完整的不可变数据结构来防止属性重新分配。此外,他们将尽可能减少显式声明/跟踪的变量和属性的数量,更喜欢值传递——函数链、作为参数传递的`return`值,等等——而不是中间值存储。 -This book is about "Functional-Light" programming in JavaScript, and this is one of those cases where I diverge from the core FP crowd. +这本书是关于JavaScript中的“轻量函数”编程的,这是我与函数编程核心人群的分歧之一。 -I think variable reassignment can be quite useful, and when used appropriately, quite readable in its explicitness. It's certainly been my experience that debugging is a lot easier when you can insert a `debugger` or breakpoint, or track a watch expression. +我认为变量重新分配是非常有用的,如果使用得当,它的明确性是非常可读的。根据我的经验,如果可以插入一个`debugger`或断点,或者跟踪一个watch表达式,调试就会容易得多。 -### Cloning State +### 克隆状态 -As we learned in [Chapter 6](ch6.md), one of the best ways we prevent side effects from eroding the predictability of our code is to make sure we treat all state values as immutable, regardless of whether they are actually immutable (frozen) or not. +正如我们在[第6章](ch6.md)中了解到的,防止副作用侵蚀代码可预测性的最好方法之一是确保将所有状态值都视为不可变的,而不管它们实际上是否不可变(冻结)。 -If you're not using a purpose-built library to provide sophisticated immutable data structures, the simplest approach will suffice: duplicate your objects/arrays each time before making a change. +如果您没有使用专门构建的库来提供复杂的不可变数据结构,那么最简单的方法就足够了:每次更改之前复制对象/数组。 -Arrays are easy to clone shallowly -- just use `...` array spread: +数组很容易被浅克隆——只需使用`...`数组扩展: ```js var a = [ 1, 2, 3 ]; @@ -584,7 +585,7 @@ a; // [1,2,3] b; // [1,2,3,4] ``` -Objects can be shallow-cloned relatively easily too: +对象也可以相对容易地浅克隆: ```js var o = { @@ -592,32 +593,32 @@ var o = { y: 2 }; -// in ES2018+, using object spread: +// ES2018+可以使用对象扩展: var p = { ...o }; p.y = 3; -// in ES6/ES2015+: +// 在 ES6/ES2015+: var p = Object.assign( {}, o ); p.y = 3; ``` -If the values in an object/array are themselves non-primitives (objects/arrays), to get deep cloning you'll have to walk each layer manually to clone each nested object. Otherwise, you'll have copies of shared references to those sub-objects, and that's likely to create havoc in your program logic. +如果对象/数组中的值本身是非基础值(对象/数组),则要进行深度克隆,必须手动遍历每一层以克隆每个嵌套对象。否则,您将拥有对这些子对象的共享引用的副本,这可能会在程序逻辑中造成混乱。 -Did you notice that this cloning is possible only because all these state values are visible and can thus be easily copied? What about a set of state wrapped up in a closure; how would you clone that state? +您是否注意到,之所以可以进行克隆,是因为所有这些状态值都是可见的,因此很容易复制?那么包在闭包中的一组状态呢?你如何克隆那个状态? -That's much more tedious. Essentially, you'd have to do something similar to our custom `forEach` API method earlier: provide a function inside each layer of the closure with the privilege to extract/copy the hidden values, creating new equivalent closures along the way. +这要乏味得多。本质上,您必须做一些类似于前面的自定义`forEach`API方法的事情:在闭包的每个层中提供一个函数,该函数具有提取/复制隐藏值的特权,并在此过程中创建新的等效闭包。 -Even though that's theoretically possible -- another exercise for the reader! -- it's far less practical to implement than you're likely to justify for any real program. +尽管这在理论上是可能的——这是读者的另一个练习!对于任何实际的程序来说,实现它的实际意义都远远小于它的实际意义。 -Objects have a clear advantage when it comes to representing state that we need to be able to clone. +对象在表示我们需要能够克隆的状态方面具有明显的优势。 -### Performance +### 性能 -One reason objects may be favored over closures, from an implementation perspective, is that in JavaScript objects are often lighter-weight in terms of memory and even computation. +从实现的角度来看,对象可能比闭包更受欢迎的一个原因是,在JavaScript中,对象在内存甚至计算方面通常更轻。 -But be careful with that as a general assertion: there are plenty of things you can do with objects that will erase any performance gains you may get from ignoring closure and moving to object-based state tracking. +但是要注意这是一个通用的断言:您可以对对象执行许多操作,这些操作将消除忽略闭包并转向基于对象的状态跟踪所带来的性能收益。 -Let's consider a scenario with both implementations. First, the closure-style implementation: +让我们考虑一个同时具有这两种实现的场景。首先,封闭式实现: ```js function StudentRecord(name,major,gpa) { @@ -628,15 +629,15 @@ function StudentRecord(name,major,gpa) { var student = StudentRecord( "Kyle Simpson", "CS", 4 ); -// later +// 然后 student(); // Kyle Simpson, Major: CS, GPA: 4.0 ``` -The inner function `printStudent()` closes over three variables: `name`, `major`, and `gpa`. It maintains this state wherever we transfer a reference to that function -- we call it `student()` in this example. +内部函数`printStudent()`关闭三个变量:`name`, `major`, 和 `gpa`。无论我们在何处传递对该函数的引用,它都会保持这种状态——在本例中,我们将其称为`student()`。 -Now for the object (and `this`) approach: +现在对于对象(和“this”)方法: ```js function StudentRecord(){ @@ -650,21 +651,21 @@ var student = StudentRecord.bind( { gpa: 4 } ); -// later +// 然后 student(); // Kyle Simpson, Major: CS, GPA: 4.0 ``` -The `student()` function -- technically referred to as a "bound function" -- has a hard-bound `this` reference to the object literal we passed in, such that any later call to `student()` will use that object for its `this`, and thus be able to access its encapsulated state. +`student()`函数——在技术上称为“绑定函数”——有一个硬绑定的`this`引用,指向我们传入的对象文本,这样以后任何对`student()`的调用都会将该对象用于它的`this`,从而能够访问它的封装状态。 -Both implementations have the same outcome: a function with preserved state. But what about the performance; what differences will there be? +这两种实现都有相同的结果:具有保留状态的函数。但是性能呢?会有什么不同? -**Note:** Accurately and actionably judging performance of a snippet of JS code is a very dodgy affair. We won't get into all the details here, but I urge you to read *You Don't Know JS: Async & Performance*, specifically Chapter 6, "Benchmarking & Tuning", for more details. +注意:准确而有效地判断JS代码片段的性能是一件非常危险的事情。我们不会在这里讨论所有的细节,但我强烈建议您阅读《您不知道JS: Async & Performance》,特别是第6章,“基准测试和调优”,以获得更多的细节。 -If you were writing a library that created a pairing of state with its function -- either the call to `StudentRecord(..)` in the first snippet or the call to `StudentRecord.bind(..)` in the second snippet -- you're likely to care most about how those two perform. Inspecting the code, we can see that the former has to create a new function expression each time. The second one uses `bind(..)`, which is not as obvious in its implications. +如果您正在编写一个用它的函数创建状态对的库——第一个代码片段中对`StudentRecord(..)`的调用,或者第二个代码片段中对`StudentRecord.bind(..)`的调用——您可能最关心这两个函数的性能。检查代码,我们可以看到前者每次都必须创建一个新的函数表达式。第二种方法使用`bind(..)`,其含义并不明显。 -One way to think about what `bind(..)` does under the covers is that it creates a closure over a function, like this: +考虑`bind(..)`在内部做什么的一种方法是,它在函数上创建一个闭包,如下所示: ```js function bind(orinFn,thisObj) { @@ -676,32 +677,32 @@ function bind(orinFn,thisObj) { var student = bind( StudentRecord, { name: "Kyle.." } ); ``` -In this way, it looks like both implementations of our scenario create a closure, so the performance is likely to be about the same. +这样,看起来我们场景的两种实现都创建了一个闭包,所以性能可能差不多。 -However, the built-in `bind(..)` utility doesn't really have to create a closure to accomplish the task. It simply creates a function and manually sets its internal `this` to the specified object. That's potentially a more efficient operation than if we did the closure ourselves. +然而,内置的`bind(..)`实际上并不需要创建一个闭包来完成该任务。它只是创建一个函数,并手动将其内部`this`设置为指定的对象。这可能比我们自己封闭更有效。 -The kind of performance savings we're talking about here is miniscule on an individual operation. But if your library's critical path is doing this hundreds or thousands of times or more, that savings can add up quickly. Many libraries -- Bluebird being one such example -- have ended up optimizing by removing closures and going with objects, in exactly this means. +我们在这里讨论的性能节省对于单个操作来说是微不足道的。但是,如果您的库的关键路径是这样做数百次、数千次或更多次,那么节省的时间就会很快增加。许多库——Bluebird库就是一个这样的例子——最终都通过删除闭包和使用对象来优化,这就是所谓的优化。 -Outside of the library use-case, the pairing of the state with its function usually only happens relatively few times in the critical path of an application. By contrast, typically the usage of the function+state -- calling `student()` in either snippet -- is more common. +在库用例之外,状态与其函数的配对通常只在应用程序的关键路径中发生相对较少的情况。相比之下,通常使用函数+状态方式(在两个代码段中调用`student()`)更为常见。 -If that's the case for some given situation in your code, you should probably care more about the performance of the latter versus the former. +如果代码中的某些特定情况就是这种情况,那么您可能应该更关心后者的性能问题。 -Bound functions have historically had pretty lousy performance in general, but have recently been much more highly optimized by JS engines. If you benchmarked these variations a couple of years ago, it's entirely possible you'd get different results repeating the same test with the latest engines. +绑定函数过去的性能一般都很差,但最近JS引擎对其进行了高度优化。如果您在几年前对这些变化进行基准测试,完全有可能使用最新的引擎重复相同的测试,得到不同的结果。 -A bound function is now likely to perform at least as good if not better as the equivalent closed-over function. So that's another tick in favor of objects over closures. +现在,绑定函数的性能至少可能与等价的闭包函数一样好,甚至更好。这是另一个有利于对象而不是闭包的标记。 -I just want to reiterate: these performance observations are not absolutes, and the determination of what's best for a given scenario is very complex. Do not just casually apply what you've heard from others or even what you've seen on some other earlier project. Carefully examine whether objects or closures are appropriately efficient for the task. +我只想重申:这些性能观察并不是绝对的,对于给定的场景,确定什么是最好的是非常复杂的。不要随意地应用你从别人那里听到的,甚至是你在其他早期项目中看到的。仔细检查对象或闭包是否适合该任务。 -## Summary +## 总结 -The truth of this chapter cannot be written out. One must read this chapter to find its truth. +这一章的真理是不能写出来的。人们必须阅读这一章才能找到它的真理。 ---- -Coining some Zen wisdom here was my attempt at being clever. But you deserve a proper summary of this chapter's message. +在这里,我想要创造一些禅宗智慧,让自己变得更聪明。但是你应该对这一章的信息做一个适当的总结。 -Objects and closures are isomorphic to each other, which means that they can be used somewhat interchangeably to represent state and behavior in your program. +对象和闭包是同构的,这意味着它们可以在一定程度上互换使用,以表示程序中的状态和行为。 -Representation as a closure has certain benefits, like granular change control and automatic privacy. Representation as an object has other benefits, like easier cloning of state. +表示为闭包有一定的好处,比如粒度更改控制和自动隐私。表示为对象还有其他好处,比如更容易克隆状态。 -The critically thinking FPer should be able to conceive any segment of state and behavior in the program with either representation, and pick the representation that's most appropriate for the task at hand. +批判性思维的函数编程者应该能够构想出程序中任意一段状态和行为,并选择最适合当前任务的表示。 diff --git a/manuscript/ch8.md b/manuscript/ch8.md index 4d3f27b0..1bcca587 100644 --- a/manuscript/ch8.md +++ b/manuscript/ch8.md @@ -1,13 +1,12 @@ -# Functional-Light JavaScript -# Chapter 8: Recursion +# 章节 8: 递归 -Did you have fun down our little closures/objects rabbit hole in the previous chapter? Welcome back! +你在前一章的闭包/对象了解的怎么样?现在欢迎回来! -On the next page, we're going to jump into the topic of recursion. +下一页,我们将直接进入递归的话题。
-*(rest of the page intentionally left blank)* +*(这一页的其余部分故意留白)*

 

 

@@ -23,25 +22,25 @@ On the next page, we're going to jump into the topic of recursion.
-Let's talk about recursion. Before we dive in, consult the previous page for the formal definition. +我们来谈谈递归。在深入讨论之前,请参阅前一页中的正式定义。 -Weak joke, I know. :) +我知道这是个蹩脚的笑话。 :) -Recursion is one of those programming techniques that most developers admit can be very powerful, but also most of them don't like to use it. I'd put it in the same category as regular expressions, in that sense. Powerful, but confusing, and thus seen as *not worth the effort*. +递归是大多数开发人员都承认非常强大的编程技术之一,但是大多数开发人员也不喜欢使用它。在这个意义上,我把它和正则表达式放在同一个类别。强大,但令人困惑,因此被视为“不值得努力”。 -I'm a big fan of recursion, and you can, too! Unfortunately, many examples of recursion focus on trivial academic tasks like generating Fibonacci sequences. If you're needing those kinds of numbers in your program -- and let's face it, that's not very common! -- you'll likely miss the big picture. +我非常喜欢递归,你也可以!不幸的是,许多递归的例子都集中在一些琐碎的学术任务上,比如生成斐波那契数列。如果你的程序中需要这些数字——让我们面对现实吧,这并不常见!——你可能会错过大局。 -As a matter of fact, recursion is one of the most important ways that FP developers avoid imperative looping and reassignment, by offloading the implementation details to the language and engine. When used properly, recursion is powerfully declarative for complex problems. +事实上,递归是FP开发人员避免命令性循环和重新分配的最重要方法之一,方法是将实现细节转移到语言和引擎中。如果使用得当,递归对于复杂的问题具有强大的声明性。 -Sadly, recursion gets far less attention, especially in JS, than it should, in large part because of some very real performance (speed and memory) limitations. Our goal in this chapter is to dig deeper and find practical reasons that recursion should be front and center in our FP. +遗憾的是,递归得到的关注要少得多,尤其是在JS中,这在很大程度上是因为一些非常实际的性能(速度和内存)限制。我们在这一章的目标是深入挖掘,并找到递归应该成为FP的首要和中心的实际原因。 -## Definition +## 定义 -Recursion is when a function calls itself, and that call does the same, and this cycle continues until a base condition is satisfied and the call loop unwinds. +递归是当一个函数调用它自己时,这个调用也做同样的事情,这个循环一直持续,直到满足一个基本条件,然后调用循环展开。 -**Warning:** If you don't ensure that a base condition is *eventually* met, recursion will run forever, and crash or lock up your program; the base condition is pretty important to get right! +**警告:**如果不能确保最终满足基本条件,递归将永远运行,并导致程序崩溃或锁定;基本条件是相当重要的,以得到正确! -But... that definition is too confusing in its written form. We can do better. Consider this recursive function: +但是…那个定义的书面形式太混乱了。我们可以做得更好。考虑这个递归函数: ```js function foo(x) { @@ -50,31 +49,31 @@ function foo(x) { } ``` -Let's visualize what happens with this function when we call `foo( 16 )`: +让我们想象一下当我们调用`foo(16)`时这个函数会发生什么:

-In step 2, `x / 2` produces `8`, and that's passed in as the argument to a recursive `foo(..)` call. In step 3, same thing, `x / 2` produces `4`, and that's passed in as the argument to yet another `foo(..)` call. That part is hopefully fairly straightforward. +在步骤2中,`x / 2` 生成`8`,并将其作为参数传递给递归的`foo(..)`调用。在步骤3中,同样的事情,`x / 2` 生成 `4`,并作为参数传递给另一个`foo(..)`调用。这部分应该很简单 -But where someone may often get tripped up is what happens in step 4. Once we've satisfied the base condition where `x` (value `4`) is `< 5`, we no longer make any more recursive calls, and just (effectively) do `return 4`. Specifically the dotted line return of `4` in this figure simplifies what's happening there, so let's dig into that last step and visualize it as these three sub-steps: +但有些人可能经常犯错的地方是步骤4。一旦我们满足了`x` (值`4`)为 `< 5` 的基本条件,我们就不再执行任何递归调用,而只是(有效地)执行 `return 4`。具体来说,图中虚线返回的 `4` 简化了这里发生的事情,所以让我们深入研究最后一步,并将其可视化为以下三个子步骤:

-Once the base condition is satisfied, the returned value cascades back through all of the current function calls (and thus `return`s), eventually `return`ing the final result out. +一旦基本条件得到满足,返回的值将通过所有当前函数调用级联返回,最终 `return` 输出最终结果。 -Another way to visualize this recursion is by considering the function calls in the order they happen (commonly referred to as the call stack): +另一种可视化这种递归的方法是考虑函数调用的发生顺序(通常称为调用堆栈):

-More on the call stack later in this chapter. +本章稍后将详细介绍调用堆栈。 -Another recursion example: +另一个递归的例子: ```js function isPrime(num,divisor = 2){ @@ -89,9 +88,9 @@ function isPrime(num,divisor = 2){ } ``` -This prime checking basically works by trying each integer from `2` up to the square root of the `num` being checked, to see if any of them divide evenly (`%` mod returning `0`) into the number. If any do, it's not a prime. Otherwise, it must be prime. The `divisor + 1` uses the recursion to iterate through each possible `divisor` value. +这个质数检查基本上是通过尝试从`2` 到被检查的`num`的平方根的每个整数来工作的,看看它们中是否有一个被均匀地除(`%` mod返回 `0`)到这个数字。如果有的话,它不是质数。否则,它必须是质数。`divisor + 1` 使用递归遍历每个可能的“除数”值。 -One of the most famous examples of recursion is calculating a Fibonacci number, where the sequence is defined as: +递归最著名的例子之一是计算斐波那契数列,其中序列定义为: ```txt fib( 0 ): 0 @@ -100,9 +99,9 @@ fib( n ): fib( n - 2 ) + fib( n - 1 ) ``` -**Note:** The first several numbers of this sequence are: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... Each number is the addition of the previous two numbers in the sequence. +**注意:**这个序列的前几个数字是:0、1、1、2、3、5、8、13、21、34、…每个数字都是序列中前两个数字的加法。 -The definition of Fibonacci expressed directly in code: +Fibonacci(斐波那契)的定义直接用代码表示: ```js function fib(n) { @@ -111,17 +110,17 @@ function fib(n) { } ``` -`fib(..)` calls itself recursively twice, which is typically referred to as binary recursion. We'll talk more about binary recursion later. +`fib(..)` 两次递归地调用自己,通常称为二进制递归。稍后我们会详细讨论二进制递归。 -We'll use `fib(..)` variously throughout this chapter to illustrate ideas around recursion, but one downside to this particular form is that there's an awful lot of duplicated work. `fib(n-1)` and `fib(n-2)` don't share any of their work with each other, but overlap with each other almost entirely, over the entire integer space down to `0`. +我们将在本章中多次使用`fib(..)`来说明递归的概念,但是这种特殊形式的一个缺点是有大量的重复工作。`fib(n-1)` 和 `fib(n-2)` 彼此之间没有任何工作,但几乎完全重叠,在整个整数空间直到`0`。 -We briefly touched on memoization in [Chapter 5, "Performance Effects"](ch5.md/#performance-effects). Here, memoization would allow the `fib(..)` of any given number to be computed only once, instead of being recomputed many times. We won't go further into that topic here, but that performance caveat is important to keep in mind with any algorithm, recursive or not. +我们在[第5章,“性能效果”](ch5.md/#performance-effects)中简要介绍了记忆法。在这里,记忆可以让任何给定数字的`fib(..)`只计算一次,而不是多次重新计算。我们不会在这里深入讨论这个主题,但是对于任何算法,无论是否递归,都要记住性能警告。 -### Mutual Recursion +### 相互递归调用 -When a function calls itself, specifically, this is referred to as direct recursion. That's what we saw in the previous section with `foo(..)`, `isPrime(..)`, and `fib(..)`. When two or more functions call each other in a recursive cycle, this is referred to as mutual recursion. +当一个函数调用它自己时,这被称为直接递归。这就是我们在前一节中看到的`foo(..)`, `isPrime(..)`和 `fib(..)`。当两个或多个函数在递归循环中相互调用时,这称为互递归。 -These two functions are mutually recursive: +这两个函数是相互递归的: ```js function isOdd(v) { @@ -135,9 +134,9 @@ function isEven(v) { } ``` -Yes, this is a silly way to calculate if a number is odd or even. But it illustrates the idea that certain algorithms can be defined in terms of mutual recursion. +是的,这是一个愚蠢的方法来计算一个数字是奇数还是偶数。但它说明了某些算法可以用相互递归来定义。 -Recall the binary recursive `fib(..)` from the previous section; we could instead have expressed it with mutual recursion: +回想一下前一节中的二进制递归`fib(..)`;我们可以用相互递归来表示: ```js function fib_(n) { @@ -151,17 +150,17 @@ function fib(n) { } ``` -**Note:** This mutually recursive `fib(..)` implementation is adapted from research presented in ["Fibonacci Numbers Using Mutual Recursion"](https://www.researchgate.net/publication/246180510_Fibonacci_Numbers_Using_Mutual_Recursion). +注:这个相互递归的`fib(..)`实现改编自《[使用互递归的斐波那契数]》(https://www.researchgate.net/publication/246180510_Fibonacci_Numbers_Using_Mutual_Recursion)中的研究。 -While these mutual recursion examples shown are rather contrived, there are more complex use cases where mutual recursion can be very helpful. Counting the number of leaves in a tree data structure is one example, and recursive descent parsing (of source code, by a compiler) is another. +虽然这些相互递归的例子有些做作,但是在更复杂的用例中,相互递归可能非常有用。计算树数据结构中的叶子数是一个例子,而递归下降解析(由编译器对源代码进行解析)是另一个例子。 -### Why Recursion? +### 为什么递归? -Now that we've defined and illustrated recursion, we should examine why it is useful. +既然我们已经定义并演示了递归,我们应该检查一下它为什么有用。 -The most commonly cited reason that recursion fits the spirit of FP is because it trades (much of) the explicit tracking of state with implicit state on the call stack. Typically, recursion is most useful when the problem requires conditional branching and back-tracking, and managing that kind of state in a purely iterative environment can be quite tricky; at a minimum, the code is highly imperative and harder to read and verify. But tracking each level of branching as its own scope on the call stack often significantly cleans up the readability of the code. +递归之所以符合FP的精神,最常被引用的原因是它用调用堆栈上的隐式状态与显式状态跟踪(大部分)进行了交换。通常,当问题需要条件分支和回溯时,递归是最有用的,并且在纯迭代环境中管理这种状态可能非常棘手;至少,代码是非常必要的,并且更难阅读和验证。但是,将每个级别的分支作为调用堆栈上自己的范围进行跟踪,通常会显著提高代码的可读性。 -Simple iterative algorithms can trivially be expressed as recursion: +简单的迭代算法可以简单地表示为递归: ```js function sum(total,...nums) { @@ -172,7 +171,7 @@ function sum(total,...nums) { return total; } -// vs +// 对比 function sum(num1,...nums) { if (nums.length == 0) return num1; @@ -180,15 +179,15 @@ function sum(num1,...nums) { } ``` -It's not just that the `for`-loop is eliminated in favor of the call stack, but that the incremental partial sums (the intermittent state of `total`) are tracked implicitly across the `return`s of the call stack instead of reassigning `total` each iteration. FPers will often prefer to avoid reassignment of local variables where it's possible to avoid. +这不仅是因为“for”循环被调用堆栈取代,而且增量部分和(`total`的间歇状态)通过调用堆栈的`return`隐式跟踪,而不是在每次迭代中重新分配`total`。函数编程者通常倾向于避免在可能避免的地方重新分配局部变量。 -In a basic algorithm like this kind of summation, this difference is minor and nuanced. But the more sophisticated your algorithm, the more you will likely see the payoff of recursion instead of imperative state tracking. +在类似于这种求和的基本算法中,这种差别很小,很细微。但是,您的算法越复杂,您就越有可能看到递归优势而不是强制状态跟踪的好处。 -## Declarative Recursion +## 声明式递归 -Mathematicians use the **Σ** symbol as a placeholder to represent the summation of a list of numbers. The primary reason they do that is because it's more cumbersome (and less readable!) if they're working with more complex formulas and they have to write out the summation manually, like `1 + 3 + 5 + 7 + 9 + ..`. Using the notation is declarative math! +数学家使用*Σ*符号作为一个占位符代表的总和一个数字列表。他们这样做的主要原因是,如果他们处理的是更复杂的公式,并且他们必须手工写出求和,比如`1 + 3 + 5 + 7 + 9 + ..`,那么这会更麻烦(而且可读性更差!)使用这个符号是声明式数学! -Recursion is declarative for algorithms in the same sense that **Σ** is declarative for mathematics. Recursion expresses that a problem solution exists, but doesn't necessarily require the reader of the code to understand how that solution works. Let's consider two approaches to finding the highest even number passed as an argument: +递归是声明性算法在同样的意义上,*Σ*是数学的声明。递归表示存在一个问题解决方案,但并不一定要求代码的读者理解该解决方案是如何工作的。让我们考虑两种方法来找到作为参数传递的最高偶数: ```js function maxEven(...nums) { @@ -206,23 +205,23 @@ function maxEven(...nums) { } ``` -This implementation is not particularly intractable, but it's also not readily apparent what its nuances are. How obvious is it that `maxEven()`, `maxEven(1)`, and `maxEven(1,13)` all return `undefined`? Is it quickly clear why the final `if` statement is necessary? +这种实现并不是特别棘手,但是它的细微差别也不是很明显。`maxEven()`, `maxEven(1)`和' maxEven(1,13) '都返回`undefined`,这很明显为什么最后的`if`声明是必要的? -Let's instead consider a recursive approach, to compare. We could notate the recursion this way: +让我们考虑一个递归方法来进行比较。我们不能这样做递归: ```txt maxEven( nums ): maxEven( nums.0, maxEven( ...nums.1 ) ) ``` -In other words, we can define the max-even of a list of numbers as the max-even of the first number compared to the max-even of the rest of the numbers. For example: +换句话说,我们可以将一组数的最大值-偶数定义为第一个数的最大值-偶数与其余数的最大值-偶数的比较。例如: ```txt maxEven( 1, 10, 3, 2 ): maxEven( 1, maxEven( 10, maxEven( 3, maxEven( 2 ) ) ) ``` -To implement this recursive definition in JS, one approach is: +要在JS中实现这个递归定义,一种方法是: ```js function maxEven(num1,...restNums) { @@ -236,38 +235,38 @@ function maxEven(num1,...restNums) { } ``` -So what advantages does this approach have? +那么这种方法有什么优势呢? -First, the signature is a little different than before. I intentionally called out `num1` as the first argument name, collecting the rest of the arguments into `restNums`. But why? We could just have collected them all into a single `nums` array and then referred to `nums[0]`. +首先,方法与以前略有不同。我故意将`num1`作为第一个参数名,将其余的参数收集到`restNums`中。但是为什么呢?我们可以将它们全部收集到一个`nums`数组中,然后引用`nums[0]`。 -This function signature is an intentional hint at the recursive definition. It reads like this: +这个函数签名是递归定义的一个有意的提示。它是这样写的: ```txt maxEven( num1, ...restNums ): maxEven( num1, maxEven( ...restNums ) ) ``` -Do you see the symmetry between the signature and the recursive definition? +你看到签名和递归定义之间的对称性了吗? -When we can make the recursive definition more apparent even in the function signature, we improve the declarativeness of the function. And if we can then mirror the recursive definition from the signature to the function body, it gets even better. +当我们可以使递归定义更明显,甚至在函数签名,我们提高了函数的声明性。如果我们能将递归定义从签名映射到函数体,它会变得更好。 -But I'd say the most obvious improvement is that the distraction of the imperative `for`-loop is suppressed. All the looping logic is abstracted into the recursive call stack, so that stuff doesn't clutter the code. We're free then to focus on the logic of finding a max-even by comparing two numbers at a time -- the important part anyway! +但我想说,最明显的改进是抑制了“for”循环的干扰。所有循环逻辑都抽象到递归调用堆栈中,这样代码就不会混乱。然后我们就可以自由地把注意力集中在寻找最大值的逻辑上——即使是一次比较两个数字——无论如何,这是最重要的部分! -Mentally, what's happening is similar to when a mathematician uses a **Σ** summation in a larger equation. We're saying, "the max-even of the rest of the list is calculated by `maxEven(...restNums)`, so we'll just assume that part and move on." +在精神上,发生的事情类似于数学家在一个更大的方程中使用**∑**和。说的是,列表中的最大偶数是由`maxEven(...restNums)`计算的,所以我们假设这一部分并继续。 -Additionally, we reinforce that notion with the `restNums.length > 0` guard, because if there are no more numbers to consider, the natural result is that `maxRest` would have to be `undefined`. We don't need to devote any extra mental attention to that part of the reasoning. This base condition (no more numbers to consider) is clearly evident. +此外,我们使用`restNums.length > 0`保护来加强这一概念,因为如果没有更多的数字需要考虑,自然的结果是`maxRest`必须是`undefined`的。我们不需要对推理的那一部分投入任何额外的精神关注。这个基本条件(不再考虑数字)是显而易见的。 -Next, we turn our attention to checking `num1` against `maxRest` -- the main logic of the algorithm is how to determine which of two numbers, if any, is a max-even. If `num1` is not even (`num1 % 2 != 0`), or it's less than `maxRest`, then `maxRest` *has* to be `return`ed, even if it's `undefined`. Otherwise, `num1` is the answer. +接下来,我们将注意力转向检查`num1`与`maxRest`之间的关系——算法的主要逻辑是如何确定两个数字中哪个是最大偶数(如果有的话)。如果`num1`不是偶数(`num1 % 2 != 0`),或者它小于`maxRest`,那么`maxRest` 会被`return`,即使它是`undefined`。否则,`num1` 就是答案。 -The case I'm making is that this reasoning while reading an implementation is more straightforward, with fewer nuances or noise to distract us, than the imperative approach; it's **more declarative** than the `for`-loop with `-Infinity` version. +我要说明的是,在阅读实现时,这种推理比命令式方法更直观,很少有细微差别或干扰;它比for循环的`-Infinity`版本更具有声明性。 -**Tip:** We should point out that another (likely better!) way to model this besides manual iteration or recursion would be with list operations (see [Chapter 9](ch9.md)), with a `filter(..)` to include only evens and then a `reduce(..)` to find the max. We only used this example to illustrate the more declarative nature of recursion over manual iteration. +提示:我们应该指出,除了手动迭代或递归之外,另一种(可能更好的)建模方法是使用列表操作(参见[章节 9](ch9.md),使用`filter(..)`只包含偶数,然后使用`reduce(..)`来找到最大值。我们只是用这个例子来说明递归在手工迭代中的声明性。 -### Binary Tree Recursion +### 二叉树递归 -Here's another recursion example: calculating the depth of a binary tree. In fact, almost every operation you'll do with trees is implemented most easily with recursion, because manually tracking the stack up and down is highly imperative and error-prone. +下面是另一个递归例子:计算二叉树的深度。实际上,几乎所有使用树的操作都是通过递归最容易实现的,因为手动上下跟踪堆栈是非常必要的,而且容易出错。 -The depth of a binary tree is the longest path down (either left or right) through the nodes of the tree. Another way to define that is recursively -- the depth of a tree at any node is 1 (the current node) plus the greater of depths from either its left or right child trees: +二叉树的深度是沿着树的节点向下(左或右)的最长路径。另一种定义方法是递归的——树在任何节点的深度都是1(当前节点)加上它的左子树或右子树的深度: ```txt depth( node ): @@ -288,13 +287,13 @@ function depth(node) { } ``` -I'm not going to list out the imperative form of this algorithm, but trust me, it's a lot messier. This recursive approach is nicely and gracefully declarative. It follows the recursive definition of the algorithm very closely with very little distraction. +我不打算列出这个算法的命令式,但是相信我,它要复杂得多。这种递归方法具有良好的声明性。它严格遵循算法的递归定义,几乎没有干扰。 -Not all problems are cleanly recursive. This is not some silver bullet that you should try to apply everywhere. But recursion can be very effective at evolving the expression of a problem from more imperative to more declarative. +并不是所有的问题都是干净的递归的。这并不是你应该在任何地方使用的灵丹妙药。但是递归可以非常有效地将问题的表达式从命令式演化为更声明式。 -## Stack +## 栈 -Let's revisit the `isOdd(..)`/`isEven(..)` recursion from earlier: +让我们回顾一下前面的`isOdd(..)`/`isEven(..)`递归: ```js function isOdd(v) { @@ -308,19 +307,19 @@ function isEven(v) { } ``` -In most browsers, if you try this you'll get an error: +在大多数浏览器中,如果你尝试这个,你会得到一个错误: ```js isOdd( 33333 ); // RangeError: Maximum call stack size exceeded ``` -What's going on with this error? The engine throws this error because it's trying to protect your program from running the system out of memory. To explain that, we need to peek a little below the hood at what's going on in the JS engine when function calls happen. +这个错误是怎么回事?引擎抛出此错误,因为它试图保护您的程序不运行系统内存不足。为了解释这一点,我们需要了解一下当函数调用发生时JS引擎中发生了什么。 -Each function call sets aside a small chunk of memory called a stack frame. The stack frame holds certain important information about the current state of processing statements in a function, including the values in any variables. The reason this information needs to be stored in memory (in a stack frame) is because the function may call out to another function, which pauses the current function. When the other function finishes, the engine needs to resume the exact state from when it was paused. +每个函数调用都会留出一小块内存,称为堆栈帧。堆栈框架包含关于函数中处理语句当前状态的某些重要信息,包括任何变量中的值。此信息需要存储在内存中(在堆栈框架中)的原因是,函数可能会调用另一个函数,该函数将暂停当前函数。当其他函数完成时,引擎需要恢复暂停时的确切状态。 -When the second function call starts, it needs a stack frame as well, bringing the count to 2. If that function calls another, we need a third stack frame. And so on. The word "stack" speaks to the notion that each time a function is called from the previous one, the next frame is *stacked* on top. When a function call finishes, its frame is popped off the stack. +当第二个函数调用开始时,它也需要一个堆栈帧,使计数变为2。如果该函数调用另一个堆栈帧,则需要第三个堆栈帧。等等。单词“stack”表示每次从前一个函数调用一个函数时,下一个帧都被“堆叠”在上面。当一个函数调用结束时,它的帧从堆栈中弹出。 -Consider this program: +考虑一下这个程序: ```js function foo() { @@ -340,97 +339,99 @@ function baz() { baz(); ``` -Visualizing this program's stack frames step by step: +逐步可视化该程序的堆栈帧:

-**Note:** If these functions didn't call each other, but were just called sequentially -- like `baz(); bar(); foo();`, where each one finishes before the next one starts -- the frames won't stack up; each function call finishes and removes its frame from the stack before the next one is added. +注:如果这些函数没有互相调用,而是按顺序调用——比如`baz(); bar(); foo();`,其中每一个帧都在下一个帧开始之前结束——帧不会叠起来;每个函数调用结束并在添加下一个函数之前从堆栈中删除它的帧。 -OK, so a little bit of memory is needed for each function call. No big deal under most normal program conditions, right? But it quickly becomes a big deal once you introduce recursion. While you'd almost certainly never manually stack thousands (or even hundreds!) of calls of different functions together in one call stack, you'll easily see tens of thousands or more recursive calls stack up. +每个函数调用都需要一点内存。在大多数正常的程序条件下没什么大不了的,对吧?但一旦引入递归,它很快就会成为一个大问题。虽然您几乎肯定不会将不同函数的数千(甚至数百!)个调用手工堆叠在一个调用堆栈中,但是您很容易看到数万或更多递归调用堆叠在一起。 -The `isOdd(..)`/`isEven(..)` pairing throws a `RangeError` because the engine steps in at an arbitrary limit when it thinks the call stack has grown too much and should be stopped. This is not likely a limit based on actual memory levels nearing zero, but rather a prediction by the engine that if this kind of program was left running, memory usage would be runaway. It is impossible to know or prove that a program will eventually stop, so the engine has to make an informed guess. +`isOdd(..)`/`isEven(..)`配对会抛出一个`RangeError`,因为当引擎认为调用堆栈增长太多,应该停止时,它会以一个任意的限制介入。这可能不是基于实际内存水平接近于零的限制,而是引擎的一个预测,即如果让这类程序继续运行,内存使用将会失控。要知道或证明一个程序最终会停止是不可能的,因此引擎必须做出有根据的猜测。 -This limit is implementation dependent. The specification doesn't say anything about it at all, so it's not *required*. But practically all JS engines do have a limit, because having no limit would create an unstable device that's susceptible to poorly written or malicious code. Each engine in each different device environment is going to enforce its own limits, so there's no way to predict or guarantee how far we can run up the function call stack. +这个限制依赖于实现。规范根本没有说明它,所以它不是“必需的”。但实际上所有的JS引擎都有一个限制,因为没有限制将创建一个不稳定的设备,很容易编写不好或恶意代码。每个不同设备环境中的每个引擎都将执行自己的限制,因此无法预测或保证我们可以在多大程度上运行函数调用堆栈。 -What this limit means to us as developers is that there's a practical limitation on the usefulness of recursion in solving problems on non-trivially sized data sets. In fact, I think this kind of limitation might be the single biggest reason that recursion is a second-class citizen in the developer's toolbox. Regrettably, recursion is an afterthought rather than a primary technique. +对于开发人员来说,这个限制意味着递归在解决非平凡大小的数据集上的问题时的实用性存在实际限制。事实上,我认为这种限制可能是递归成为开发人员工具箱中的二等公民的最大原因。遗憾的是,递归是事后才想到的,而不是主要的技术。 -### Tail Calls +### 末尾调用 -Recursion far predates JS, and so do these memory limitations. Back in the 1960s, developers were wanting to use recursion and running up against hard limits of device memory of their powerful computers that were far lower than we have on our watches today. +递归远远早于JS,这些内存限制也是如此。早在20世纪60年代,开发人员就希望使用递归,并在其功能强大的计算机的设备内存的硬限制下运行,这些内存远远低于我们今天的手表。 -Fortunately, a powerful observation was made in those early days that still offers hope. The technique is called *tail calls*. +幸运的是,早期的一项强有力的观察仍然带来了希望。这种技术称为“末尾调用”。 -The idea is that if a call from function `baz()` to function `bar()` happens at the very end of function `baz()`'s execution -- referred to as a tail call -- the stack frame for `baz()` isn't needed anymore. That means that either the memory can be reclaimed, or even better, simply reused to handle function `bar()`'s execution. Visualizing: +其思想是,如果函数`baz()`对函数`bar()`的调用发生在函数`baz()`执行的末尾——称为末尾调用——那么就不再需要`baz()`的堆栈帧了。这意味着要么可以回收内存,要么可以更好地重用内存来处理函数`bar()`的执行。可视化:

-Tail calls are not really directly related to recursion, per se; this notion holds for any function call. But your manual non-recursion call stacks are unlikely to go beyond maybe 10 levels deep in most cases, so the chances of tail calls impacting your program's memory footprint are pretty low. +末尾调用本身并没有真正与递归直接相关;这个概念适用于任何函数调用。但是在大多数情况下,手动非递归调用栈的深度不太可能超过10个级别,因此末尾调用影响程序内存占用的几率非常低。 -Tail calls really shine in the recursion case, because it means that a recursive stack could run "forever", and the only performance concern would be computation, not fixed memory limitations. Tail call recursion can run in `O(1)` fixed memory usage. +末尾调用在递归情况下非常出色,因为它意味着递归堆栈可以“永远”运行,惟一的性能问题是计算,而不是固定的内存限制。尾调用递归可以运行在`O(1)`固定内存使用。 -These sorts of techniques are often referred to as Tail Call Optimizations (TCO), but it's important to distinguish the ability to detect a tail call to run in fixed memory space, from the techniques that optimize this approach. Technically, tail calls themselves are not a performance optimization as most people would think, as they might actually run slower than normal calls. TCO is about optimizing tail calls to run more efficiently. +这类技术通常称为末尾调用优化(Tail Call optimization, TCO),但重要的是要区分检测要在固定内存空间中运行的末尾调用的能力和优化此方法的技术。从技术上讲,tail调用本身并不是大多数人认为的性能优化,因为它们实际上可能比正常调用运行得慢。TCO是关于优化末尾调用以更有效地运行。 -### Proper Tail Calls (PTC) +### 正确的末尾调用 (PTC) -JavaScript has never required (nor forbidden) tail calls, until ES6. ES6 mandates recognition of tail calls, of a specific form referred to as Proper Tail Calls (PTC), and the guarantee that code in PTC form will run without unbounded stack memory growth. Practically speaking, this means we should not get `RangeError`s thrown if we adhere to PTC. +JavaScript从来没有要求(或禁止)末尾调用,直到ES6。ES6要求对末尾调用(称为适当的末尾调用(PTC))的特定形式的末尾调用进行识别,并保证PTC形式的代码在没有无限制堆栈内存增长的情况下运行。实际上,这意味着如果我们坚持PTC,就不会抛出RangeError。 -First, PTC in JavaScript requires strict mode. You should already be using strict mode, but if you aren't, this is yet another reason you should already be using strict mode. Did I mention, yet, you should already be using strict mode!? +首先,JavaScript中的PTC需要严格的模式。您应该已经使用了严格模式,但是如果您没有使用严格模式,这是您应该使用严格模式的另一个原因。我提过吗,你应该已经使用严格模式了!? -Second, a *proper* tail call looks like this: +其次,一个“适当的”尾部调用是这样的: ```js return foo( .. ); ``` -In other words, the function call is the last thing to execute in the surrounding function, and whatever value it returns is explicitly `return`ed. In this way, JS can be absolutely guaranteed that the current stack frame won't be needed anymore. +换句话说,函数调用是在周围函数中执行的最后一件事,它返回的任何值都显式地`return`。通过这种方式,JS可以绝对保证不再需要当前栈帧。 -These *are not* PTC: +这些都不是 PTC: ```js foo(); return; -// or +// 或者 var x = foo( .. ); return x; -// or +// 或者 return 1 + foo( .. ); ``` -**Note:** A JS engine, or a smart transpiler, *could* do some code reorganization to treat `var x = foo(); return x;` effectively the same as `return foo();`, which would then make it eligible for PTC. But that is not required by the specification. +注:一个JS引擎,或者一个智能换行器,*可以*做一些代码重组来处理`var x = foo(); return x;`实际上与`return foo();`相同,这将使它符合PTC的条件。但这不是规范所要求的。 + +`1 +`部分肯定是在 `foo(..)`完成后处理的,因此堆栈帧必须保持不变。 The `1 +` part is definitely processed *after* `foo(..)` finishes, so the stack frame has to be kept around. -However, this *is* PTC: +然而, 这样的是 PTC: ```js return x ? foo( .. ) : bar( .. ); ``` -After the `x` condition is computed, either `foo(..)` or `bar(..)` will run, and in either case, the return value will always be `return`ed back. That's PTC form. +在计算了`x`条件之后,将运行`foo(..)`或`bar(..)`,在这两种情况下,返回值总是`return。这就是PTC的形式。 -Binary (or multiple) recursion -- as shown earlier, two (or more!) recursive calls made at each level -- can never be fully PTC as-is, because all the recursion has to be in tail call position to avoid the stack growth; at most, only one recursive call can appear in PTC position. +二进制(或多个)递归——如前所述,在每个级别上执行两个(或多个)递归调用——永远不可能完全按原样执行PTC,因为所有递归都必须位于尾部调用位置,以避免堆栈增长;在PTC位置最多只能出现一个递归调用。 -Earlier, we showed an example of refactoring from binary recursion to mutual recursion. It may be possible to achieve PTC from a multiple-recursive algorithm by splitting each into separate function calls, where each is expressed respectively in PTC form. However, that type of intricate refactoring is highly dependent on the scenario, and beyond the scope of what we can cover in this text. +前面,我们展示了一个从二进制递归重构到互递归的例子。通过将每个函数调用分割成单独的函数调用,其中每个函数调用分别以PTC的形式表示,可以从一个多递归算法中实现PTC。然而,这种类型的复杂重构高度依赖于场景,并且超出了我们在本文中所能涵盖的范围。 -## Rearranging Recursion +## 重新安排递归 -If you want to use recursion but your problem set could grow enough eventually to exceed the stack limit of the JS engine, you're going to need to rearrange your recursive calls to take advantage of PTC (or avoid nested calls entirely). There are several refactoring strategies that can help, but there are of course trade-offs to be aware of. +如果您想使用递归,但是您的问题集最终可能增长到超过JS引擎的堆栈限制,那么您需要重新安排递归调用,以利用PTC(或者完全避免嵌套调用)。有几种重构策略可以提供帮助,但当然也有一些需要注意的权衡。 -As a word of caution, always keep in mind that code readability is our overall most important goal. If recursion along with some combination of the strategies described here results in code that is harder to read/understand, **don't use recursion**; find another more readable approach. +需要注意的是,始终牢记代码可读性是我们整体上最重要的目标。如果递归和这里描述的一些策略的组合导致代码更难读/理解,**不要使用递归**;寻找另一种更易于阅读的方法。 -### Replacing the Stack +### 更换栈 -The main problem with recursion is its memory usage, keeping around the stack frames to track the state of a function call while it dispatches to the next recursive call iteration. If we can figure out how to rearrange our usage of recursion so that the stack frame doesn't need to be kept, then we can express recursion with PTC and take advantage of the JS engine's optimized handling of tail calls. +递归的主要问题是它的内存使用,在函数调用被分派到下一个递归调用迭代时,保持堆栈帧的状态来跟踪函数调用的状态。如果我们能够重新安排递归的用法,使堆栈框架不需要保留,那么我们就可以使用PTC来表示递归,并利用JS引擎对末尾调用的优化处理。 -Let's recall the summation example from earlier: +让我们回忆一下前面的求和例子: ```js function sum(num1,...nums) { @@ -439,11 +440,11 @@ function sum(num1,...nums) { } ``` -This isn't in PTC form because after the recursive call to `sum(...nums)` is finished, the `total` variable is added to that result. So, the stack frame has to be preserved to keep track of the `total` partial result while the rest of the recursion proceeds. +这不是PTC形式,因为在递归调用`sum(...nums)` 完成后,`total`变量将添加到该结果中。因此,必须保留堆栈帧,以便在递归的其余部分继续进行时跟踪`total`部分结果。 -The key recognition point for this refactoring strategy is that we could remove our dependence on the stack by doing the addition *now* instead of *after*, and then forward-passing that partial result as an argument to the recursive call. In other words, instead of keeping `total` in the current function's stack frame, push it into the stack frame of the next recursive call; that frees up the current stack frame to be removed/reused. +这种重构策略的关键识别点是,我们可以通过添加*now*而不是*after*来消除对堆栈的依赖,然后将部分结果作为参数转发给递归调用。换句话说,不要将`total`保存在当前函数的堆栈帧中,而是将它推入下一次递归调用的堆栈帧中;这将释放当前堆栈帧,以便删除/重用。 -To start, we could alter our `sum(..)` function's signature to have a new first parameter as the partial result: +首先,我们可以改变我们的`sum(..)`函数的签名,以获得一个新的第一个参数作为部分结果: ```js function sum(result,num1,...nums) { @@ -451,7 +452,7 @@ function sum(result,num1,...nums) { } ``` -Now, we should pre-calculate the addition of `result` and `num1`, and pass that along: +现在,我们应该预先计算`result`和`num1`的加法,并传递下去: ```js "use strict"; @@ -463,17 +464,17 @@ function sum(result,num1,...nums) { } ``` -Now our `sum(..)` is in PTC form! Yay! +现在我们的`sum(..)`已经是PTC格式了!耶! -But the downside is we now have altered the signature of the function that makes using it stranger. The caller essentially has to pass `0` as the first argument ahead of the rest of the numbers they want to sum: +但是缺点是我们现在已经改变了函数的签名,使得使用它变得更加奇怪。调用者本质上必须将`0`作为第一个参数传递给他们想要求和的其他数字: ```js sum( /*initialResult=*/0, 3, 1, 17, 94, 8 ); // 123 ``` -That's unfortunate. +这是不幸的。 -Typically, people will solve this by naming their awkward-signature recursive function differently, then defining an interface function that hides the awkwardness: +通常,人们会通过不同的命名他们的笨拙签名递归函数来解决这个问题,然后定义一个接口函数来隐藏这种笨拙: ```js "use strict"; @@ -491,7 +492,7 @@ function sum(...nums) { sum( 3, 1, 17, 94, 8 ); // 123 ``` -That's better. Still unfortunate that we've now created multiple functions instead of just one. Sometimes you'll see developers "hide" the recursive function as an inner function, like this: +这是更好的。不幸的是,我们现在已经创建了多个函数,而不是一个。有时您会看到开发人员将递归函数“隐藏”为一个内部函数,如下所示: ```js "use strict"; @@ -509,7 +510,7 @@ function sum(...nums) { sum( 3, 1, 17, 94, 8 ); // 123 ``` -The downside here is that we'll re-create that inner `sumRec(..)` function each time the outer `sum(..)` is called. So, we can go back to them being side-by-side functions, but hide them both inside an IIFE, and expose just the one we want to: +这里的缺点是,每次调用外部`sum(..)`函数时,我们都会重新创建内部的`sumRec(..)`函数。所以,我们可以回到它们是并排的函数,但是把它们都隐藏在一个生命周期中,并且只暴露我们想要的一个: ```js "use strict"; @@ -531,11 +532,11 @@ var sum = (function IIFE(){ sum( 3, 1, 17, 94, 8 ); // 123 ``` -OK, we've got PTC and we've got a nice clean signature for our `sum(..)` that doesn't require the caller to know about our implementation details. Yay! +好的,我们已经有了PTC,并且我们已经为`sum(..)`有了一个干净漂亮的签名,它不需要调用者知道我们的实现细节。耶! -But... wow, our simple recursive function has a lot more noise now. The readability has definitely been reduced. That's unfortunate to say the least. Sometimes, that's just the best we can do. +但是…哇,我们简单的递归函数现在有很多干扰。可读性确实降低了。至少可以说,这是不幸的。有时候,这是我们能做的最好的了。 -Luckily, in some other cases, like the present one, there's a better way. Let's reset back to this version: +幸运的是,在其他一些情况下,比如现在,有一个更好的方法。让我们回到这个版本: ```js "use strict"; @@ -549,7 +550,7 @@ function sum(result,num1,...nums) { sum( /*initialResult=*/0, 3, 1, 17, 94, 8 ); // 123 ``` -What you might observe is that `result` is a number just like `num1`, which means that we can always treat the first number in our list as our running total; that includes even the first call. All we need is to rename those params to make this clear: +您可能会注意到,`result`是一个与`num1`类似的数字,这意味着我们总是可以将列表中的第一个数字作为运行总数;这甚至包括第一次通话。我们所需要做的就是重新命名这些参数,以明确这一点: ```js "use strict"; @@ -563,20 +564,20 @@ function sum(num1,num2,...nums) { sum( 3, 1, 17, 94, 8 ); // 123 ``` -Awesome. That's much better, huh!? I think this pattern achieves a good balance between declarative/reasonable and performant. +太棒了。好多了,是吧?我认为这种模式在声明性/合理性和性能之间取得了很好的平衡。 -Let's try refactoring with PTC once more, revisiting our earlier `maxEven(..)` (currently not PTC). We'll observe that similar to keeping the sum as the first argument, we can narrow the list of numbers one at a time, keeping the first argument as the highest even we've come across thus far. +让我们再次尝试用PTC进行重构,重新访问前面的`maxEven(..)`(目前还没有PTC)。我们将观察到,与将和作为第一个参数类似,我们可以一次缩小数字列表的范围,将第一个参数作为迄今为止遇到的最高参数。 -For clarity, the algorithm strategy (similar to what we discussed earlier) we might use: +为了清晰起见,我们可以使用以下算法策略(与我们前面讨论的类似): -1. Start by comparing the first two numbers, `num1` and `num2`. -2. Is `num1` even, and is `num1` greater than `num2`? If so, keep `num1`. -3. If `num2` is even, keep it (store in `num1`). -4. Otherwise, fall back to `undefined` (store in `num1`). -5. If there are more `nums` to consider, recursively compare them to `num1`. -6. Finally, just return whatever value is left in `num1`. +1. 首先比较前两个数字`num1`和`num2`。 +2. `num1`是偶数吗? `num1`是否大于`num2` ?如果是,保持`num1`。 +3. 如果`num2`是偶数,请保存它(存储在`num1`中)。 +4. 否则,返回到`undefined`(存储在`num1`中)。 +5. 如果需要考虑更多的`nums`,递归地将它们与`num1`进行比较。 +6. 最后,返回`num1`中剩下的任何值。 -Our code can follow these steps almost exactly: +我们的代码可以遵循这些步骤: ```js "use strict"; @@ -593,21 +594,21 @@ function maxEven(num1,num2,...nums) { } ``` -**Note:** The first `maxEven(..)` call is not in PTC position, but because it only passes in `num2`, it only recurses just that one level then returns right back out; this is only a trick to avoid repeating the `%` logic. As such, this call won't increase the growth of the recursive stack, any more than if that call was to an entirely different function. The second `maxEven(..)` call is the legitimate recursive call, and it is in fact in PTC position, meaning our stack won't grow as the recursion proceeds. +注意:第一个`maxEven(..)`调用不在PTC位置,但因为它只传递`num2`,它只递归那一层,然后直接返回;这只是一个避免重复“%”逻辑的技巧。因此,这个调用不会增加递归堆栈的增长,就像调用一个完全不同的函数一样。第二个`maxEven(..)`调用是合法的递归调用,它实际上位于PTC位置,这意味着我们的堆栈不会随着递归的进行而增长。 -It should be repeated that this example is only to illustrate the approach to moving recursion to the PTC form to optimize the stack (memory) usage. The more direct way to express a max-even algorithm might indeed be a filtering of the `nums` list for evens first, followed then by a max bubbling or even a sort. +应该重复一下,这个例子只是为了说明将递归移动到PTC表单以优化堆栈(内存)使用的方法。表达max-even算法的更直接的方法可能是先对偶数的`nums`列表进行过滤,然后进行冒泡或排序。 -Refactoring recursion into PTC is admittedly a little intrusive on the simple declarative form, but it still gets the job done reasonably. Unfortunately, some kinds of recursion won't work well even with an interface function, so we'll need different strategies. +诚然,将递归重构为PTC对简单的声明式表单有点干扰,但它仍然能够合理地完成工作。不幸的是,有些类型的递归即使使用接口函数也不能很好地工作,因此我们需要不同的策略。 -### Continuation Passing Style (CPS) +### 持续传递式 (CPS) -In JavaScript, the word *continuation* is often used to mean a function callback that specifies the next step(s) to execute after a certain function finishes its work. Organizing code so that each function receives another function to execute at its end is referred to as Continuation Passing Style (CPS). +在JavaScript中,“continuation”这个词通常用来表示一个函数回调,它指定某个函数完成工作后要执行的下一个步骤。组织代码,使每个函数接收另一个函数在其末端执行,这称为延续传递样式(CPS)。 -Some forms of recursion cannot practically be refactored to pure PTC, especially multiple recursion. Recall the `fib(..)` function earlier, and even the mutual recursion form we derived. In both cases, there are multiple recursive calls, which effectively defeats PTC memory optimizations. +某些递归形式实际上不能重构为纯PTC,尤其是多次递归。回想一下前面的`fib(..)`函数,甚至是我们派生的相互递归形式。在这两种情况下,都有多个递归调用,这有效地破坏了PTC内存优化。 -However, you can perform the first recursive call, and wrap the subsequent recursive calls in a continuation function to pass into that first call. Even though this would mean ultimately many more functions will need to be executed in the stack, as long all of them, continuations included, are in PTC form, stack memory usage will not grow unbounded. +但是,您可以执行第一次递归调用,并将随后的递归调用封装在一个延续函数中,以传递给第一次调用。尽管这意味着最终需要在堆栈中执行更多的函数,但只要所有函数(包括延续)都是PTC格式的,堆栈内存使用量就不会无限增长。 -We could do this for `fib(..)`: +我们可以对`fib(..)`这样做: ```js "use strict"; @@ -624,27 +625,29 @@ function fib(n,cont = identity) { } ``` -Pay close attention to what's happening here. First, we default the `cont(..)` continuation function as our [`identity(..)` utility from Chapter 3](ch3.md/#one-on-one); remember, it simply returns whatever is passed to it. +密切关注这里发生的事情。首先,我们默认`cont(..)`延续函数作为我们[第3章的`identity(..)`](ch3.md/#one-on-one);记住,它只是返回传递给它的任何内容。 -Moreover, not just one but two continuation functions are added to the mix. The first one receives the `n2` argument, which eventually receives the computation of the `fib(n-2)` value. The next inner continuation receives the `n1` argument, which eventually is the `fib(n-1)` value. Once both `n2` and `n1` values are known, they can be added together (`n2 + n1`), and that value is passed along to the next `cont(..)` continuation step. +此外,还添加了两个延续函数。第一个接收`n2`参数,它最终接收`fib(n-2)`值的计算。下一个内部延续接收`n1`参数,它最终是`fib(n-1)`值。一旦知道`n2`和`n1`的值,就可以将它们相加(`n2 + n1`),然后将该值传递给下一个`cont(..)`延续步骤。 -Perhaps this will help mentally sort out what's going on: just like in the previous discussion when we passed partial results along instead of returning them back after the recursive stack, we're doing the same here, but each step gets wrapped in a continuation, which defers its computation. That trick allows us to perform multiple steps where each is in PTC form. +也许这将有助于在头脑中理清正在发生的事情:就像在前面的讨论中,当我们传递部分结果而不是在递归堆栈之后返回它们时,我们在这里做的是相同的事情,但是每一步都被封装在一个延续中,这将延迟它的计算。这个技巧允许我们执行多个步骤,每个步骤都是PTC形式的。 -In static languages, CPS is often an opportunity for tail calls the compiler can automatically identify and rearrange recursive code to take advantage of. Unfortunately, that doesn't really apply to the nature of JS. +在静态语言中,CPS通常是尾部调用的机会,编译器可以自动识别并重新排列递归代码以加以利用。不幸的是,这并不适用于JS的本质。 -In JavaScript, you'd likely need to write the CPS form yourself. It's clunkier, for sure; the declarative notation-like form has certainly been obscured. But overall, this form is still more declarative than the `for`-loop imperative implementation. +在JavaScript中,您可能需要自己编写CPS表单。当然,它更笨拙;陈述句式的形式肯定被模糊了。但是总的来说,这个表单仍然比‘for’循环的命令式实现更具有声明性。 -**Warning:** One major caveat that should be noted is that in CPS, creating the extra inner continuation functions still consumes memory, but of a different sort. Instead of piling up stack frames, the closures just consume free memory (typically, from the heap). Engines don't seem to apply the `RangeError` limits in these cases, but that doesn't mean your memory usage is fixed in scale. +需要注意的一个重要警告是,在CPS中,创建额外的内部延续函数仍然会消耗内存,但是是另一种类型的内存。闭包只消耗空闲内存(通常是从堆中),而不是堆积堆栈帧。在这些情况下,引擎似乎没有应用“RangeError”限制,但这并不意味着内存使用是按比例固定的。 ### Trampolines -Where CPS creates continuations and passes them along, another technique for alleviating memory pressure is called trampolines. In this style of code, CPS-like continuations are created, but instead of passed in, they are shallowly returned. +尾调用函数层层嵌套,永不返回,然而在缺乏尾调用优化的语言中,并不知晓函数不会返回,状态、参数压栈依旧会发生,因此需要手动强制弹出下一层调用的函数,禁止解释器的压栈行为,这就是所谓的Trampoline + +CPS创建延续并传递它们,另一种减轻记忆压力的技术称为Trampoline。在这种风格的代码中,创建了类似于cps的延续,但不是传入,而是简单地返回。 -Instead of functions calling functions, the stack never goes beyond depth of one, because each function just returns the next function that should be called. A loop simply keeps running each returned function until there are no more functions to run. +与函数调用函数不同,堆栈的深度永远不会超过1,因为每个函数只返回应该调用的下一个函数。循环只是简单地运行每个返回的函数,直到没有更多的函数可以运行为止。 -One advantage with trampolines is you aren't limited to environments that support PTC; another is that each function call is regular, not PTC optimized, so it may run quicker. +Trampoline的一个优点是你不必局限于支持PTC的环境;另一个原因是每个函数调用都是常规的,而不是经过PTC优化的,因此它可能运行得更快。 -Let's sketch out a `trampoline(..)` utility: +让我们勾画出一个`trampoline(..)`实用程序: ```js function trampoline(fn) { @@ -660,9 +663,9 @@ function trampoline(fn) { } ``` -As long as a function is returned, the loop keeps going, executing that function and capturing its return, then checking its type. Once a non-function comes back, the trampoline assumes the function calling is complete, and just gives back the value. +只要函数被返回,循环就会继续,执行那个函数并捕获它的返回,然后检查它的类型。一旦一个非函数返回,Trampoline就假定函数调用已经完成,并返回值。 -Because each continuation needs to return another continuation, we'll need to use the earlier trick of forward-passing the partial result as an argument. Here's how we could use this utility with our earlier example of summation of a list of numbers: +因为每个延续都需要返回另一个延续,所以我们需要使用前面的技巧,将部分结果作为参数转发。下面是我们如何使用这个实用程序与我们之前的例子的总和的一组数字: ```js var sum = trampoline( @@ -681,18 +684,18 @@ for (let i=0; i<20000; i++) { sum( ...xs ); // 199990000 ``` -The downside is that a trampoline requires you to wrap your recursive function in the trampoline driving function; moreover, just like CPS, closures are created for each continuation. However, unlike CPS, each continuation function returned runs and finishes right away, so the engine won't have to accumulate a growing amount of closure memory while the call stack depth of the problem is exhausted. +缺点是Trampoline需要您将递归函数包装在Trampoline驱动函数中;而且,就像CPS一样,闭包是为每个延续创建的。但是,与CPS不同的是,返回的每个延续函数都立即运行并完成,因此当问题的调用堆栈深度耗尽时,引擎不必积累越来越多的闭包内存。 -Beyond execution and memory performance, the advantage of trampolines over CPS is that they're less intrusive on the declarative recursion form, in that you don't have to change the function signature to receive a continuation function argument. Trampolines are not ideal, but they can be effective in your balancing act between imperative looping code and declarative recursion. +除了执行和内存性能之外,与CPS相比,Trampoline的优势在于它们对声明式递归形式的干扰更小,因为您不必更改函数签名来接收延续函数参数。Trampoline并不是理想的,但是它们可以有效地平衡命令式循环代码和声明式递归。 -## Summary +## 总结 -Recursion is when a function recursively calls itself. Heh. A recursive definition for recursion. Get it!? +递归是指一个函数递归地调用自己。明白了吧! -Direct recursion is a function that makes at least one call to itself, and it keeps dispatching to itself until it satisfies a base condition. Multiple recursion (like binary recursion) is when a function calls itself multiple times. Mutual recursion is when two or more functions recursively loop by *mutually* calling each other. +直接递归是一个至少对自身进行一次调用的函数,它不断地对自身进行调度,直到满足基本条件。多重递归(类似于二进制递归)是指一个函数多次调用自身。相互递归是指两个或多个函数通过相互调用来递归循环。 -The upside of recursion is that it's more declarative and thus typically more readable. The downside is usually performance, but more memory constraints even than execution speed. +递归的好处是它更具有声明性,因此通常更易于阅读。缺点通常是性能,但是内存限制甚至比执行速度还多。 -Tail calls alleviate the memory pressure by reusing/discarding stack frames. JavaScript requires strict mode and proper tail calls (PTC) to take advantage of this "optimization". There are several techniques we can mix-n-match to refactor a non-PTC recursive function to PTC form, or at least avoid the memory constraints by flattening the stack. +尾部调用通过重用/丢弃堆栈帧来缓解内存压力。JavaScript需要严格的模式和适当的尾调用(PTC)来利用这种“优化”。有几种技术我们可以混合n-match来将非PTC递归函数重构为PTC形式,或者至少通过压扁堆栈来避免内存约束。 -Remember: recursion should be used to make code more readable. If you misuse or abuse recursion, the readability will end up worse than the imperative form. Don't do that! +请记住:应该使用递归使代码更具可读性。如果您误用或滥用递归,可读性将比命令式更差。别干那事!