├── .editorconfig ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.txt ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── ch11-code ├── fp-helpers.js ├── index.html ├── mock-server.js ├── stock-ticker-events.js ├── stock-ticker.css └── stock-ticker.js └── manuscript ├── README.md ├── apA.md ├── apB.md ├── apC.md ├── ch1.md ├── ch10.md ├── ch11.md ├── ch2.md ├── ch3.md ├── ch4.md ├── ch5.md ├── ch6.md ├── ch7.md ├── ch8.md ├── ch9.md ├── foreword.md ├── images ├── fig1.png ├── fig10.png ├── fig10.svg ├── fig11.png ├── fig11.svg ├── fig12.png ├── fig12.svg ├── fig13.png ├── fig13.svg ├── fig14.png ├── fig14.svg ├── fig15.png ├── fig15.svg ├── fig16.png ├── fig16.svg ├── fig17.png ├── fig17.svg ├── fig18.png ├── fig18.svg ├── fig19.png ├── fig19.svg ├── fig2.png ├── fig2.svg ├── fig3.png ├── fig3.svg ├── fig4.png ├── fig4.svg ├── fig5.png ├── fig5.svg ├── fig6.png ├── fig6.svg ├── fig7.png ├── fig7.svg ├── fig8.png ├── fig8.svg ├── fig9.png ├── fig9.svg ├── marketing │ ├── back-cover-hd.png │ ├── fljs-lightbulb.svg │ ├── front-cover-hd.png │ ├── front-cover-hd.tiff │ ├── front-cover-sd.png │ ├── front-cover-small.png │ ├── print-book-cover.pdf │ ├── print-book-cover.png │ ├── social-avatar.png │ └── social-header.png ├── paycode1.jpg └── paycode2.jpg └── preface.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | [*.md] 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please feel free to contribute to the quality of this content by submitting PR's for improvements to code snippets, explanations, etc. If there's any doubt or if you think that a word/phrase is used confusingly, **before submitting a PR, open an issue to ask about it.** 4 | 5 | However, if you choose to contribute content (not just typo corrections) to this repo, you agree that you're giving me a non-exclusive license to use that content for the book, as I (and any future publisher) deem appropriate. You probably guessed that already, but I just have to make sure the lawyers are happy by explicitly stating it. 6 | 7 | ## Search First! 8 | 9 | If you have any questions or concerns, please make sure to search the issues (both open and closed!) first, to keep the churn of issues to a minimum. I want to keep my focus on improving the content as much as possible. 10 | 11 | ## Typos? 12 | 13 | This book has already been edited. Most typos have already been caught and fixed, but there might still be some mistakes here and there. 14 | 15 | If you're going to submit a PR for typo fixes, please be measured in doing so, perhaps by collecting several small changes into a single PR (in separate commits). 16 | 17 | ## Reading Experience (Chapter/Section links, etc) 18 | 19 | This repo **is not optimized for your reading experience.** It's optimized for the publishing process. 20 | 21 | The primary intended reading experience -- likely the most pleasant one for many of you! -- is the ebook, [which is now on sale](https://leanpub.com/fljs). The balance I'm striking here is releasing the content for free, but selling the reading experience. Other authors make different decisions on that balance, but that's what I've come to for now. 22 | 23 | I hope you continue to enjoy and benefit from the content, and I also hope you value it enough to purchase the best reading experience in the ebook/print form. 24 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Yes, I promise I've read the [Contributions Guidelines](https://github.com/getify/Functional-Light-JS/blob/master/CONTRIBUTING.md)** (please feel free to remove this line -- if you leave this line here, I'm going to assume you didn't actually read it). 2 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Yes, I promise I've read the [Contributions Guidelines](https://github.com/getify/Functional-Light-JS/blob/master/CONTRIBUTING.md)** (please feel free to remove this line -- if you leave this line here, I'm going to assume you didn't actually read it). 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript轻量级函数式编程 2 | 3 | [![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/) 4 | 5 | ### 译者说 6 | 本书讲解如何规避一些不可预知的副作用,友好的书写js,封装的理念。让我们在认识这门js语言的同时,更加深入理解语言的底层思维,通过这些思维去灵活应用到我们的业务语言上。里面的例子浅显易懂,却都是我们常犯的一些错误,通过看这本书你会更加理解运用“what”与“how”。 7 | 8 | 由于术语与js语言的精深,里面的翻译全部由本人直译与理解意译,可能存在小误差,但是不影响阅读,如果想要更加了解作者的思维(外国人的思维表达也不一样),可以翻阅原著,如果能帮助你更加理解一门语言,帮你前行,那本书翻译的目的也就达到了,谢谢。 9 | 10 | ·····结束 11 | 12 | Book Cover 13 | 14 | 这是一本比较相对中稳,务实的去看待JS的函数式编程基础的书籍。第一版现在已完成。可免费在线阅读, 如果喜欢书籍可从以下渠道购买: 15 | 16 |

17 | Buy on Leanpub Buy on Manning Buy on Amazon 18 |

19 | 20 | 本书主要探讨了应用于javascript的函数式编程的核心原则。但本书与众不同的是,我们在处理这些原则时,没有沉溺于上面的繁重的术语中。我们研究了JS的函数式编程基本概念的一个子集,我把他称之为函数式编程基础,并将它应用到javascript中去。 21 | 22 | **注意:** 尽管书名中含有“轻”这个字眼,但我并不推荐把这本书归类到作为“初学者入门”或“记帐式”的那一类主题书中去。本书严谨严谨,内容翔实,在阅读深入之前,需要有扎实的JS知识基础。“轻”可以理解为范围有限;这本书对每个主题的理解要比在其他常见的书写函数式编程的JS书籍深入得多,而不是更广泛。 23 | 24 | 让我们看看现实中是怎么写的吧:“一个单元只是内函数类中的一个单位半群(又名:幺半群)”,这句话对我们来说是没有任何帮助的,除非你已经是基础函数中佼佼者(可惜我不是!)。 25 | 26 | 上面这并不是说这些术语没有意义,或者说函数式程序设计的理念不适用于他们。一旦你掌握了JavaScript轻量级函数式编程,你可能会希望能更正式地学习函数式的程序设计(FP),你肯定会接触到它们的含义和其中的原因。 27 | 28 | 但我希望您现在能够将函数式程序设计(fp)的一些基本原理应用到您的javascript中,因为我相信它将帮助您编写更好、更合理的代码。 29 | 30 | **要更多地了解这本书背后的动机和观点,请看[序言](manuscript/preface.md)。 ** 31 | 32 | ## 书籍 33 | 34 | [目录](manuscript/README.md/#table-of-contents) 35 | 36 | * [序言](manuscript/foreword.md/#foreword) 37 | * [前言](manuscript/preface.md/#preface) 38 | * [章节 1: 为什么要函数式编程?](manuscript/ch1.md/#chapter-1-why-functional-programming) 39 | * [章节 2: 函数的本质](manuscript/ch2.md/#chapter-2-the-nature-of-functions) 40 | * [章节 3: 管理函数输入](manuscript/ch3.md/#chapter-3-managing-function-inputs) 41 | * [章节 4: 组合函数](manuscript/ch4.md/#chapter-4-composing-functions) 42 | * [章节 5: 减少副作用影响](manuscript/ch5.md/#chapter-5-reducing-side-effects) 43 | * [章节 6: 值的不变性质](manuscript/ch6.md/#chapter-6-value-immutability) 44 | * [章节 7: 闭包与对象](manuscript/ch7.md/#chapter-7-closure-vs-object) 45 | * [章节 8: 递归](manuscript/ch8.md/#chapter-8-recursion) 46 | * [章节 9: 列表的操作](manuscript/ch9.md/#chapter-9-list-operations) 47 | * [章节 10: 函数的异步](manuscript/ch10.md/#chapter-10-functional-async) 48 | * [章节 11: 汇总](manuscript/ch11.md/#chapter-11-putting-it-all-together) 49 | * [附言 A: 转换](manuscript/apA.md/#appendix-a-transducing) 50 | * [附言 B: 卑微的单元](manuscript/apB.md/#appendix-b-the-humble-monad) 51 | * [附言 C: 函数式编程的库](manuscript/apC.md/#appendix-c-fp-libraries) 52 | 53 | 54 | ## Buy me a coffee 55 | if you like the book,just buy me a coffee. 56 | 57 | Book Cover 58 | Book Cover 59 | 60 | -------------------------------------------------------------------------------- /ch11-code/fp-helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var pipe = reverseArgs(compose); 4 | 5 | // curried list operators 6 | var map = unboundMethod( "map", 2 ); 7 | var filter = unboundMethod( "filter", 2 ); 8 | var filterIn = filter; 9 | var reduce = unboundMethod( "reduce", 3 ); 10 | var each = unboundMethod( "forEach", 2 ); 11 | var flatMap = curry( function flatMap(mapperFn,arr) { 12 | return arr.reduce( function reducer(list,v) { 13 | return list.concat( mapperFn( v ) ); 14 | }, [] ); 15 | } ); 16 | 17 | 18 | // ************************************ 19 | 20 | function filterOut(predicateFn,arr) { 21 | return filterIn( not( predicateFn ), arr ); 22 | } 23 | 24 | function unary(fn) { 25 | return function onlyOneArg(arg){ 26 | return fn( arg ); 27 | }; 28 | } 29 | 30 | function not(predicate) { 31 | return function negated(...args){ 32 | return !predicate( ...args ); 33 | }; 34 | } 35 | 36 | function reverseArgs(fn) { 37 | return function argsReversed(...args){ 38 | return fn( ...args.reverse() ); 39 | }; 40 | } 41 | 42 | function spreadArgs(fn) { 43 | return function spreadFn(argsArr){ 44 | return fn( ...argsArr ); 45 | }; 46 | } 47 | 48 | function partial(fn,...presetArgs) { 49 | return function partiallyApplied(...laterArgs){ 50 | return fn( ...presetArgs, ...laterArgs ); 51 | }; 52 | } 53 | 54 | function partialRight(fn,...presetArgs) { 55 | return function partiallyApplied(...laterArgs){ 56 | return fn( ...laterArgs, ...presetArgs ); 57 | }; 58 | } 59 | 60 | function curry(fn,arity = fn.length) { 61 | return (function nextCurried(prevArgs){ 62 | return function curried(nextArg){ 63 | var args = [ ...prevArgs, nextArg ]; 64 | 65 | if (args.length >= arity) { 66 | return fn( ...args ); 67 | } 68 | else { 69 | return nextCurried( args ); 70 | } 71 | }; 72 | })( [] ); 73 | } 74 | 75 | function uncurry(fn) { 76 | return function uncurried(...args){ 77 | var ret = fn; 78 | 79 | for (let i = 0; i < args.length; i++) { 80 | ret = ret( args[i] ); 81 | } 82 | 83 | return ret; 84 | }; 85 | } 86 | 87 | function zip(arr1,arr2) { 88 | var zipped = []; 89 | arr1 = [...arr1]; 90 | arr2 = [...arr2]; 91 | 92 | while (arr1.length > 0 && arr2.length > 0) { 93 | zipped.push( [ arr1.shift(), arr2.shift() ] ); 94 | } 95 | 96 | return zipped; 97 | } 98 | 99 | function compose(...fns) { 100 | return fns.reduceRight( function reducer(fn1,fn2){ 101 | return function composed(...args){ 102 | return fn2( fn1( ...args ) ); 103 | }; 104 | } ); 105 | } 106 | 107 | function prop(name,obj) { 108 | return obj[name]; 109 | } 110 | 111 | function setProp(name,obj,val) { 112 | var o = Object.assign( {}, obj ); 113 | o[name] = val; 114 | return o; 115 | } 116 | 117 | function unboundMethod(methodName,argCount = 2) { 118 | return curry( 119 | (...args) => { 120 | var obj = args.pop(); 121 | return obj[methodName]( ...args ); 122 | }, 123 | argCount 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /ch11-code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Functional-Light JavaScript, Ch11: Stock Ticker 6 | 7 | 8 | 9 |

Functional-Light JavaScript, Ch11: Stock Ticker

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ch11-code/mock-server.js: -------------------------------------------------------------------------------- 1 | function connectToServer() { 2 | // faking an event emitter attached to a server-event stream 3 | return evtEmitter; 4 | } 5 | 6 | 7 | // *********************************** 8 | // MOCK SERVER 9 | // *********************************** 10 | 11 | // simple/mock event emitter 12 | var evtEmitter = { 13 | handlers: {}, 14 | on(evtName,cb) { 15 | this.handlers[evtName] = this.handlers[evtName] || []; 16 | this.handlers[evtName][this.handlers[evtName].length] = cb; 17 | }, 18 | addEventListener(...args) { 19 | return this.on( ...args ); 20 | }, 21 | removeEventListener(){}, 22 | emit(evtName,...args) { 23 | for (let handler of (this.handlers[evtName] || [])) { 24 | handler(...args); 25 | } 26 | } 27 | }; 28 | 29 | var stocks = { 30 | "AAPL": { price: 121.95, change: 0.01 }, 31 | "MSFT": { price: 65.78, change: 1.51 }, 32 | "GOOG": { price: 821.31, change: -8.84 }, 33 | }; 34 | 35 | setTimeout( function initialStocks(){ 36 | for (let id in stocks) { 37 | // !!SIDE EFFECTS!! 38 | evtEmitter.emit( "stock", Object.assign( { id }, stocks[id] ) ); 39 | } 40 | }, 100 ); 41 | 42 | setTimeout( function randomStockUpdate(){ 43 | var stockIds = Object.keys( stocks ); 44 | var stockIdx = randInRange( 0, stockIds.length - 1 ); 45 | var change = (randInRange( 1, 10 ) > 7 ? -1 : 1) * 46 | (randInRange( 1, 10 ) / 1E2); 47 | 48 | var newStock = Object.assign( stocks[stockIds[stockIdx]] ); 49 | newStock.price += change; 50 | newStock.change += change; 51 | 52 | // !!SIDE EFFECTS!! 53 | stocks[stockIdx[stockIdx]] = newStock; 54 | evtEmitter.emit( "stock-update", Object.assign( { id: stockIds[stockIdx] }, newStock ) ); 55 | 56 | setTimeout( randomStockUpdate, randInRange( 300, 1500 ) ); 57 | }, 1000 ); 58 | 59 | 60 | // !!SIDE EFFECTS!! 61 | function randInRange(min = 0,max = 1E9) { 62 | return (Math.round(Math.random() * 1E4) % (max - min)) + min; 63 | } 64 | -------------------------------------------------------------------------------- /ch11-code/stock-ticker-events.js: -------------------------------------------------------------------------------- 1 | var server = connectToServer(); 2 | 3 | var formatDecimal = unboundMethod( "toFixed" )( 2 ); 4 | var formatPrice = pipe( formatDecimal, formatCurrency ); 5 | var formatChange = pipe( formatDecimal, formatSign ); 6 | var processNewStock = pipe( addStockName, formatStockNumbers ); 7 | var observableMapperFns = [ processNewStock, formatStockNumbers ]; 8 | var makeObservableFromEvent = curry( Rx.Observable.fromEvent, 2 )( server ); 9 | var mapObservable = uncurry( map ); 10 | 11 | var stockEventNames = [ "stock", "stock-update" ]; 12 | 13 | var [ newStocks, stockUpdates ] = pipe( 14 | map( makeObservableFromEvent ), 15 | curry( zip )( observableMapperFns ), 16 | map( spreadArgs( mapObservable ) ) 17 | ) 18 | ( stockEventNames ); 19 | 20 | 21 | // ********************* 22 | 23 | function addStockName(stock) { 24 | return setProp( "name", stock, stock.id ); 25 | } 26 | 27 | function formatStockNumbers(stock) { 28 | var stockDataUpdates = [ 29 | [ "price", formatPrice( stock.price ) ], 30 | [ "change", formatChange( stock.change ) ] 31 | ]; 32 | 33 | return reduce( function formatter(stock,[propName,val]){ 34 | return setProp( propName, stock, val ); 35 | } ) 36 | ( stock ) 37 | ( stockDataUpdates ); 38 | } 39 | 40 | function formatSign(val) { 41 | if (Number(val) > 0) { 42 | return `+${val}`; 43 | } 44 | return val; 45 | } 46 | 47 | function formatCurrency(val) { 48 | return `$${val}`; 49 | } 50 | -------------------------------------------------------------------------------- /ch11-code/stock-ticker.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | -moz-box-sizing: border-box; 3 | box-sizing: border-box; 4 | } 5 | 6 | #stock-ticker { 7 | background-color: #efefef; 8 | padding: 1em; 9 | margin: 0; 10 | width: 14em; 11 | } 12 | 13 | .stock { 14 | list-style-type: none; 15 | padding: 0; 16 | margin: 0 0 1em 0; 17 | } 18 | 19 | .stock:last-child { 20 | margin-bottom: 0; 21 | } 22 | 23 | .stock > span { 24 | display: inline-block; 25 | width: 4em; 26 | text-align: right; 27 | overflow: hidden; 28 | } 29 | 30 | .stock-name { 31 | font-weight: bold; 32 | text-align: center; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /ch11-code/stock-ticker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var stockTickerUI = { 4 | 5 | updateStockElems(stockInfoChildElemList,data) { 6 | var getDataVal = curry( reverseArgs( prop ), 2 )( data ); 7 | var extractInfoChildElemVal = pipe( 8 | getClassName, 9 | curry( stripPrefix )( /\bstock-/i ), 10 | getDataVal 11 | ); 12 | var orderedDataVals = 13 | map( extractInfoChildElemVal )( stockInfoChildElemList ); 14 | var elemsValsTuples = 15 | filterOut( function updateValueMissing([infoChildElem,val]){ 16 | return val === undefined; 17 | } ) 18 | ( zip( stockInfoChildElemList, orderedDataVals ) ); 19 | 20 | // !!SIDE EFFECTS!! 21 | compose( each, spreadArgs )( setDOMContent ) 22 | ( elemsValsTuples ); 23 | }, 24 | 25 | updateStock(tickerElem,data) { 26 | var getStockElemFromId = curry( getStockElem )( tickerElem ); 27 | var stockInfoChildElemList = pipe( 28 | getStockElemFromId, 29 | getStockInfoChildElems 30 | ) 31 | ( data.id ); 32 | 33 | return stockTickerUI.updateStockElems( 34 | stockInfoChildElemList, 35 | data 36 | ); 37 | }, 38 | 39 | addStock(tickerElem,data) { 40 | var [stockElem, ...infoChildElems] = map( 41 | createElement 42 | ) 43 | ( [ "li", "span", "span", "span" ] ); 44 | var attrValTuples = [ 45 | [ ["class","stock"], ["data-stock-id",data.id] ], 46 | [ ["class","stock-name"] ], 47 | [ ["class","stock-price"] ], 48 | [ ["class","stock-change"] ] 49 | ]; 50 | var elemsAttrsTuples = 51 | zip( [stockElem, ...infoChildElems], attrValTuples ); 52 | 53 | // !!SIDE EFFECTS!! 54 | each( function setElemAttrs([elem,attrValTupleList]){ 55 | each( 56 | spreadArgs( partial( setElemAttr, elem ) ) 57 | ) 58 | ( attrValTupleList ); 59 | } ) 60 | ( elemsAttrsTuples ); 61 | 62 | // !!SIDE EFFECTS!! 63 | stockTickerUI.updateStockElems( infoChildElems, data ); 64 | reduce( appendDOMChild )( stockElem )( infoChildElems ); 65 | appendDOMChild( tickerElem, stockElem ); 66 | } 67 | 68 | }; 69 | 70 | var getDOMChildren = pipe( 71 | listify, 72 | flatMap( 73 | pipe( 74 | curry( prop )( "childNodes" ), 75 | Array.from 76 | ) 77 | ) 78 | ); 79 | var createElement = document.createElement.bind( document ); 80 | var getElemAttrByName = curry( getElemAttr, 2 ); 81 | var getStockId = getElemAttrByName( "data-stock-id" ); 82 | var getClassName = getElemAttrByName( "class" ); 83 | var isMatchingStock = curry( matchingStockId, 2 ); 84 | var ticker = document.getElementById( "stock-ticker" ); 85 | var stockTickerUIMethodsWithDOMContext = map( 86 | pipe( partialRight, unary )( partial, ticker ) 87 | ) 88 | ( [ stockTickerUI.addStock, stockTickerUI.updateStock ] ); 89 | var subscribeToObservable = 90 | pipe( unboundMethod, uncurry )( "subscribe" ); 91 | var stockTickerObservables = [ newStocks, stockUpdates ]; 92 | 93 | // !!SIDE EFFECTS!! 94 | each( spreadArgs( subscribeToObservable ) ) 95 | ( zip( stockTickerUIMethodsWithDOMContext, stockTickerObservables ) ); 96 | 97 | 98 | // ********************* 99 | 100 | function stripPrefix(prefixRegex,val) { 101 | return val.replace( prefixRegex, "" ); 102 | } 103 | 104 | function listify(listOrItem) { 105 | if (!Array.isArray( listOrItem )) { 106 | return [ listOrItem ]; 107 | } 108 | return listOrItem; 109 | } 110 | 111 | function isTextNode(node) { 112 | return node && node.nodeType == 3; 113 | } 114 | 115 | function getElemAttr(prop,elem) { 116 | return elem.getAttribute( prop ); 117 | } 118 | 119 | function setElemAttr(elem,prop,val) { 120 | // !!SIDE EFFECTS!! 121 | return elem.setAttribute( prop, val ); 122 | } 123 | 124 | function matchingStockId(id,node) { 125 | return getStockId( node ) == id; 126 | } 127 | 128 | function isStockInfoChildElem(elem) { 129 | return /\bstock-/i.test( getClassName( elem ) ); 130 | } 131 | 132 | function getStockElem(tickerElem,stockId) { 133 | return pipe( 134 | getDOMChildren, 135 | filterOut( isTextNode ), 136 | filterIn( isMatchingStock( stockId ) ) 137 | ) 138 | ( tickerElem ); 139 | } 140 | 141 | function getStockInfoChildElems(stockElem) { 142 | return pipe( 143 | getDOMChildren, 144 | filterOut( isTextNode ), 145 | filterIn( isStockInfoChildElem ) 146 | ) 147 | ( stockElem ); 148 | } 149 | 150 | function appendDOMChild(parentNode,childNode) { 151 | // !!SIDE EFFECTS!! 152 | parentNode.appendChild( childNode ); 153 | return parentNode; 154 | } 155 | 156 | function setDOMContent(elem,html) { 157 | // !!SIDE EFFECTS!! 158 | elem.innerHTML = html; 159 | return elem; 160 | } 161 | -------------------------------------------------------------------------------- /manuscript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript轻量级函数式编程 2 | 3 | 4 | 5 | [![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) 6 | 7 | ## 目录 8 | 9 | * [前言](foreword.md/#foreword) 10 | * [序言](preface.md/#preface) 11 | * [章节 1: 为什么要函数式编程?](ch1.md/#chapter-1-why-functional-programming) 12 | * [At a Glance](ch1.md/#at-a-glance) 13 | * [Confidence](ch1.md/#confidence) 14 | * [Communication](ch1.md/#communication) 15 | * [Readability](ch1.md/#readability) 16 | * [Perspective](ch1.md/#perspective) 17 | * [How to Find Balance](ch1.md/#how-to-find-balance) 18 | * [Resources](ch1.md/#resources) 19 | * [章节 2: 函数的本质](ch2.md/#chapter-2-the-nature-of-functions) 20 | * [What Is A Function?](ch2.md/#what-is-a-function) 21 | * [Function Input](ch2.md/#function-input) 22 | * [Named Arguments](ch2.md/#named-arguments) 23 | * [Function Output](ch2.md/#function-output) 24 | * [Functions Of Functions](ch2.md/#functions-of-functions) 25 | * [Syntax](ch2.md/#syntax) 26 | * [What's This?](ch2.md/#whats-this) 27 | * [章节 3: 管理函数输入](ch3.md/#chapter-3-managing-function-inputs) 28 | * [All For One](ch3.md/#all-for-one) 29 | * [Adapting Arguments to Parameters](ch3.md/#adapting-arguments-to-parameters) 30 | * [Some Now, Some Later](ch3.md/#some-now-some-later) 31 | * [One At A Time](ch3.md/#one-at-a-time) 32 | * [Order Matters](ch3.md/#order-matters) 33 | * [No Points](ch3.md/#no-points) 34 | * [章节 4: 组合函数](ch4.md/#chapter-4-composing-functions) 35 | * [Output To Input](ch4.md/#output-to-input) 36 | * [General Composition](ch4.md/#general-composition) 37 | * [Reordered Composition](ch4.md/#reordered-composition) 38 | * [Abstraction](ch4.md/#abstraction) 39 | * [Revisiting Points](ch4.md/#revisiting-points) 40 | * [章节 5: 减少副作用影响](ch5.md/#chapter-5-reducing-side-effects) 41 | * [Effects On The Side, Please](ch5.md/#effects-on-the-side-please) 42 | * [Once Is Enough, Thanks](ch5.md/#once-is-enough-thanks) 43 | * [Pure Bliss](ch5.md/#pure-bliss) 44 | * [There Or Not](ch5.md/#there-or-not) 45 | * [Purifying](ch5.md/#purifying) 46 | * [章节 6: 值的不变性质](ch6.md/#chapter-6-value-immutability) 47 | * [Primitive Immutability](ch6.md/#primitive-immutability) 48 | * [Value To Value](ch6.md/#value-to-value) 49 | * [Reassignment](ch6.md/#reassignment) 50 | * [Performance](ch6.md/#performance) 51 | * [Treatment](ch6.md/#treatment) 52 | * [章节 7: 闭包与对象](ch7.md/#chapter-7-closure-vs-object) 53 | * [The Same Page](ch7.md/#the-same-page) 54 | * [Look Alike](ch7.md/#look-alike) 55 | * [Two Roads Diverged In A Wood...](ch7.md/#two-roads-diverged-in-a-wood) 56 | * [章节 8: 递归](ch8.md/#chapter-8-recursion) 57 | * [Definition](ch8.md/#definition) 58 | * [Declarative Recursion](ch8.md/#declarative-recursion) 59 | * [Stack](ch8.md/#stack) 60 | * [Rearranging Recursion](ch8.md/#rearranging-recursion) 61 | * [章节 9: 列表的操作](ch9.md/#chapter-9-list-operations) 62 | * [Non-FP List Processing](ch9.md/#non-fp-list-processing) 63 | * [Map](ch9.md/#map) 64 | * [Filter](ch9.md/#filter) 65 | * [Reduce](ch9.md/#reduce) 66 | * [Advanced List Operations](ch9.md/#advanced-list-operations) 67 | * [Method vs. Standalone](ch9.md/#method-vs-standalone) 68 | * [Looking For Lists](ch9.md/#looking-for-lists) 69 | * [Fusion](ch9.md/#fusion) 70 | * [Beyond Lists](ch9.md/#beyond-lists) 71 | * [章节 10: 函数的异步](ch10.md/#chapter-10-functional-async) 72 | * [Time As State](ch10.md/#time-as-state) 73 | * [Eager vs Lazy](ch10.md/#eager-vs-lazy) 74 | * [Reactive FP](ch10.md/#reactive-fp) 75 | * [章节 11: 汇总](ch11.md/#chapter-11-putting-it-all-together) 76 | * [Setup](ch11.md/#setup) 77 | * [Stock Events](ch11.md/#stock-events) 78 | * [Stock Ticker UI](ch11.md/#stock-ticker-ui) 79 | * [附言 A: 转换](apA.md/#appendix-a-transducing) 80 | * [首先弄清楚“为什么”](apA.md/#why-first) 81 | * [How, Next](apA.md/#how-next) 82 | * [What, Finally](apA.md/#what-finally) 83 | * [附言 B: 卑微的单子](apB.md/#appendix-b-the-humble-monad) 84 | * [Type](apB.md/#type) 85 | * [Loose Interface](apB.md/#loose-interface) 86 | * [Just a Monad](apB.md/#just-a-monad) 87 | * [Maybe](apB.md/#maybe) 88 | * [Humble](apB.md/#humble) 89 | * [附言 C: 函数式编程的库](apC.md/#appendix-c-fp-libraries) 90 | * [Stuff to Investigate](apC.md/#stuff-to-investigate) 91 | * [Ramda](apC.md/#ramda-0230) 92 | * [Lodash/fp](apC.md/#lodashfp-4174) 93 | * [Mori](apC.md/#mori-032) 94 | * [Bonus: FPO](apC.md/#bonus-fpo) 95 | * [Bonus #2: fasy](apC.md/#bonus-2-fasy) 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /manuscript/apB.md: -------------------------------------------------------------------------------- 1 | # 附言 B: 卑微的monoid(单子) 2 | 3 | 从这个附录我开始承认:在开始编写这个附录之前,我对单子是什么并不了解。我们犯了很多错误才了解到。如果您不相信我的话,请访问[这本书在github](https://github.com/getify/Functional-Light-JS)查看这个附录的提交历史! 4 | 5 | 我在书中包含了单子的主题,因为它是每个开发人员在学习FP时都会遇到的过程的一部分,正如我在本书中所写的那样。 6 | 7 | 我们基本上是以简短地浏览一下单子来结束这本书,而大多数FP文献几乎都是以单子开始的!在我的“轻量函数”编程中,我没有遇到需要显式地按照单子进行思考的情况,所以这就是为什么本文比主体核心更有价值。但这并不是说单子没有用处或不流行——它们非常流行。 8 | 9 | 在JavaScript 函数编程世界里有一个笑话,几乎每个人都必须编写自己的教程或博客文章来介绍单子是什么,就像单独编写单子是一种仪式一样。多年来,单子被各种各样地描述为墨西哥卷饼、洋葱和其他各种古怪的概念抽象。我希望这里不要发生那种愚蠢的事情! 10 | 11 | > 单子说白了不过就是自函子范畴上的一个幺半群而已 12 | 13 | 我们以这句引言作为序言,所以回到这里似乎是合适的。但不,我们不会讨论单子,内函子,或范畴理论。这句话不仅羞涩难懂,而且毫无帮助。 14 | 15 | 我希望你们从这次讨论中得到的是不要再害怕‘monad’这个词或者这个概念——我已经害怕了很多年了!——当你看到他们的时候能够认出他们。你可能,只是可能,偶尔会用到它们。 16 | 17 | ## 类型 18 | 19 | FP中有一个很大的领域是我们在整本书中都没有涉及的:类型理论。我不打算深入研究类型理论,因为坦白地说,我没有资格这么做。即使我做了,你也不会感激的。 20 | 21 | 但我要说的是monad基本上是一种值类型。 22 | 23 | 数字`42`有一个值类型(数字!),它带来了我们所依赖的某些特性和功能。字符串`42`可能看起来非常相似,但是在我们的程序中它有不同的用途。 24 | 25 | 在面向对象编程中,当您有一组数据(甚至是单个离散值),并且您有一些想要与之绑定的行为时,您将创建一个对象/类来表示该“类型”。实例就是该类型的成员。这种实践通常被称为“数据结构”。 26 | 27 | 我将在这里宽松地使用数据结构的概念,我们可能会发现,在程序中为某个值定义一组行为和约束,并将它们与该值捆绑到一个抽象中,是非常有用的。这样,当我们在程序中处理一个或多个这样的值时,它们的行为是免费的,并且会使处理它们更加方便。所谓方便,我的意思是对代码的读者来说更具有声明性和可接近性! 28 | 29 | monad是一种数据结构。这是一个类型。它是一组专门设计用来使使用值的工作变得可预测的行为。 30 | 31 | 回想一下在[第9章,我们讨论了函数变量](ch9.md/#a-word-functors):一个值和一个类似于map的实用程序,用于对其所有构成数据成员执行操作。monad(单子)是包含一些附加行为的函数变量。 32 | 33 | ## 零散接口 34 | 35 | 实际上,monad并不是一种单一的数据类型,它更像是一组相关的数据类型。它是一种根据不同值的需要实现不同的接口。每个实现都是不同类型的单子。 36 | 37 | 例如,您可能会读到关于"Identity Monad"、"IO Monad"、"Maybe Monad"、"Either Monad"或其他各种Monad。它们都定义了基本的单子行为,但是它根据每种单子类型的用例扩展或覆盖了交互。 38 | 39 | 它不仅仅是一个接口,因为使一个对象成为monad的不仅仅是某些API方法的存在。这些方法的相互作用有一定的保证,这是monadic的。这些众所周知的不变量对于使用单子是至关重要的,通过熟悉提高可读性;否则,它只是一个特殊的数据结构,必须被完全读取才能被读者理解。 40 | 41 | 事实上,对于这些单子方法的名称,甚至没有一个统一的协议,这是一个真正的接口所要求的;monad更像是一个松散的接口。有些人将某个方法称为`bind(..)`,有些人将其称为`chain(..)`,有些人将其称为`flatMap(..)`,依此类推。 42 | 43 | 因此monad是一个对象数据结构,具有足够的方法(实际上是任何名称或类型的方法),至少满足monad定义的主要行为需求。每一种单子在最小值以上都有一种不同的扩展。但是,因为它们在行为上都有重叠,所以将两种不同的单子放在一起使用仍然是直接和可预测的。 44 | 45 | 在这个意义上,单子有点像一个接口。 46 | 47 | ## Just monad 48 | 49 | 您将遇到的许多其他单子下面的基本单子称为Just。它只是一个简单的单子包装任何常规(又名,非空)的值。 50 | 51 | 由于monad是一种类型,您可能认为我们应该将`Just`定义为要实例化的类。这是一种有效的方法,但它在我不想处理的方法中引入了“this”绑定问题;相反,我将坚持使用一个简单的函数方法。 52 | 53 | 下面是一个基本的实现: 54 | 55 | ```js 56 | function Just(val) { 57 | return { map, chain, ap, inspect }; 58 | 59 | // ********************* 60 | 61 | function map(fn) { return Just( fn( val ) ); } 62 | 63 | // aka: bind, flatMap 64 | function chain(fn) { return fn( val ); } 65 | 66 | function ap(anotherMonad) { return anotherMonad.map( val ); } 67 | 68 | function inspect() { 69 | return `Just(${ val })`; 70 | } 71 | } 72 | ``` 73 | 74 | **注:**`inspect(..)`方法仅用于演示目的。它在单子意义上没有直接作用。 75 | 76 | 您将注意到,无论`val`值是多少,实例`Just(..)`都会保持不变。所有monad方法都创建新的monad实例,而不是修改monad的值本身。 77 | 78 | 如果现在这些都没有意义,不要担心。我们不会过分关注monad设计背后的细节或数学/理论。相反,我们将更专注于说明我们可以用它们做什么。 79 | 80 | ### 使用Monad(单子)方法工作 81 | 82 | 所有monad实例都有`map(..)`, `chain(..)`(也称为`bind(..)`或`flatMap(..)`)和`ap(..)`方法。这些方法及其行为的目的是提供一种标准化的方法来实现多个monad实例之间的交互。 83 | 84 | 让我们首先看看单子`map(..)`函数。就像数组上的`map(..)`(参见[第9章](ch9.md/#map))用它的值调用mapper函数并生成一个新的数组一样,monad的`map(..)`用monad的值调用mapper函数,无论返回什么都被包装在一个新的Just monad实例中: 85 | 86 | ```js 87 | var A = Just( 10 ); 88 | var B = A.map( v => v * 2 ); 89 | 90 | B.inspect(); // Just(20) 91 | ``` 92 | 93 | 单子`chain(..)`函数和`map(..)`做的是一样的,但它会从它的新单子中打开结果值。然而,相对于非正式地考虑“展开”monad,更正式的解释应该是`chain(..)`将monad扁平易懂。考虑: 94 | 95 | ```js 96 | var A = Just( 10 ); 97 | var eleven = A.chain( v => v + 1 ); 98 | 99 | eleven; // 11 100 | typeof eleven; // "number" 101 | ``` 102 | 103 | `eleven`是实际的原始数字`11`,而不是包含该值的单子。 104 | 105 | 为了从概念上将这个`chain(..)`方法与我们已经学过的东西联系起来,我们将指出,许多monad实现将这个方法命名为`flatMap(..)`。现在,回想一下[第9章`flatMap(..)`](ch9.md/#user-content-flatmap)(与`map(..)`相比)使用数组做了什么: 106 | 107 | ```js 108 | var x = [3]; 109 | 110 | map( v => [v,v+1], x ); // [[3,4]] 111 | flatMap( v => [v,v+1], x ); // [3,4] 112 | ``` 113 | 114 | 看出不同了吗?mapper函数`v => [v,v+1]`产生一个`[3,4]` 数组,它最终位于外部数组的第一个位置,因此我们得到`[[3,4]]`。但是`flatMap(..)`将内部数组展平到外部数组中,因此我们得到的只是 `[3,4]` 。 115 | 116 | monad的`chain(..)`(通常称为`flatMap(..)`)也是如此。不是像`map(..)`那样获得一个monad来保存值,而是`chain(..)`将monad扁平化到基础值中。实际上,`chain(..)`通常实现得更高效,而不是创建中间的单子只是为了立即使它变扁平化,只是走捷径而不是首先创建单子。无论哪种方式,最终结果都是一样的。 117 | 118 | 以这种方式说明 `chain(..)`的一种方法是结合`identity(..)`实用程序(参见[第3章](ch3.md/#one-on-one)),以便有效地从单子中提取一个值: 119 | 120 | ```js 121 | var identity = v => v; 122 | 123 | A.chain( identity ); // 10 124 | ``` 125 | 126 | `A.chain(..)`使用`A`中的值调用`identity(..)`,而不管`identity(..)`返回什么值(在本例中是`10`),都会直接返回,不需要任何中间的单子。换句话说,从前面的`Just(..)`代码清单中,我们实际上不需要包含可选的`inspect(..)` ,因为`chain(identity)`实现了相同的目标;这纯粹是为了便于调试,因此我们学习单子。 127 | 128 | 在这一点上,希望`map(..)`和`chain(..)`对您来说都是合理的。 129 | 130 | 相比之下,monad的`ap(..)`方法乍一看可能没有那么直观。这看起来像是一种奇怪的交互扭曲,但设计背后有深刻而重要的理由。让我们花点时间把它分解一下。 131 | 132 | `ap(..)`获取一个monad中包装的值,并使用另一个monad的 `map(..)`将其“应用”到另一个monad。到目前为止还好。 133 | 134 | 然而,`map(..)` 总是需要一个函数。这意味着你调用的monad`ap(..)`必须包含一个函数作为它的值,以传递给另一个monad`map(..)`。 135 | 136 | 困惑吗?是啊,不是你想的那样。我们将尝试简要说明,但只是希望这些事情困惑你的一段时间,直到你有了更多的探索了解和monads练习。 137 | 138 | 我们将`A`定义为包含值 `10`的单子, `B`定义为包含值`3`的单子: 139 | 140 | ```js 141 | var A = Just( 10 ); 142 | var B = Just( 3 ); 143 | 144 | A.inspect(); // Just(10) 145 | B.inspect(); // Just(3) 146 | ``` 147 | 148 | 现在,我们如何创建一个新的monad,其中的值`10`和`3`已经添加在一起,比如通过`sum(..)`函数?事实证明, `ap(..)`可以提供帮助。 149 | 150 | 为了使用`ap(..)`,我们说首先需要构造一个包含函数的单子。具体地说,我们需要一个函数本身包含`A`中的值(通过闭包记住)。让我们先理解一下。 151 | 152 | 要从`A`生成一个包含值函数的单子,我们调用`A.map(..)`,给它一个curried函数“记住”提取的值(参见[Chapter 3](ch3.md/#one-at-a-time)作为它的第一个参数。我们将这个新的功能包含单子`C`: 153 | 154 | ```js 155 | function sum(x,y) { return x + y; } 156 | 157 | var C = A.map( curry( sum ) ); 158 | 159 | C.inspect(); 160 | // Just(function curried...) 161 | ``` 162 | 163 | 想想如何运行的。`sum(..)`函数期望有两个值来完成它的工作,我们通过`A.map(..)` 将`10`提取出来并传递给它,从而得到第一个值。`C`现在保存了通过闭包记住`10`的函数。 164 | 165 | 现在,要获得第二个值(`3`内`B`)传递给在`C`等待的curried函数: 166 | 167 | ```js 168 | var D = C.ap( B ); 169 | 170 | D.inspect(); // Just(13) 171 | ``` 172 | 173 | 值`10`来自`C`,`3`来自`B`,`sum(..)`将它们加到`13`中,并将其封装在monad`D`中。让我们把这两个步骤放在一起,这样你就能更清楚地看到他们之间的联系: 174 | 175 | ```js 176 | var D = A.map( curry( sum ) ).ap( B ); 177 | 178 | D.inspect(); // Just(13) 179 | ``` 180 | 181 | 为了说明`ap(..)` 正在帮助我们做什么,我们可以通过以下方法获得相同的结果: 182 | 183 | ```js 184 | var D = B.map( A.chain( curry( sum ) ) ); 185 | 186 | D.inspect(); // Just(13); 187 | ``` 188 | 189 | 当然,这只是一个组合(见[第4章](ch4.md)): 190 | 191 | ```js 192 | var D = compose( B.map, A.chain, curry )( sum ); 193 | 194 | D.inspect(); // Just(13) 195 | ``` 196 | 197 | 酷吧! ? 198 | 199 | 如果到目前为止关于monad方法的讨论还不清楚,请回去重新阅读。如果解释还是难懂,那就坚持看完上面的。monad很容易让开发者困惑,这就是它的本质! 200 | 201 | ## Maybe 单子实例 202 | 203 | 在FP中很常见的是覆盖著名的单子,比如Maybe。实际上,Maybe monad是另外两个更简单的monad的特定组合:Just和Nothing。 204 | 205 | 我们已经看到Just;Nothing是一个包含空值的单子。也许是一个单子要么持有一个Just或一个Nothing。 206 | 207 | 下面是一个最简单的实现Maybe: 208 | 209 | ```js 210 | var Maybe = { Just, Nothing, of/* aka: unit, pure */: Just }; 211 | 212 | function Just(val) { /* .. */ } 213 | 214 | function Nothing() { 215 | return { map: Nothing, chain: Nothing, ap: Nothing, inspect }; 216 | 217 | // ********************* 218 | 219 | function inspect() { 220 | return "Nothing"; 221 | } 222 | } 223 | ``` 224 | 225 | **注:** `Maybe.of(..)`(有时称为`unit(..)`或`pure(..)`)是`Just(..)`的别名。 226 | 227 | 与`Just()`实例不同,`Nothing()`实例对所有monadic方法都有无操作定义的含义。因此,如果这样一个单子实例出现在任何monadic运算中,它的作用基本上是短路而不发生任何行为。请注意,这里没有强制执行“空”的含义——您的代码将决定这一点。稍后会详细介绍。 228 | 229 | 在Maybe中,如果一个值是非空的,它由`Just(..)`实例表示;如果它是空的,则由`Nothing()`实例表示。 230 | 231 | 但是这种monad表示的重要性在于,无论我们有一个`Just(..)`实例还是一个`Nothing()`实例,我们都将使用相同的API方法。 232 | 233 | 抽象的功能在于隐式地封装了行为/无操作对偶性。 234 | 235 | ### 不同的Maybe 236 | 237 | JavaScript的许多实现可能都包含一个检查(通常在`map(..)`中)来查看值是否为`null`/`undefined`,如果是,则跳过该行为。事实上,也许被鼓吹为有价值,正是因为它自动短路其行为与封装的空值检查。 238 | 239 | 下面是Maybe通常的表达方式: 240 | 241 | ```js 242 | // instead of unsafe `console.log( someObj.something.else.entirely )`: 243 | 244 | Maybe.of( someObj ) 245 | .map( prop( "something" ) ) 246 | .map( prop( "else" ) ) 247 | .map( prop( "entirely" ) ) 248 | .map( console.log ); 249 | ``` 250 | 251 | 换句话说,如果在链上的任何一点我们得到一个`null`/`undefined`值,那么可能会神奇地切换到无操作定义模式——现在是一个 `Nothing()`monad实例!——停止对链的其他部分做任何事情。这使得嵌套属性访问在某些属性丢失/为空时不会抛出JS异常。这很酷,而且是一个非常有用的抽象概念! 252 | 253 | 但是…这种Maybe不是一个纯粹的单子 254 | 255 | Monad的核心是它必须对所有值都有效,并且不能对值进行任何检查——甚至不能进行空检查。所以其他的实现都是为了方便而偷工减料。这并不是什么大事,但是当涉及到学习一些东西的时候,你应该先以最纯粹的形式来学习,然后再去改变规则。 256 | 257 | 我提供的Maybe monad的早期实现与其他Maybe的主要区别在于它没有空检查。此外,我们将`Maybe`表示为`Just(..)`/`Nothing()`的松散组合。 258 | 259 | 所以等等。如果我们没有自动短路,为什么Maybe有用呢?!?这似乎就是它的全部意义。 260 | 261 | 不要害怕!我们可以简单地在外部提供空检查,而Maybe monad的其他短路行为将正常工作。下面是如何实现以前的嵌套属性访问(`someObj.something.else.entirely`),但更“正确”: 262 | 263 | ```js 264 | function isEmpty(val) { 265 | return val === null || val === undefined; 266 | } 267 | 268 | var safeProp = curry( function safeProp(prop,obj){ 269 | if (isEmpty( obj[prop] )) return Maybe.Nothing(); 270 | return Maybe.of( obj[prop] ); 271 | } ); 272 | 273 | Maybe.of( someObj ) 274 | .chain( safeProp( "something" ) ) 275 | .chain( safeProp( "else" ) ) 276 | .chain( safeProp( "entirely" ) ) 277 | .map( console.log ); 278 | ``` 279 | 280 | 我们创建了一个`safeProp(..)`来执行空检查,如果是空检查,则选择`Nothing()` monad实例,或者将值包装在`Just(..)`实例中(通过`Maybe.of(..)`)。然后,我们不再使用`map(..)`,而是使用`chain(..)`,它知道如何“打开”`safeProp(..)`返回的单子。 281 | 282 | 当遇到空值时,我们会得到相同的链短路。我们只是不把这种逻辑嵌入Maybe中。 283 | 284 | monad的好处,可能更具体地说,是我们的 `map(..)`和`chain(..)`方法具有一致的和可预测的交互,不管哪种monad返回。这都很酷! 285 | 286 | ## Humble 单子实例 287 | 288 | 既然我们对“Maybe”和它的作用有了更多的了解,我将对它进行一点小小的改动——并在我们的讨论中加入一些自我幽默——通过发明Maybe+Humble monad。从技术上讲,`MaybeHumble(..)`本身不是一个单子,而是一个工厂函数,它生成一个可能的单子实例。 289 | 290 | Humble是一个公认的人为设计的数据结构包装器,它可能用于跟踪`egoLevel`数字的状态。具体地说,`MaybeHumble(..)`—生成的monad实例只有在其自我级别值足够低(小于`42`!)到被认为是humble时才会进行肯定的操作;否则它就是一个`Nothing()`、无操作定义。这听起来很像Maybe;真的非常像! 291 | 292 | 下面是我们的Maybe+Humble monad的工厂函数: 293 | 294 | ```js 295 | function MaybeHumble(egoLevel) { 296 | // accept anything other than a number that's 42 or higher 297 | return !(Number( egoLevel ) >= 42) ? 298 | Maybe.of( egoLevel ) : 299 | Maybe.Nothing(); 300 | } 301 | ``` 302 | 303 | 您会注意到,这个工厂函数有点像`safeProp(..)`,因为它使用一个条件来决定应该选择`Just(..)`还是 `Nothing()`作为Maybe的一部分。 304 | 305 | 让我们来说明一些基本用法: 306 | 307 | ```js 308 | var bob = MaybeHumble( 45 ); 309 | var alice = MaybeHumble( 39 ); 310 | 311 | bob.inspect(); // Nothing 312 | alice.inspect(); // Just(39) 313 | ``` 314 | 315 | 如果Alice赢得了一个大奖,现在对自己更自豪了呢? 316 | 317 | ```js 318 | function winAward(ego) { 319 | return MaybeHumble( ego + 3 ); 320 | } 321 | 322 | alice = alice.chain( winAward ); 323 | alice.inspect(); // Nothing 324 | ``` 325 | 326 | `MaybeHumble( 39 + 3 )`调用创建一个`Nothing()`monad实例从 `chain(..)`调用返回,因此现在Alice不再符合humble的条件。 327 | 328 | 在,让我们一起使用几个单子: 329 | 330 | ```js 331 | var bob = MaybeHumble( 41 ); 332 | var alice = MaybeHumble( 39 ); 333 | 334 | var teamMembers = curry( function teamMembers(ego1,ego2){ 335 | console.log( `Our humble team's egos: ${ego1} ${ego2}` ); 336 | } ); 337 | 338 | bob.map( teamMembers ).ap( alice ); 339 | // Our humble team's egos: 41 39 340 | ``` 341 | 342 | 回顾前面`ap(..)`的用法,我们现在可以解释这段代码是如何工作的。 343 | 344 | 因为`teamMembers(..)`是curried,所以`bob.map(..)`调用传递到`bob`,并创建一个monad实例,其中封装了剩余的函数。在monad调用`ap(alice)`,调用`alice.map(..)` ,并将函数从monad传递给它。其效果是,`bob`和`alice`monad的数值都提供给了`teamMembers(..)`函数,打印出了如下所示的消息。 345 | 346 | 然而,如果其中一个或两个单子实际上是`Nothing()` 实例: 347 | 348 | ```js 349 | var frank = MaybeHumble( 45 ); 350 | 351 | bob.map( teamMembers ).ap( frank ); 352 | // ..no output.. 353 | 354 | frank.map( teamMembers ).ap( bob ); 355 | // ..no output.. 356 | ``` 357 | 358 | `teamMembers(..)`永远不会被调用(也不会打印消息),因为`frank`是一个`Nothing()`实例。这就是Maybe monad的力量,而我们的`MaybeHumble(..)`允许我们进行选择。太酷了! 359 | 360 | ### Humility实例 361 | 362 | 再举一个例子来说明我们的Maybe+Humble数据结构的行为: 363 | 364 | ```js 365 | function introduction() { 366 | console.log( "I'm just a learner like you! :)" ); 367 | } 368 | 369 | var egoChange = curry( function egoChange(amount,concept,egoLevel) { 370 | console.log( `${amount > 0 ? "Learned" : "Shared"} ${concept}.` ); 371 | return MaybeHumble( egoLevel + amount ); 372 | } ); 373 | 374 | var learn = egoChange( 3 ); 375 | 376 | var learner = MaybeHumble( 35 ); 377 | 378 | learner 379 | .chain( learn( "closures" ) ) 380 | .chain( learn( "side effects" ) ) 381 | .chain( learn( "recursion" ) ) 382 | .chain( learn( "map/reduce" ) ) 383 | .map( introduction ); 384 | // Learned closures. 385 | // Learned side effects. 386 | // Learned recursion. 387 | // ..nothing else.. 388 | ``` 389 | 390 | 不幸的是,学习过程似乎被缩短了。你看,我发现学习一堆东西而不与他人分享会让你的自我膨胀得太厉害,对你的技能没有好处。 391 | 392 | 让我们尝试一个更好的学习方法: 393 | 394 | ```js 395 | var share = egoChange( -2 ); 396 | 397 | learner 398 | .chain( learn( "closures" ) ) 399 | .chain( share( "closures" ) ) 400 | .chain( learn( "side effects" ) ) 401 | .chain( share( "side effects" ) ) 402 | .chain( learn( "recursion" ) ) 403 | .chain( share( "recursion" ) ) 404 | .chain( learn( "map/reduce" ) ) 405 | .chain( share( "map/reduce" ) ) 406 | .map( introduction ); 407 | // Learned closures. 408 | // Shared closures. 409 | // Learned side effects. 410 | // Shared side effects. 411 | // Learned recursion. 412 | // Shared recursion. 413 | // Learned map/reduce. 414 | // Shared map/reduce. 415 | // I'm just a learner like you! :) 416 | ``` 417 | 418 | 学习时分享。这是学得更多、学得更好的最好方法。 419 | 420 | ## 总结 421 | 422 | 什么是单子?monad是具有封装行为的值类型、接口和对象数据结构。 423 | 424 | 但这些定义都不是特别有用。这里有一个更好的尝试:** monad是您如何以更声明的方式围绕一个值去组织行为方式。 425 | 426 | 就像这本书里的其他内容一样,在有用的地方使用单子,但不要仅仅因为别人在FP中谈论它们就使用单子。monad并不是万能的灵丹妙药,但如果使用得比较保守,它们确实提供了一些实用功能。 427 | -------------------------------------------------------------------------------- /manuscript/apC.md: -------------------------------------------------------------------------------- 1 | # JavaScript轻量级函数式编程 2 | # 附录C: 关于FP的库 3 | 当你把这本书从头读到尾,请花点时间回看一下[Chapter 1](ch1.md). 这是一段很长的旅程. 我希望你已经学到了很多,并且把函数式的精髓深入到你的编程中去。 4 | 5 | 我想把这本书合上来告诉你一些常用/流行的FP库的快速入门。接下来要介绍并不是一份详尽的文档,而是一个快速使你从轻量函数式进入更广泛的FP的入门介绍。 6 | 7 | 如果可能的话,我建议你 “不要” 再发明任何轮子。如果你找到一个符合你需要的FP库,就用它吧。-- 如果你实在找不到适合自己库,那么也使用本书中的示例程序 -- 或者你也可以自己发明一个。 8 | 9 | ## 需关注的工具库(注:我的理解) 10 | 11 | 让我们展开第1章 [需要注意的FP库列表,从第1章开始](ch1.md/#libraries),我们不可能涵盖所有这些内容(可能有很多相似),但以下应该是你要关注的库: 12 | 13 | * [Ramda](http://ramdajs.com): 通用的FP工具库 14 | * [Sanctuary](https://github.com/sanctuary-js/sanctuary): 类似Ramda的FP工具库 15 | * [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide): lodash的FP工具库 16 | * [functional.js](http://functionaljs.com/): 通用的FP工具库 17 | * [Immutable](https://github.com/facebook/immutable-js): facebook官方的,一个关于不可变数据的库 18 | * [Mori](https://github.com/swannodette/mori): 基于ClojureScript不可变数据的库 19 | * [Seamless-Immutable](https://github.com/rtfeldman/seamless-immutable): 关于不可变数据的辅助函数 20 | * [transducers-js](https://github.com/cognitect-labs/transducers-js): Transducers 21 | * [monet.js](https://github.com/monet/monet.js): Monadic Types 22 | 23 | 还有几十个其他优秀的库不在此列表中。仅仅因为它不在我的名单上并不意味着它不好,这里只是简单地浏览一下JavaScript中的FP。你可以[在这里](https://github.com/stoeffel/awesome-fp-js)找到更多关于FP的编程资源。 24 | 25 | 函数式编程世界中十分重要的学习资源,[Fantasy Land](https://github.com/fantasyland/fantasy-land) (简称 FL) ,与其说它是一个库,更像是一本百科全书。 26 | 27 | Fantasy Land(FL) 不仅仅是一份为初学者准备的轻量级读物,更是一个完整而详细的 JavaScript 函数式编程路线图。为了确保最大的通用性,FL 已经成为 JavaScript 函数式编程库遵循的业内标准。 28 | 29 | Fantasy Land (FL)与“轻量函数式编程”的概念几乎完全相反,它是 JavaScript 函数式编程世界中全面的毫无保留的一种诠释 。也就是说,当你的能力超越本书时,FL 将会成为你接下来前进的方向。我建议你将其保存在收藏夹,并在你使用本书的概念进行至少 6 个月的实战练习之后再回来。 30 | 31 | ## Ramda (0.23.0) (注:目前最新版本是0.26.1) 32 | 33 | 来自 [Ramda 文件](http://ramdajs.com/): 34 | 35 | > Ramda函数是自动被柯里化的. 36 | > 37 | > Ramda函数的参数进行了优化,使其便于柯里化。要操作的数据通常跟在最后。 38 | 39 | 我发现合理设计是Ramda的优势之一。还需要注意的是,Ramda的柯里化形式(似乎与大多数库一样)是 [我们第3章讨论的“松散柯里化”](ch3.md/#user-content-loosecurry)。 40 | 41 | 回想一下,[在第3章最后的示例](ch3.md/#user-content-finalshortlong),我们定义一个无参函数`printif(..) ` -- 在Ramda中可以这样定义: 42 | 43 | ```js 44 | function output(msg) { 45 | console.log( msg ); 46 | } 47 | 48 | function isShortEnough(str) { 49 | return str.length <= 5; 50 | } 51 | 52 | var isLongEnough = R.complement( isShortEnough ); 53 | 54 | var printIf = R.partial( R.flip( R.when ), [output] ); 55 | 56 | var msg1 = "Hello"; 57 | var msg2 = msg1 + " World"; 58 | 59 | printIf( isShortEnough, msg1 ); // Hello 60 | printIf( isShortEnough, msg2 ); 61 | 62 | printIf( isLongEnough, msg1 ); 63 | printIf( isLongEnough, msg2 ); // Hello World 64 | ``` 65 | 66 | 与第3章实现有点不同的是 [Chapter 3's approach](ch3.md/#user-content-finalshortlong): 67 | 68 | * 我们使用 `R.complement(..)` 代替 `not(..)` 基于`isShortEnough(..)` 创建一个相反函数 `isLongEnough(..)` 。 69 | 70 | * 我们使用 `R.flip(..)` 代替 `reverseArgs(..)`。需要注意的是,`R.flip(..)`只交换前两个参数,而`recseArgs(..)`则反转所有参数。在这种情况下,`flip(..)`对我们来说更方便,所以我们不需要使用`ParalRight(..)`或其他的方法。 71 | 72 | * `R.partial(..)`将其所有后续参数(函数以外)作为单个数组传入。 73 | 74 | * 由于Ramda 使用松散柯里化,因此我们不需要使用 `R.uncurryN(..)` 来取得一个所有参数的 `printIf(..)` 方法。如果我们这样做了,它看起来就像用 `R.uncurryN( 2,.. )` 包装了 `R.partial(..)` 来调用,这看似是没必要的。 75 | 76 | Ramda 是一个非常受欢迎和强大的函数库。如果您正在尝试将FP添加到您的代码库中,那么这是一个非常好的开始。 77 | 78 | ## Lodash/fp (4.17.4) 79 | 80 | Lodash 是整个JS生态系统中最受欢迎的函数库。Lodash团队也发布了一个 "FP-friendly" API版本 -- ["lodash/fp"](https://github.com/lodash/lodash/wiki/FP-Guide)。 81 | 82 | 在 [第9章,我们探讨了合并独立列表操作](ch9.md/#composing-standalone-utilities) (`map(..)`, `filter(..)`, 和 `reduce(..)`)。在"lodash/fp"中,我们可以这么做 : 83 | 84 | ```js 85 | var sum = (x,y) => x + y; 86 | var double = x => x * 2; 87 | var isOdd = x => x % 2 == 1; 88 | 89 | fp.compose( [ 90 | fp.reduce( sum )( 0 ), 91 | fp.map( double ), 92 | fp.filter( isOdd ) 93 | ] ) 94 | ( [1,2,3,4,5] ); // 18 95 | ``` 96 | 97 | 与我们熟悉的 `_.` 命名空间前缀不同,“lodash/fp”以 `fp.` 作为命名空间前缀定义其方法。我觉得这是个很有帮助的区别,而且比 `_.` 更容易理解! 98 | 99 | 需要注意的是,`fp.Composed(..)` (在lodash中称为`_.flowRight(..)`)接受一个函数数组,而不是单个函数参数。 100 | 101 | lodash的稳定性、广泛的社区支持和优秀性能是你探索FP的后盾。 102 | 103 | ## Mori (0.3.2) 104 | 105 | 在[第6章](ch6.md),我们已经简要地看过 Immutable.js 库,它可能是最著名的不可变数据结构库。 106 | 107 | 让我们看看另一个流行的库:[Mori](https://github.com/swannodette/mori) 。Mori在设计时采用了一套不同的API(表面上看更像FP):它使用独立的函数,而不是直接对值做操作。 108 | 109 | ```js 110 | var state = mori.vector( 1, 2, 3, 4 ); 111 | 112 | var newState = mori.assoc( 113 | mori.into( state, Array.from( {length: 39} ) ), 114 | 42, 115 | "meaning of life" 116 | ); 117 | 118 | state === newState; // false 119 | 120 | mori.get( state, 2 ); // 3 121 | mori.get( state, 42 ); // undefined 122 | 123 | mori.get( newState, 2 ); // 3 124 | mori.get( newState, 42 ); // "meaning of life" 125 | 126 | mori.toJs( newState ).slice( 1, 3 ); // [2,3] 127 | ``` 128 | 129 | 在这个例子中,Mori有一些有趣的地方需要指出: 130 | 131 | * 我们使用 `vector` 代替 `list` ,主要是因为文档说它的行为更像JS的数组。 132 | 133 | * 我们不能像操作JS数组那样在末尾随机设置值,这会引发异常报错。因此,我们必须首先使用 `morio .into(..)` 传入一个合适长度的数组来扩展 vector 的长度。当有一个43个插槽(注:索引值)(4 + 39)的vector,我们就可以使用 `mori.assoc(..)`方法将最后的索引位置 `42` 设置为`"meaning of life"`这个值。 134 | 135 | * 使用 `mori.into(..)` 创建一个更大的vector,然后使用 `mori.assoc(..)` 在此基础上创建另一个vector,这样的操作可能听起来效率不高。但是不可变数据结构的美妙之处在于数据没有克隆。每次进行“更改”时,新的数据结构只是跟踪与以前状态的差异。 136 | 137 | Mori深受ClojureScript的启发。 如果你有ClojureScript语言经验(或目前正在使用!),那么应该对它的API非常熟悉。由于我没有这种经验,我觉得方法名有点奇怪,不习惯。 138 | 139 | 我真心喜欢这种独立的调用函数设计,而不是基于值的调用方法。Mori还有一些自动返回常规JS数组的函数,用起来都很方便。 140 | 141 | ## 干货: FPO 142 | 143 | 在 [第2章中,我们介绍了一种模式](ch2.md/#named-arguments),用于处理称为“命名参数”的参数,在JS(注:ES6)中,使用对象将属性映射到析构函数的参数: 144 | 145 | ```js 146 | function foo( {x,y} = {} ) { 147 | console.log( x, y ); 148 | } 149 | 150 | foo( { 151 | y: 3 152 | } ); // undefined 3 153 | ``` 154 | 155 | 接着在[Chapter 3, 我们探讨了更多](ch3.md/#order-matters) 关于析构函数的柯里化和偏函数应用,像下面这样: 156 | 157 | ```js 158 | function foo({ x, y, z } = {}) { 159 | console.log( `x:${x} y:${y} z:${z}` ); 160 | } 161 | 162 | var f1 = curryProps( foo, 3 ); 163 | 164 | f1( {y: 2} )( {x: 1} )( {z: 3} ); 165 | ``` 166 | 167 | 这种风格的一个好处是能够以任何顺序传参(即使是柯里化和偏函数应用!),而不必像`reverseArgs(..)`式的传参。 另一个好处是能够省略一个可选参数,只不需传一个丑陋的占位符。 168 | 169 | 在我学习FP的过程中,我经常被固定位置传参感到沮丧,因此,我非常欣赏用命名参数风格(注:传入一个对象)解决以往的问题。 170 | 171 | 有一天,我在思考FP编码风格,我想如果整个FP库都以这种风格公开其所有API方法,那会是什么样子。我开始做尝试,不断把这些尝试展示给一些人看,并得到了一些积极的反馈。 172 | 173 | 经过努力,[FPO](https://github.com/getify/fpo) (发音为“ef-poh”) 库最终诞生了。FPO意思是FP-with-Objects。 174 | 175 | 官方文档: 176 | 177 | ```js 178 | // Ramda's `reduce(..)` 179 | R.reduce( 180 | (acc,v) => acc + v, 181 | 0, 182 | [3,7,9] 183 | ); // 19 184 | 185 | // FPO named-argument method style 186 | FPO.reduce({ 187 | arr: [3,7,9], 188 | fn: ({acc,v}) => acc + v 189 | }); // 19 190 | ``` 191 | 192 | 例如传统FP库(Ramda)的`reduce(..)`方法,初始参数的位置是固定不可变的。而FPO的`reduce(..)`方法可以按任意顺序传参,如果需要,甚至可以省略初始值。 193 | 194 | 与大多数其他FP库一样,FPO的API方法自动松柯里化,因此您不仅可以按任何顺序传参,还可以通过多次调用赋值方法来传入参数: 195 | 196 | ```js 197 | var f = FPO.reduce({ arr: [3,7,9] }); 198 | 199 | // later 200 | 201 | f({ fn: ({acc,v}) => acc + v }); // 19 202 | ``` 203 | 204 | 最后,在`FPO.std.*`命名文件下,你会发现它们与Ramda和其他库非常相似,所有FPO的API方法也可以使用传统固定位置参数操作: 205 | 206 | ```js 207 | FPO.std.reduce( 208 | (acc,v) => acc + v, 209 | undefined, 210 | [3,7,9] 211 | ); // 19 212 | ``` 213 | 214 | 如果FPO的命名参数写法对你有吸引力,你可以查看源码了解更多。 它拥有完整的测试套件和大多数你期待的FP方法,包括本文中介绍的所有内容,以帮助您更好的学习(使用)轻量级函数式编程! 215 | 216 | ## 干货 #2: fasy 217 | 218 | FP迭代方法(`map(..)`,`filter(..)`等)几乎总是被看作同步操作,意味着立即执行所有步骤。 事实上,其他FP模式,如合成甚至转换也是迭代,并且也以这种方式执行。 219 | 220 | 但是如果迭代中的一个或多个步骤需要异步完成,会发生什么?自然你会想到观察者模式(见[第10章](ch10.md/#observables)),但目前不是我们所要的。 221 | 222 | 让我快速说明一下。 223 | 224 | 想象一下,您有一个URL列表,表示您要加载到网页中的图像。 显然,提取图像是异步的。显然,这并不像你希望的那样顺序加载: 225 | 226 | ```js 227 | var imageURLs = [ 228 | "https://some.tld/image1.png", 229 | "https://other.tld/image2.png", 230 | "https://various.tld/image3.png" 231 | ]; 232 | 233 | var images = imageURLs.map( fetchImage ); 234 | ``` 235 | 236 | `images` 数组的内容基于`fetchImage(..)`方法执行,一旦它下载完成将返回一个promise的对象。 237 | 238 | 当然,你也可以使用`Promise.all(..)` 来等待所有的图片下载完成返回一个resolve对象,通过`then()`方法返回一个接收图片对象的函数: 239 | 240 | ```js 241 | Promise.all( images ) 242 | .then(function allImages(imgObjs){ 243 | // .. 244 | }); 245 | ``` 246 | 247 | 不幸的是,这个“技巧”只有在你要同时执行所有异步步骤(而不是串行,一个接一个)时才有效,并且只有当操作是一个`map(..)`调用时才有效。 如果你想要串行异步操作,或者你想同事使用`filter(..)`方法,这将可能返回错乱结果。 248 | 249 | 有些操作自然需要串行异步,例如异步`reduce(..)`,它显然需要一次从左到右工作;这些步骤不能同时运行,并且不能让该操作有任何意义。 250 | 251 | 正如我所说,可观察性(参见[第10章](ch10.md/#observables))不是这类任务的答案。原因是,一个可观察对象的异步协调是在单独的操作之间进行的,而不是在单个操作级别的步骤/迭代之间进行的。 252 | 253 | 另一种可视化这种区别的方法是,可观测支持“垂直异步”,而我所说的是“水平异步”。 254 | 255 | 考虑: 256 | 257 | ```js 258 | var obsv = Rx.Observable.from( [1,2,3,4,5] ); 259 | 260 | obsv 261 | .map( x => x * 2 ) 262 | .delay( 100 ) // <-- vertical asynchrony 263 | .map( x => x + 1 ) 264 | .subscribe( v => console.log ); 265 | // {after 100 ms} 266 | // 3 267 | // 5 268 | // 7 269 | // 9 270 | // 11 271 | ``` 272 | 273 | 如果出于某种原因,我想确保从第一个`map(..)`处理`1`到处理`2`之间有100毫秒的延迟,那么这就是我所指的“水平异步”。没有一种明朗的方法来模拟它。 274 | 275 | 那么,我们如何跨异步操作同时支持串行迭代和并发迭代呢? 276 | 277 | **fasy**(发音与“Tracy”相似,但带有“f”)是我为支持这类任务而构建的一个小实用程序库。你可以在这里找到更多关于它的[信息](https://github.com/getify/fasy)。 278 | 279 | 为了说明**fasy**,让我们考虑一个并发的`map(..)`与一个串行的`map(..)`: 280 | 281 | ```js 282 | FA.concurrent.map( fetchImage, imageURLs ) 283 | .then( function allImages(imgObjs){ 284 | // .. 285 | } ); 286 | 287 | FA.serial.map( fetchImage, imageURLs ) 288 | .then( function allImages(imgObjs){ 289 | // .. 290 | } ); 291 | ``` 292 | 293 | 在这两种情况下,`then(..)`处理程序只会在所有获取完全完成后调用。不同之处在于,所有的获取是同时启动(也就是“并行”),还是一次发出一个的。 294 | 295 | 你的直觉可能是同时进行总是更好的,虽然这可能是常见的,但并不总是这样。 296 | 297 | 例如,如果 `fetchImage(..)`维护一个获取图像的缓存,并在发出实际的网络请求之前检查缓存,结果会怎样?除此之外,如果“imageURLs”列表中可以有多个副本呢?在检查列表中稍后的重复图像URL之前,您肯定希望完成图像URL的第一次获取(并填充缓存)。 298 | 299 | 同样,在某些情况下不可避免地需要并发或串行异步。异步缩减总是串行的,而异步映射可能更倾向于并发,但在某些情况下也可能需要串行。这就是为什么**fasy**支持所有这些选项。 300 | 301 | 除了Observables,**fasy**将帮助您将更多fp模式和原则扩展到异步操作。 302 | 303 | ## 总结 304 | 305 | JavaScript并不是专门为FP语言而设计的。 但是它具备设计FP的该有的核心(如函数值,闭包等)。 就像上面我们探讨过的优秀的库。 306 | 307 | 通过学习本书中的知识,你可以开始开始coding了。 找一个好的,适合的FP库并使用之。练习,练习,练习!(注:多写才是王道) 308 | 309 | 看到这里,我已经分享了我所有了解的。 我认为你完全可以称之为“轻量级函数式编程”程序员! 现在是时候结束我们共同学习FP的“篇章”了。 但我的学习之旅仍在继续,我也希望你的确如此! 310 | -------------------------------------------------------------------------------- /manuscript/ch1.md: -------------------------------------------------------------------------------- 1 | # 章节1:为什么要函数式编程? 2 | 3 | > 函数编程人员:(名词)将变量命名为“x”,将函数命名为“f”,并将代码模式命名为“zygohistomorphic prepromorphism”。 4 | > 5 | > James Iry @jamesiry 5/13/15 6 | > 7 | > https://twitter.com/jamesiry/status/598547781515485184 8 | 9 | 函数式编程(FP)并不是一个新概念。但它几乎贯穿了整个编程历史。不过,我不确定这样说是否合理,但是…直到最近几年,函数式编程才成为整个开发界的主流观念。所以我觉得函数式编程更像是学者的领域。 10 | 11 | 然而一切都在改变。不仅仅是在语言层面,甚至是在库和框架方面对函数式编程的兴趣正在与日俱增。你很可能读到了这篇文章,因为你最后也会意识到函数式编程是一个你不能再忽视的东西。或者你像我一样,你以前学过很多次函数式编程,但是很难理解其中所有的术语或数学符号。 12 | 13 | 第一章的目的是回答诸如“为什么要在代码中使用函数式编程的风格”之类的问题。“JavaScript轻量级函数式编程“与其他人对函数式编程的看法相比如何?”在我们做好基础准备之后,通过在本书的其余部分中,我们将一块一块地揭示以轻量级风格编写JS的技术和模式。 14 | 15 | ## 快照 16 | 17 | 让我们用代码的前后快照简要说明“JavaScript轻量级函数式编程”的概念。思考下列代码: 18 | 19 | ```js 20 | var numbers = [4,10,0,27,42,17,15,-6,58]; 21 | var faves = []; 22 | var magicNumber = 0; 23 | 24 | pickFavoriteNumbers(); 25 | calculateMagicNumber(); 26 | outputMsg(); // The magic number is: 42 27 | 28 | // *************** 29 | 30 | function calculateMagicNumber() { 31 | for (let fave of faves) { 32 | magicNumber = magicNumber + fave; 33 | } 34 | } 35 | 36 | function pickFavoriteNumbers() { 37 | for (let num of numbers) { 38 | if (num >= 10 && num <= 20) { 39 | faves.push( num ); 40 | } 41 | } 42 | } 43 | 44 | function outputMsg() { 45 | var msg = `The magic number is: ${magicNumber}`; 46 | console.log( msg ); 47 | } 48 | ``` 49 | 50 | 现在考虑一种完全不同的风格,它可以实现相同的结果: 51 | 52 | ```js 53 | var sumOnlyFavorites = FP.compose( [ 54 | FP.filterReducer( FP.gte( 10 ) ), 55 | FP.filterReducer( FP.lte( 20 ) ) 56 | ] )( sum ); 57 | 58 | var printMagicNumber = FP.pipe( [ 59 | FP.reduce( sumOnlyFavorites, 0 ), 60 | constructMsg, 61 | console.log 62 | ] ); 63 | 64 | var numbers = [4,10,0,27,42,17,15,-6,58]; 65 | 66 | printMagicNumber( numbers ); // The magic number is: 42 67 | 68 | // *************** 69 | 70 | function sum(x,y) { return x + y; } 71 | function constructMsg(v) { return `The magic number is: ${v}`; } 72 | ``` 73 | 74 | 一旦你理解了函数式编程和轻量函数,第二个代码片段很可能是你阅读并脑中想着处理的方式: 75 | 76 | > 我们首先创建一个函数 `sumOnlyFavorites(..)` 这是其他三个函数的组合。 我们结合了两个过滤器, 一个检查值是否大于或等于10,一个检查值是否小于或等于20. 然后我们使用 `sum(..)` 减少数据传输. 结果函数 `sumOnlyFavorites(..)` 作为缩减作用,用于检查一个值是否通过两个过滤器,如果通过,则将该值添加到累加器值中。 77 | > 78 | > 然后我们使用定义好的函数 `sumOnlyFavorites(..)` 它可以首先减少一个数字列表,然后使用另一个函数 `printMagicNumber(..)` 打印产生通过“sumOnlyFavorites”计算出数字的总和. 函数 `printMagicNumber(..)` 把最后的总数再输送到 `constructMsg(..)`, 进入 `console.log(..)`打印创建一个字符串值结果. 79 | 80 | 所有这些处理函数与函数式编程的开发人员的”对话“就好像当前相当不熟悉函数式编程的你一样,这本书帮助你像跟其他你熟悉的代码一样跟这种方式进行”对话“ 81 | 82 | 关于此代码比较的其他一些简短说明: 83 | 84 | * 对于许多读者来说,前一个片段比后一个片段更接近舒适/可读/可维护性一些。如果是这样思考的话,也完全可以。你也在一个正确的位置下想的。我相信,如果你在整本书中坚持下去,并实践我们所谈论的一切,第二个片段最终会变得更加自然,甚至更可取! 85 | 86 | * 您可能已经完成了这项任务,可能这与所提供的任何一个代码片段都有显著的或完全的不同。也没关系。这本书并没有特定性的说你应该以一种特定的方式做某件事时。目的是说明各种模式的优缺点,并使您能够做出这些决定。在本书的最后,您将如何处理这个任务可能会比现在更接近第二个片段。 87 | 88 | * 也有可能你已经是一个经验丰富的函数式编程开发人员,正在浏览这本书的开头,看看它是否有任何有用的东西供你阅读。第二个片段肯定有一些非常熟悉的片段。但我敢打赌你会想,“嗯,我不会那样做的……”好几次。没关系,完全合理 89 | 90 | 这不是一本传统的、规范的函数式编程书。我们的方法有时会显得很离经叛道。我们正在寻求在函数式编程明显的不可否认的好处与需要运送可操作、可维护的JS之间达成一个务实的平衡,而不必处理数学/符号/术语这座令人望而生畏的大山。这不是你独有的函数式编程,它是“js的轻量函数式编程”。 91 | 92 | 无论你出于何目的翻阅本书,欢迎加入我们! 93 | 94 | ## 信心 95 | 96 | 我有一个非常简单的前提,那就是作为一名软件开发教师,我所做的每一件事情的基础(在javascript中):您不能信任的代码是您不理解的代码。反过来也是正确的:你不理解的代码是你不能信任的代码。此外,如果您不能信任或理解您的代码,那么您就不能对您编写的代码是否适合该任务。你运行程序,基本上就是交叉手指,祈祷没有问题发生了。 97 | 98 | 我所说的信任是什么意思?我的意思是,你可以通过阅读和推理,而不仅仅是执行来验证你理解一段代码将要做什么;而不是依赖它应该做什么的层面上。通常情况下,我们倾向于依靠运行测试来验证程序的正确性。我不是说测试不好。但是我认为我们应该渴望能够充分理解我们的代码,这样我们就知道测试在运行之前会通过。. 99 | 100 | 仅仅通过阅读代码就能让他们对我们的程序有更多信心,形成函数式编程的技术是以这样的心态设计的。理解函数式编程的人,并且有足够的自我约束在他们的程序中频繁地使用它,他们将编写代码,他们和其他人可以阅读并验证程序如他们想的一样运行。 101 | 102 | 当我们使用避免或最小化可能的错误源的技术时,信心也会大大增强。这可能是函数式编程最大卖点之一:函数式编程的程序通常比较少的错误,而且存在的错误通常在一些更明显的地方,因此更容易找到和修复。函数式编程代码趋向于更具防bug性——当然不只是为防代码错误的。 103 | 104 | 在阅读本书的过程中,您将开始对编写的代码培养更多的信心,因为您将使用已经很好证明的模式和实践;并且您将避免最常见的程序错误! 105 | 106 | ## 沟通 107 | 108 | 为什么函数式编程很重要?为了回答这个问题,我们需要后退一大步,讨论为什么编程本身很重要。 109 | 110 | 听到这个可能会让你吃惊,但我不认为代码只是计算机的一组指令。实际上,我认为代码指示计算机这几乎是一个愉快的意外。 111 | 112 | 我非常深刻地相信,代码的更重要的作用是作为与其他人交流的一种手段。 113 | 114 | 你可能从经验中知道,你花在“编码”上的大量时间实际上是花在阅读现有代码上。我们中很少有人享有这样的特权:把全部或大部分时间都花在简单地敲出所有新代码上,从不处理别人(或我们过去的自己)写的代码上。 115 | 116 | 据广泛估计,开发人员将70%的代码维护时间花在阅读上以理解它。这真让人大开眼界。居然达到了70%。难怪程序员每天编写的代码行数的平均值大约是10行。我们每天花7个小时来阅读代码,去理解这10行怎么运行! 117 | 118 | 我们需要更加关注代码的可读性。还得提一下,可读性不仅仅是字符数的减少,可读性实际上最受熟悉度的影响。 119 | 1 120 | 121 | 如果我们要花时间来写更易于阅读和理解的代码,那么函数式编程就是这项工作的核心。函数式编程的原则是建立良好的,深入研究和审查,并可证实的。花时间学习和使用这些函数式编程原则最终将为您和其他人带来更容易识别的熟悉代码。代码熟悉度的提高以及识别的便利性将提高代码的可读性。 122 | 123 | 124 | 例如,一旦您学习了“map(…)”的功能,当您在任何程序中看到它时,您几乎可以立即发现并理解它。 但是每次你看到一个“for”循环,你就必须阅读整个循环才能理解它。“for”循环的语法可能是熟悉的,但实际上它所做的并不是;你每次都必须*读*才能理解。 125 | 126 | 通过拥有看一眼就能识别的代码的能力,从而减少时间去了解代码在做什么,我们的注意力被释放出来,去思考更高层次的程序逻辑;那些都是最需要我们关注的重要内容。 127 | 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. 128 | 129 | 函数式编程 (至少,没有所有的术语来衡量它) 是制作可读代码最有效的工具之一。 这也是它如此重要的原因。 130 | 131 | ## 可读性 132 | 133 | 可读性不是一个二进制特性。这在很大程度上是描述我们与代码关系的主观因素。随着时间的推移,我们的技能和理解自然会发生变化。我曾经历过类似下图的效果,而且我也曾与许多人聊过关于这些有趣的事。 134 | 135 |

136 | 137 |

138 | 139 | 你可能会发现,当你读这本书的时候,你也会有类似的感受。但振作起来;如果你坚持下去,曲线就会上升! 140 | 141 | 命令式代码描述我们大多数人已经自然编写的代码;它专注于精确指导计算机“如何”做某事。而我们将学习编写的声明式代码,它遵循函数式编程原则是更专注于描述结果输出的代码。 142 | 143 | 让我们回顾本章前面介绍的两个代码片段。 144 | 145 | 第一个片段是命令式的,几乎完全集中于“如何”完成任务;它充斥着“if”语句、“for”循环、临时变量、重新分配、值突变、带有副作用的函数调用以及函数之间的隐式数据流。当然,你“可以”通过它的逻辑来查看数字是如何流动和更改到最终状态的,但它一点也不清楚或直接。 146 | 147 | 第二个片段更具声明性一些;它消除了前面提到的大多数命令式技术。注意没有显式的条件、循环、副作用、重新分配或突变;相反,它使用我们所说的函数式编程和可信的模式,如过滤、还原、转换和组合。注重从低级别的“如何”转移到高级的“结果”。 148 | 149 | 我们没有使用“if”语句来测试一个数字,而是将其委托给一个函数式编程里的实用程序,如“gte(..)”(大于或等于)去操作,然后将重点放在更重要的任务上,即将该过滤器与另一个过滤器和求和函数组合起来,得到我们想要的结果。 150 | 151 | 此外,通过第二个程序的数据流是明确的: 152 | 153 | 1. 一系列数字经过函数 `printMagicNumber(..)`. 154 | 2. 这些数字由“sumOnlyFavorites(..)”依次次处理,最后结果得到一个数字,其中得出一个我们最喜欢的数字 155 | 3. 这个总数被“constructMsg(..)”函数转换为一个带有的消息字符串。 156 | 4. 消息字符串通过`console.log(..)`打印出来. 157 | 158 | 从上面可以看到命令式代码片段更容易理解,但您可能仍然觉得这种方法很复杂,你的习惯与熟悉度对可读性的判断有深刻的影响。不过,在本书的最后,您将会潜移默化的了解到第二个代码片段的声明性方法的优点,并且熟悉性将使其可读性更强。 159 | 160 | 我知道让你相信这一点是一种信仰的飞跃。 161 | 162 | 正如我所建议的,要提高它的可读性,并最小化或消除导致bug的许多错误,需要付出更多的努力,有时还需要编写更多的代码。坦白地说,当我开始写这本书的时候,我可能还完全写不出(甚至完全理解)第二段。当我更深入学习的时候,一切都变得自然与舒适。 163 | 164 | 如果您希望使用函数式编程重构,那这就像一个神奇的银弹,能够快速地将您的代码转换为更优雅、更优雅、更聪明、更有弹性、更简洁的代码(短期内很容易实现),万幸的是,这是一个现实的期望不难实现。 165 | 166 | 函数式编程是一种非常不同的方式来考虑代码应该如何构造,从而使数据流更加明显,并帮助读者跟随您的想法。这需要时间。这一努力非常值得,但可能是一段艰苦的旅程。 167 | 168 | 通常,我仍然需要多次尝试将命令式代码片段重构为更具声明性的函数式编程模式的代码,然后才能得到一些足够清晰的代码,以便以后能够理解。我发现转换到函数式编程模式是一个缓慢的迭代过程,不像从一个范例到另一个范例的二进制转换那样快速。 169 | 170 | 我还将“以后再教”测试应用到我编写的每段代码中。在我写完一段代码后,我会把它放在一边几个小时或几天,然后回来,试着用新的眼光来读它,假装我需要教别人或向别人解释它。通常,这样给别人解释的时候相当混乱,而且要不断的调整那些代码! 171 | 172 | 我不是想让你扫兴。我真希望你能解开疑惑。我很高兴我做到了。我最终可以看到上面图像解释的曲线向上弯曲的状态,改造代码以提高可读性。这些努力是值得的。 173 | 174 | ## 客观判断 175 | 176 | 大多数FP文本似乎都采取了自上而下的方法,但我们会以相反的方向:从头开始,我们将揭示基本的基本原则,我相信正式的FP使用者会承认这是他们所做一切的脚手架。但在很大程度上,我们会与大多数吓人的术语或数学符号保持一定距离,因为这些术语或数学符号很容易让学习者感到沮丧。 177 | 178 | 我相信你所说的东西不那么重要,更重要的是你要了解它是什么以及它是如何工作的。这并不是说共享术语不重要——毫无疑问,它简化了经验丰富的专业人员之间的交流。但对于学习者来说,我发现这会分散注意力。 179 | 180 | 因此,这本书将会更多地集中在基本概念,而不是花里胡哨的说些无意义的事情上。这并不是说没有术语,而是肯定会有术语。但不要过于沉溺于复杂的语言中。在必要的时候,超越他们去考虑这些想法。 181 | 182 | 我把这些不太正式的实践称为“轻量编程”,我认为真正的FP的形式主义所受害的地方是,如果你还不习惯于正式的思想,它可能是非常压倒性的。我不只是简单猜测,从我自己的故事中可以说明。即使在教过FP和写过这本书之后,我仍然可以说,FP中的术语和符号的形式主义对我来说是非常难以处理的。我试过又试,但还是觉得难以处理。 183 | 184 | 我知道许多人相信形式主义的方式有助于学习。但我认为很明显这是一个误解,只有当你对形式主义有了一定的了解后,才会认识到。如果你恰好已经有了数学背景,甚至是一些CS经验,这对你来说可能更自然。但是我们中的一些人没有,不管我们多么努力,形式主义总是阻碍我们。 185 | 186 | 所以这本书介绍了我相信函数编程是建立在其基础上的概念,但它是通过从下面给你一个动力来爬上悬崖峭壁,而不是屈尊地从顶部冲你喊叫,督促你去弄清楚怎么爬。 187 | 188 | ## 如何找到平衡 189 | 190 | 如果你已经在编程方面工作很长时间了,很可能你以前听过“YAGNI”这个缩写:“You Ain’t Gonna Need It”(“你不需要它”)。这个原则主要来自于极端编程,强调在需要之前构建一个特性的高风险和成本。. 191 | 192 | “YAGNI”挑战让我们要记住:即使在某种情况下这是违反直觉的,我们通常也应该推迟建造一些东西,直到目前需要它为止。我们倾向于夸大我们对未来重构的估计,即在需要时稍后添加重构的成本。很可能,以后做起来并不像我们想象的那么困难。 193 | 194 | 当它应用于函数式编程时,我会给出这样的警告:在本文中会讨论许多有趣的模式,但只因为您发现一些令人兴奋的模式可以应用,在您的代码的应用这些模式可能未必合适。 195 | 196 | 这就是我不同于许多正式的函数编程人员的地方:仅仅是因为您*可以*将函数编程模式应用于某个东西,但并不意味着您就*应该*将函数编程模式应用于到你的代码上。此外,有很多方法可以分割一个问题,即使您可能已经学习了一种更为复杂的方法,这种方法对维护和可扩展性来说更具“未来证明”,但在这一点上,更简单的函数编程模式可能就足够了。 197 | 198 | 一般来说,我建议您在编写代码时寻求平衡,并在掌握了一些技巧后,在应用函数编程概念时保持保守。在决定某个特定的模式或抽象的时候,考虑是否有助于该部分代码更具可读性,或者它只是引入了尚不保证的巧妙的复杂性时,默认了“YAGNI”原则。 199 | 200 | > 提醒一下,任何从未使用过的扩展点不仅仅是浪费精力,还可能妨碍您的工作。 201 | > 202 | > Jeremy D. Miller @jeremydmiller 2/20/15 203 | > 204 | > https://twitter.com/jeremydmiller/status/568797862441586688 205 | 206 | 记住,您编写的每一行代码都会有相应的阅读成本。看这代码的人可能是团队的成员,甚至是你未来的自己。谁都不会对看起来老练的代码印象深刻,代码只是为了炫耀你的函数编程能力罢了。 207 | 208 | 最好的代码是未来可读性最高的代码,因为它在理想与实用之间达到了正确的平衡。 209 | 210 | ## 资源 211 | 212 | 我利用了大量不同的资源来撰写这篇文章。我相信你也会从中受益,下面花点时间简单介绍一下。 213 | 214 | ### 相关书籍 215 | 216 | 一些你不可错过的函数编程/JS书籍: 217 | 218 | * [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) 219 | * [JavaScript Allongé](https://leanpub.com/javascriptallongesix) by [Reg Braithwaite](https://twitter.com/raganwald) 220 | * [Functional JavaScript](http://shop.oreilly.com/product/0636920028857.do) by [Michael Fogus](https://twitter.com/fogus) 221 | 222 | ### 博客/网站 223 | 224 | 一些作者和内容: 225 | 226 | * [Fun Fun Function Videos](https://www.youtube.com/watch?v=BMUiFMZr7vk) by [Mattias P Johansson](https://twitter.com/mpjme) 227 | * [Awesome FP JS](https://github.com/stoeffel/awesome-fp-js) 228 | * [Kris Jenkins](http://blog.jenkster.com/2015/12/what-is-functional-programming.html) 229 | * [Eric Elliott](https://medium.com/@_ericelliott) 230 | * [James A Forbes](https://james-forbes.com/) 231 | * [James Longster](https://github.com/jlongster) 232 | * [André Staltz](http://staltz.com/) 233 | * [Functional Programming Jargon](https://github.com/hemanth/functional-programming-jargon#functional-programming-jargon) 234 | * [Functional Programming Exercises](https://github.com/InceptionCode/Functional-Programming-Exercises) 235 | 236 | ### 资源库 237 | 238 | 这本书中的代码片段在很大程度上不依赖于库。我们发现的每一个操作,都将派生出如何在独立的纯javascript中实现它。但是,当您开始使用函数式编程构建更多的实际代码时,您很快就会希望库能够提供这些常见的实用程序的优化和高度可靠的版本。 239 | 240 | 顺便说一下,您需要检查文档中所使用的库函数,以确保您知道它们是如何工作的。它们中的许多与我们在本文中构建的代码有很多相似之处,但毫无疑问,即使在流行的库之间,也会存在一些差异。 241 | 242 | 下面是几个流行的JavaScript FP库,是您开始探索的好地方: 243 | 244 | * [Ramda](http://ramdajs.com) 245 | * [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide) 246 | * [functional.js](http://functionaljs.com/) 247 | * [Immutable.js](https://github.com/facebook/immutable-js) 248 | 249 | [附录C](apC.md/#stuff-to-investigate) 对这些库和其他库进行了更深入的研究. 250 | 251 | ## 总结 252 | 253 | 你可能有各种各样的理由开始读这本书,以及对你能从中得到什么的不同期望。这一章解释了为什么我要你读这本书,以及我要你从中得到什么。它还可以帮助您向其他人(如您的开发伙伴)清楚地表达为什么他们应该与您一起读这本书! 254 | 255 | 函数式编程是关于编写基于经验证的原则的代码,这样我们就可以对编写和读取的代码有自信。我们不应该满足于编写揣测的工作代码,然后测试通过后松了一口气的状态。在运行它之前,我们应该*知道*它将做什么,并且我们应该确信我们已经在代码中为其他读者(包括我们未来的自己)传达了所有这些想法。 256 | 257 | 这是该书的核心。我们的目标是学会有效地与我们的代码通信,但不必为了达到目的而困惑在符号或术语的复杂中。 258 | 259 | 学习函数编程从深入了解函数的本质开始。这就是我们在下一章要讨论的问题。 260 | 261 | ---- 262 | -------------------------------------------------------------------------------- /manuscript/ch6.md: -------------------------------------------------------------------------------- 1 | # 章节 6: 值的不变性质 2 | 3 | 在[第5章](ch5.md)中,我们讨论了减少副作用的重要性:应用程序的状态可能意外改变并导致意外(bug)的方式。使用这种地雷的地方越少,我们对代码就越有信心,代码的可读性也就越好。我们这一章的主题也是做同样的努力去减少副作用。 4 | 5 | 如果编程风格的幂等性是关于定义一个值更改操作,以便它只能影响状态一次,那么现在我们将注意力转向将发生更改的次数从1减少到0的目标。 6 | 7 | 现在让我们来探讨值的不变性,即在程序中我们只使用不可更改的值。 8 | 9 | ## 原始类型的不变性 10 | 11 | js原始类型(`number`, `string`, `boolean`, `null`, and `undefined`)的值已经是不可变的;你无法改变他们: 12 | 13 | ```js 14 | // 无效,也没有任何意义 15 | 2 = 2.5; 16 | ``` 17 | 18 | 然而,js确实有一个特殊的行为,看起来它允许改变这样的原始类型值:“boxing”(boxing所谓的装箱,是指将基本数据类型转换为对应的引用类型的操作。而装箱又分为隐式装箱和显式装箱)。当您访问某些基本类型值(特别是`number`, `string`和`boolean`)的属性时,js会自动将该值包装在其对象对应项(分别为`number`, `string`和`boolean`)中。 19 | 20 | 考虑: 21 | 22 | ```js 23 | var x = 2; 24 | 25 | x.length = 4; 26 | 27 | x; // 2 28 | x.length; // undefined 29 | ``` 30 | 31 | 数字通常没有`length`属性可用。`x.length = 4`设置试图添加一个新属性,但它会自动失败(或根据您的观点被忽略/丢弃);`x` 继续保持简单的基本数字`2`。 32 | 33 | 但js允许`x.length = 4`语句运行看起来很麻烦,如果不是因为其他原因,读者可能会感到困惑。好消息是,如果使用strict模式(`"use strict";`),这样的语句将抛出错误。 34 | 35 | 如果您尝试改变这样一个值的显式装箱对象表示,会怎么样? 36 | 37 | ```js 38 | var x = new Number( 2 ); 39 | 40 | // 运行正常 41 | x.length = 4; 42 | ``` 43 | 44 | 这个代码段中的`x`保存了对对象的引用,因此可以添加和更改自定义属性而不存在任何问题。 45 | 46 | 像`number`这样的简单原语的不可变性可能看起来相当明显。但是`string`值呢?JS开发人员有一个非常普遍的误解,认为字符串就像数组,因此可以更改。JS语法甚至暗示他们是“数组一样”与`[ ]访问操作符。然而,字符串也是不可变的: 47 | 48 | ```js 49 | var s = "hello"; 50 | 51 | s[1]; // "e" 52 | 53 | s[1] = "E"; 54 | s.length = 10; 55 | 56 | s; // "hello" 57 | ``` 58 | 59 | 尽管能够像访问数组一样访问`s[1]`,但JS字符串并不是真正的数组。设置 `s[1] = "E"`和`s.length = 10`和之前做过`x.length = 4`一样,都自动失败了。在严格模式下,这些赋值将失败,因为`1`属性和`length`属性在这个基本`string`值上都是只读的。 60 | 61 | 有趣的是,即使是被框起来的`String`对象值也会(基本上)不可变,因为如果您更改现有属性,它会在严格模式下抛出错误: 62 | 63 | ```js 64 | "use strict"; 65 | 66 | var s = new String( "hello" ); 67 | 68 | s[1] = "E"; // error 69 | s.length = 10; // error 70 | 71 | s[42] = "?"; // OK 72 | 73 | s; // "hello" 74 | ``` 75 | 76 | ## 此值到彼值 77 | 78 | 我们将在本章中进一步解释这个概念,但是首先要有一个清晰的理解:值的不变性并不意味着我们不能在程序的过程中改变值。一个没有改变状态的程序不是一个很有趣的程序!这也不意味着我们的变量不能有不同的值。这些都是关于价值不变性的普遍误解。 79 | 80 | 值不变性意味着*当*我们需要更改程序中的状态时,我们必须创建并跟踪一个新值,而不是更改一个现有值。 81 | 82 | 例如: 83 | 84 | ```js 85 | function addValue(arr) { 86 | var newArr = [ ...arr, 4 ]; 87 | return newArr; 88 | } 89 | 90 | addValue( [1,2,3] ); // [1,2,3,4] 91 | ``` 92 | 93 | 注意,我们没有更改`arr`引用的数组,而是创建了一个新数组(`newArr`),其中包含现有值加上新的`4` 值。 94 | 95 | 根据我们在[第5章](ch5.md)中讨论的关于副作用的原因/影响来分析`addValue(..)`。它是纯洁的吗?它是否具有参考透明性?给定相同的数组,它总是会产生相同的输出吗?它有没有副作用和副作用?答案为:是的 96 | 97 | 假设`[1,2,3]`数组表示来自以前一些操作的数据序列,并且我们存储在某个变量中。这是我们目前的状态。如果我们想计算应用程序的下一个状态,我们调用`addValue(..)`。但是我们希望下一个状态的计算是直接和明确的。因此,`addValue(..)`操作接受直接输入,返回直接输出,并避免了修改`arr`引用的原始数组的副作用。 98 | 99 | 这意味着我们可以计算`[1,2,3,4]`的新状态,并完全控制状态的转换。程序的任何其他部分都不能意外地将我们提前转换到那个状态,或者完全转换到另一个状态,比如`[1,2,3,5]`。通过对我们的值进行约束并将它们视为不可变的,我们大大减少了令人惊讶的表面区域,使我们的程序更易于阅读、推理和最终信任。 100 | 101 | `arr`引用的数组实际上是可变的。我们只是选择不去改变它,所以我们实践了价值不变的精神。 102 | 103 | 我们也可以对对象使用复制而不是变异的策略。思考下: 104 | 105 | ```js 106 | function updateLastLogin(user) { 107 | var newUserRecord = Object.assign( {}, user ); 108 | newUserRecord.lastLogin = Date.now(); 109 | return newUserRecord; 110 | } 111 | 112 | var user = { 113 | // .. 114 | }; 115 | 116 | user = updateLastLogin( user ); 117 | ``` 118 | 119 | ### 非局部 120 | 121 | 非原始值由引用持有,当作为参数传递时,复制的是引用,而不是值本身。 122 | 123 | 如果在程序的一个部分中有一个对象或数组,并将其传递给在程序另一个部分中的函数,那么该函数现在可以通过这个引用副本影响值,并以可能意想不到的方式对其进行修改。 124 | 125 | 换句话说,如果作为参数传递,非原始值将变为非本地值。可能需要考虑整个程序来理解是否会更改这样的值。 126 | 127 | 思考: 128 | 129 | ```js 130 | var arr = [1,2,3]; 131 | 132 | foo( arr ); 133 | 134 | console.log( arr[0] ); 135 | ``` 136 | 137 | 从表面上看,您期望`arr[0]`仍然是值`1`。但真的是这样吗?您不知道,因为`foo(..)` *可能*使用传递给它的引用副本更改了数组。 138 | 139 | 我们在前一章已经看到了一个避免这种意外的技巧: 140 | 141 | ```js 142 | var arr = [1,2,3]; 143 | 144 | foo( [...arr] ); // 拷贝一个数组 145 | 146 | console.log( arr[0] ); // 1 147 | ``` 148 | 149 | 稍后,我们将看到另一种策略,用于保护不受来自下面的值意外突变的影响。 150 | 151 | ## 重赋值 152 | 153 | 你如何描述“常量”?在你进入下一段之前想一下这个问题。 154 | 155 |

156 | * * * * 157 |

158 | 159 | 你们中的一些人可能会这么描述,“一个不能改变的值”,“一个不能改变的变量”,或者类似的东西。意思相近,但不完全正确。我们应该对常量使用的精确定义是:不能重新分配的变量。 160 | 161 | 这种吹毛求疵是非常重要的,因为它阐明了一个常量实际上与这个值无关,只是说,无论一个常量持有什么值,这个变量都不能被重新分配任何其他值。但它没有说明价值本身的性质。 162 | 163 | 思考: 164 | 165 | ```js 166 | var x = 2; 167 | ``` 168 | 169 | 如前所述,值`2`是一个不可更改(不可变)的原始值。如果我把代码改成: 170 | 171 | ```js 172 | const x = 2; 173 | ``` 174 | 175 | `const`关键字的出现,通常被称为“常量声明”,实际上根本没有改变`2`的性质;它只是变得不可改变的,而且将永远不变。 176 | 177 | 这是真的,这后面的一行将报错失败: 178 | 179 | ```js 180 | // 试着改变`x`看看! 181 | x = 3; // Error! 182 | ``` 183 | 184 | 但是,我们没有改变任何关于值的东西。我们试图重新分配变量`x`。所涉及的值几乎是偶然的。 185 | 186 | 要证明`const`与值的本质无关,思考下: 187 | 188 | ```js 189 | const x = [ 2 ]; 190 | ``` 191 | 192 | 数组是常量吗?*不* `x`是一个常量,因为它不能被重新分配。但是下面这句话完全可以: 193 | 194 | ```js 195 | x[0] = 3; 196 | ``` 197 | 198 | 为什么?因为数组仍然是完全可变的,即使`x`是一个常量。 199 | 200 | 围绕`const`和“常量”的混淆只处理赋值而不处理值语义,可以长篇大论了。似乎每一种语言中都有相当多的开发人员遇到了相同类型的混淆。实际上,Java反对使用const,并引入了一个新的关键字final,至少在一定程度上是为了将自己从“常量”语义的混乱中分离出来。 201 | 202 | 抛开混淆的影响,如果`const`与创建不可变值没有任何关系,那么它对于FPer有什么重要性呢? 203 | 204 | ### 意图 205 | 206 | `const`的使用告诉代码的读者,*那个*变量不会被重新分配。作为意图的一个信号,`const`通常被高度赞扬为JavaScript的一个受欢迎的附加功能,并在代码可读性方面得到了普遍的改进。 207 | 208 | 在我看来,这主要是炒作;这些说法没有多少实质内容。我只看到用这种方式表示你的意图所带来的最轻微的好处。而当你把这个数字与几十年来围绕它的困惑(暗示着值的不变性)进行对比时,我不认为`const`有什么分量。 209 | 210 | 为了支持我的断言,让我们考虑作用域。`const`创建了一个块作用域的变量,这意味着变量只存在于一个本地化的块中: 211 | 212 | ```js 213 | // 一些代码 214 | 215 | { 216 | const x = 2; 217 | 218 | // 几行代码 219 | } 220 | 221 | // 一些代码 222 | ``` 223 | 224 | 通常,块被认为是最好的设计只有几行。如果您的代码块超过10行,大多数开发人员会建议您重构。所以`const x = 2`最多只适用于后面的9行代码。 225 | 226 | 程序的任何其他部分都不能影响`x`的赋值。 227 | 228 | 我的主张是,程序的可读性基本上与这个相同: 229 | 230 | ```js 231 | // 一些代码 232 | 233 | { 234 | let x = 2; 235 | 236 | // 几行代码 237 | } 238 | 239 | // 一些代码 240 | ``` 241 | 242 | 如果您查看`let x = 2;`后面的几行代码,您将能够很容易地看出`x`实际上“没有”重新分配。对我来说,这是一个**更强的信号**——实际上不是重新分配它!——而不是使用一些容易混淆的 `const`声明来表示“不会重新分配它”。 243 | 244 | 此外,让我们考虑一下这段代码可能第一眼就传达给读者的信息: 245 | 246 | ```js 247 | const magicNums = [1,2,3,4]; 248 | ``` 249 | 250 | 难道您的代码的读者(错误地)认为您的意图是永远不修改数组,这至少是可能的(可能的)吗?对我来说,这似乎是一个合理的推论。想象一下他们的疑惑,如果稍后您实际上允许`magicNums`引用的数组值发生突变。这会让他们感到惊讶吗? 251 | 252 | 更糟的是,如果您故意以某种方式修改`magicNums`,结果却不为读者所知,该怎么办?随后,在代码中,他们看到了`magicNums`的用法,并假设(同样是错误的)它仍然是`[1,2,3,4]`,因为他们将您的意图理解为“不会更改这个”。 253 | 254 | 我认为您应该使用`var`或`let`来声明变量,以保存要进行更改的值。我认为这实际上比使用`const`更清楚地表达了你的意图。 255 | 256 | 但`const`的麻烦还不止于此。还记得我们在这一章的开头说过,要将值视为不可变的,就意味着当我们的状态需要更改时,我们必须创建一个新值,而不是对它进行修改吗?一旦你创建了这个新数组,你打算怎么处理它?如果使用`const`声明对它的引用,则不能重新分配它。 257 | 258 | 259 | ```js 260 | const magicNums = [1,2,3,4]; 261 | 262 | // 然后: 263 | magicNums = magicNums.concat( 42 ); // 噢, 不能重新分配 264 | ``` 265 | 266 | 那么,下一步怎么做? 267 | 268 | 从这个角度来看,我认为`const`实际上使我们更加努力地坚持FP,而不是更加容易。我的结论是:`const`并没有那么有用。它制造了不必要的混乱,并以不方便的方式限制我们。我只对一些简单的常量使用`const`,比如: 269 | 270 | ```js 271 | const PI = 3.141592; 272 | ``` 273 | 274 | 值`3.141592`已经是不可变的,我明确地表示,这个`PI`将始终用作这个文字值的替代占位符。对我来说,这就是`const`的好处。坦白地说,在我的典型代码中,我没有使用很多这样的声明。 275 | 276 | 我写过很多JavaScript代码,也见过很多JavaScript代码,我只是认为这是一个想象中的问题,我们的很多bug都来自于意外的重新分配。 277 | 278 | FPers如此青睐`const`而避免重新分配的原因之一是由于等式推理。尽管这个主题与其他语言的关系比与JS的关系更密切,而且超出了我们将在这里讨论的范围,但它是一个有效的观点。然而,我更喜欢务实的观点,而不是更学术性的观点。 279 | 280 | 例如,我发现对变量重新分配的度量使用对于简化计算中间状态的描述非常有用。当一个值经过多次类型强制转换或其他转换时,我通常不希望为每种表示形式都提供新的变量名: 281 | 282 | ```js 283 | var a = "420"; 284 | 285 | // 然后 286 | 287 | a = Number( a ); 288 | 289 | // 然后 290 | 291 | a = [ a ]; 292 | ``` 293 | 294 | 如果从字符串`"420"`更改为数字`420`后,不再需要原来的`"420"`值了,那么我认为重新分配的`a`比使用`aNum`这样的新变量名更易于阅读。 295 | 296 | 我们真正应该担心的不是变量是否被重新赋值,而是值是否发生了变化。为什么?因为值是可移植的;词汇赋值则不然。您可以将数组传递给函数,并且可以在您没有意识到的情况下更改它。但是,重新分配绝不会意外地由程序的其他部分引起。 297 | 298 | ### 冻结值 299 | 300 | 有一种既便宜又简单的方法可以将一个可变的对象/数组/函数转换成一个“不可变值”(而且有很多方法): 301 | 302 | ```js 303 | var x = Object.freeze( [2] ); 304 | ``` 305 | 306 | `Object.freeze(..)`实用程序遍历对象/数组的所有属性/索引,并将它们标记为只读,因此不能重新分配它们。实际上,这有点像用`const`声明属性!`Object.freeze(..)`还将属性标记为不可重构,并将对象/数组本身标记为不可扩展(不能添加新属性)。实际上,它使对象的顶层不可变。 307 | 308 | 不过,只有顶层不能重新分配。小心这种方式! 309 | 310 | ```js 311 | var x = Object.freeze( [ 2, 3, [4, 5] ] ); 312 | 313 | // 不允许: 314 | x[0] = 42; 315 | 316 | // 允许: 317 | x[2][0] = 42; 318 | ``` 319 | 320 | `Object.freeze(..)`提供了浅的、单一的不变性。如果您想要一个非常不可变的值,就必须手动遍历整个对象/数组结构,并对每个子对象/数组应用`Object.freeze(..)`。 321 | 322 | 但与`const`不同的是,`Object.freeze(..)实际上给了您一个不可变的值,而`const`可能会让您误以为自己没有得到值时就得到了一个不可变的值。 323 | 324 | 回想一下前面的例子: 325 | 326 | ```js 327 | var arr = Object.freeze( [1,2,3] ); 328 | 329 | foo( arr ); 330 | 331 | console.log( arr[0] ); // 1 332 | ``` 333 | 334 | 现在`arr[0]`是相当可靠的`1`。 335 | 336 | 这一点非常重要,因为当我们知道可以相信一个值在传递到某个我们看不到或无法控制的地方时不会发生变化时,它可以使我们更容易地对代码进行推理。 337 | 338 | ## 性能 339 | 340 | 每当我们开始创建新值(数组、对象等)而不是修改现有值时,下一个明显的问题是:这对性能意味着什么? 341 | 342 | 如果每次需要添加新数组时都要重新分配新数组,这不仅会浪费CPU时间,还会消耗额外的内存;旧值(如果不再引用)也将被垃圾收集。这将消耗更多的CPU。 343 | 344 | 这是一个可以接受的折中方案吗?这取决于在没有上下文的情况下,不应讨论或优化代码性能。 345 | 346 | 如果在程序的整个生命周期中只发生一次(甚至几次)状态更改,那么丢弃旧的数组/对象来替换新数组/对象几乎肯定不是问题。我们所讨论的搅动是如此之小——最多可能只有几微秒——以至于对应用程序的性能没有实际影响。与不必跟踪和修复与意外值突变相关的错误所节省的几分钟或几个小时相比,甚至是忽略不计的。 347 | 348 | 然后,如果这样的操作经常发生,或者特别发生在应用程序的“关键路径”中,那么性能——同时考虑性能和内存!担忧是完全合理的。 349 | 350 | 考虑一个类似数组的专门化数据结构,但是您希望能够对其进行更改,并使每个更改的行为隐式,就像结果是一个新数组一样。如何在不每次都创建一个新数组的情况下实现这一点呢?这样一个特殊的数组数据结构可以存储原始值,然后跟踪作为前一个版本的增量所做的每个更改。 351 | 352 | 在内部,它可能类似于对象引用的链表树,其中树中的每个节点表示原始值的一个突变。实际上,这在概念上类似于**Git**版本控制的工作方式。 353 | 354 |

355 | 356 |

357 | 358 | 在这个概念图中,一个原始数组 `[3,6,1,0]` 首先具有分配给位置 `0` 的值`4`的突变(变成`[4,6,1,0]`),然后`1`被分配给位置`3`(现在成了`[4,6,1,1]`),最后`2`被分配给位置`4`(结果:`[4,6,1,1,2]`)。关键的思想是,在每次突变时,只记录与前一个版本的更改,而不是复制整个原始数据结构。通常,这种方法在内存和CPU性能方面都更有效。 359 | 360 | 假设使用这个假设的专用数组数据结构,如下所示: 361 | 362 | ```js 363 | var state = specialArray( 4, 6, 1, 1 ); 364 | 365 | var newState = state.set( 4, 2 ); 366 | 367 | state === newState; // false 368 | 369 | state.get( 2 ); // 1 370 | state.get( 4 ); // undefined 371 | 372 | newState.get( 2 ); // 1 373 | newState.get( 4 ); // 2 374 | 375 | newState.slice( 2, 5 ); // [1,1,2] 376 | ``` 377 | 378 | `specialArray(..)`数据结构将在内部跟踪每个突变操作(如`set(..)`),将其作为一个*差异*,因此它不必为原始值(`4`, `6`, `1`和`1`)重新分配内存,只需将`2`值添加到列表的末尾。但重要的是,`state`和`newState`指向数组值的不同版本(或视图),因此保留了值的不变性语义 379 | 380 | 创建自己的性能优化的数据结构是一个有趣的挑战。但是从实用的角度来看,您可能应该使用已经做得很好的库。一个很好的选项是[Immutable.js](http://facebook.github.io/immutable-js),它提供了各种数据结构,包括`List`(类数组)和`Map`(类对象)。 381 | 382 | 考虑前面的`specialArray`示例,使用`Immutable.List`操作: 383 | 384 | ```js 385 | var state = Immutable.List.of( 4, 6, 1, 1 ); 386 | 387 | var newState = state.set( 4, 2 ); 388 | 389 | state === newState; // false 390 | 391 | state.get( 2 ); // 1 392 | state.get( 4 ); // undefined 393 | 394 | newState.get( 2 ); // 1 395 | newState.get( 4 ); // 2 396 | 397 | newState.toArray().slice( 2, 5 ); // [1,1,2] 398 | ``` 399 | 400 | 像Immutable.js这样强大的库使用复杂的性能优化。在没有这样一个库的情况下手工处理所有的细节和基本情况将是非常困难的。 401 | 402 | 当对值的更改很少或不频繁,性能也不那么重要时,我建议使用较轻量级的解决方案,坚持使用前面讨论过的内置`Object.freeze(..)`。 403 | 404 | ## 处理 405 | 406 | 如果我们收到一个函数的值,但我们不确定它是可变的还是不可变的怎么办?你能不能继续尝试变异它?不。正如我们在本章开始时所断言的,我们应该将所有接收到的值都视为不可变的——以避免副作用并保持纯值——无论它们是或不是。 407 | 408 | 回想一下前面的例子: 409 | 410 | ```js 411 | function updateLastLogin(user) { 412 | var newUserRecord = Object.assign( {}, user ); 413 | newUserRecord.lastLogin = Date.now(); 414 | return newUserRecord; 415 | } 416 | ``` 417 | 418 | 此实现将`user`视为不应发生突变的值;它是否是不可变的与读取这部分代码无关。与此实现相比: 419 | 420 | ```js 421 | function updateLastLogin(user) { 422 | user.lastLogin = Date.now(); 423 | return user; 424 | } 425 | ``` 426 | 427 | 这个版本编写起来容易得多,甚至性能更好。但是,这种方法不仅使`updateLastLogin(..)`变得不纯,而且它还以某种方式修改了一个值,使得读取这段代码以及使用它的位置变得更加复杂。 428 | 429 | **我们应该始终将`user`视为不可变的**,因为在阅读代码时,我们不知道该值来自何处,或者如果对其进行修改,可能会导致什么潜在问题。 430 | 431 | 这种方法的好例子可以在JS数组的各种内置方法中看到,如`concat(..)`和 `slice(..)`: 432 | 433 | ```js 434 | var arr = [1,2,3,4,5]; 435 | 436 | var arr2 = arr.concat( 6 ); 437 | 438 | arr; // [1,2,3,4,5] 439 | arr2; // [1,2,3,4,5,6] 440 | 441 | var arr3 = arr2.slice( 1 ); 442 | 443 | arr2; // [1,2,3,4,5,6] 444 | arr3; // [2,3,4,5,6] 445 | ``` 446 | 447 | 其他数组原型方法将值实例视为不可变的,并返回一个新数组,而不是进行修改:`map(..)`和`filter(..)`。 `filter(..)`. The `reduce(..)`/`reduceRight(..)`实用程序也避免对实例进行修改,不过它们在默认情况下不会返回一个新数组。 448 | 449 | 不幸的是,由于历史原因,相当多的其他数组方法是其实例的非纯变异:`splice(..)`, `pop(..)`, `push(..)`, `shift(..)`, `unshift(..)`, `reverse(..)`, `sort(..)`, 和 `fill(..)`,这些都更改了原有数据值。 450 | 451 | 452 | 453 | 回顾其中一个[章节4 `compose(..)` ](ch4.md/#user-content-generalcompose)的实现 : 454 | 455 | ```js 456 | function compose(...fns) { 457 | return function composed(result){ 458 | // 复制函数数组 459 | var list = [...fns]; 460 | 461 | while (list.length > 0) { 462 | // 把列表末尾的最后一个函数去掉 463 | // 并执行它 464 | result = list.pop()( result ); 465 | } 466 | 467 | return result; 468 | }; 469 | } 470 | ``` 471 | 472 | `...fns`的gather参数从传入的参数中创建一个新的本地数组,所以我们不能在这个数组上创建外部副作用。那么我们就有理由假设在局部进行变异是安全的。但这里的微妙问题是,在`fns`上关闭的内部`composed(..)`在这个意义上不是“局部”的。 473 | 474 | 考虑一下这个不复制的不同版本: 475 | 476 | ```js 477 | function compose(...fns) { 478 | return function composed(result){ 479 | while (fns.length > 0) { 480 | // 把列表末尾的最后一个函数去掉 481 | // 并执行它 482 | result = fns.pop()( result ); 483 | } 484 | 485 | return result; 486 | }; 487 | } 488 | 489 | var f = compose( x => x / 3, x => x + 1, x => x * 2 ); 490 | 491 | f( 4 ); // 3 492 | 493 | f( 4 ); // 4 <-- 变成这样了! 494 | ``` 495 | 496 | 这里第二次使用`f(..)`是不正确的,因为我们在第一次调用时修改了`fns`,这影响了后续的使用。根据具体情况,复制一个数组,如`list = [...fns]`可能有必要,也可能没有必要。但我认为,假设您需要它是最安全的——即使只是为了可读性!除非你能证明你没有,而不是相反。 497 | 498 | 严格遵守规则,始终将*接收到的值*视为不可变的,不管它们是或不是。这将提高代码的可读性和可靠性。 499 | 500 | ## 总结 501 | 502 | 值的不变性不是关于不变的值。它是在程序状态更改时创建和跟踪新值,而不是修改现有值。这种方法使我们在阅读代码时更有信心,因为我们限制了状态可以以我们不容易看到或期望的方式更改的地方。 503 | 504 | `const`声明(常量)通常会被误认为是它们发出意图信号和执行不变性的能力。实际上,`const`基本上与价值的不变性无关,它的使用可能会造成比它解决的更多的混乱。相反,`Object.freeze(..)`提供了一种很好的内置方法,可以在数组或对象上设置浅值不变性。在许多情况下,这就足够了。 505 | 506 | 对于程序的性能敏感部分,或者在经常发生更改的情况下,创建一个新的数组或对象(特别是当它包含大量数据时)对于处理和内存问题都是不可取的。在这些情况下,使用像Immutable.js库中的不可变数据结构。可能是最好的主意。 507 | 508 | 值不变性对代码可读性的重要性不在于不能更改值,而在于将值视为不可变的原则。 509 | -------------------------------------------------------------------------------- /manuscript/foreword.md: -------------------------------------------------------------------------------- 1 | # JavaScript轻量级函数式编程 2 | # 序言 3 | 4 | 众所周知,我是一个函数式编程迷。我尽可能地传播函数思想和语言,努力阅读最新的学术论文,在业余时间学习抽象代数……即使在JavaScript中,我也拒绝编写不纯的语句,这就是编写弗里斯比教授的《函数式编程指南》的原因。是的,十足的教条狂热者。 5 | 6 | 我并不总是这样…我曾经痴迷于物品。我喜欢做“真实世界”的模特。我是人造机器人的发明者,在夜间以高超的精确度修修补补。有知觉的木偶的创造者,手指在键盘上跳舞,赋予它们生命——一个真正的1337 h4x0r格培多。然而,在写了5年面向对象的代码之后,我从来没有对结果感到非常满意。这对我来说从来都不太好。我觉得自己是个差劲的程序员。我甚至不再相信一个简单、灵活的代码库是可能的。 7 | 8 | 我想我会尝试一些不同的方法:函数式编程。我开始在日常的代码库中涉猎功能性的想法,令同事们非常沮丧的是,我一点也不知道自己在做什么。那时候我写的代码很糟糕。。原因是我对自己想要完成的事情缺乏清晰的愿景或目标。我是花了很长时间和很多垃圾程序才弄清楚如何进行函数式编程。 9 | 10 | 现在,在所有这些混乱的探索之后,我觉得纯函数编程已经实现了它的承诺。可读程序确实存在!重用确实存在!我不再创造,而是发现我的模型。我变成了一个流氓侦探,揭露了一个巨大的阴谋,软木塞钉满了数学证据。一个数字时代的库斯托以科学的名义记录下这片奇异土地的特征!它并不完美,我还有很多要学,但我从来没有像现在这样对我的工作和结果感到满意。 11 | 12 | 如果在我刚开始写这本书的时候就有这本书的话,我过渡到函数式编程的世界就会容易得多,破坏性也会小得多。这本书是双重的:它不仅会教你如何在日常代码中有效地使用函数式编程中的各种构造,更重要的是,它为你提供了一个目标;指导原则将保持你在正确的轨道上。 13 | 14 | 您将学习 Kyle首创的一个轻量函数范例,它支持声明式、函数式编程,同时提供与JavaScript世界其他部分的平衡和互操作。您将理解纯函数编程所建立的基础,而不必完全遵循范式。您将获得实践和探索函数编程的技能,而不需要重写现有的代码来很好地协同工作。您可以在您的软件生涯中向前迈进一步,而不需要像我多年前那样倒退和漫无目的地徘徊。为此欢呼吧! 15 | 16 | Kyle是一位伟大的老师,他以对整体的不懈追求而闻名,没有一个角落或缝隙是未经探索的,但他对学习者的困境保持着同理心。他的风格与整个行业产生了共鸣,使我们所有人都得到了提升。他的作品在JavaScript的历史和大多数人的书签栏中占有稳固的位置。对你来说是很好的帮助。 17 | 18 | 函数式编程有许多不同的定义。lisp程序员的定义与haskell的定义大不相同。OCaml(关于OCaml,最早称为Objective Caml,是Caml编程语言的主要实现,开发工具包含交互式顶层解释器,字节码编译器以及最优本地代码编译器)的函数编程定义与erlang(Erlang是一种通用的面向并发的编程语言)中的范例几乎没有相似之处。甚至可以在javascript中找到几个相互竞争的定义。然而,有一种联系——一些模糊的“当我看到它时就知道”的定义,很像说瞎话(事实上,有些人确实发现函数式编程在敷衍!)这本书确实抓住了这一点。在某些圈子里,最终的结果可能不被认为是惯用的,但这里获得的知识直接适用于任何形式的函数式编程。 19 | 20 | 这本书是一个极好的地方开始你的函数式编程之旅。拿走,Kyle… 21 | -------------------------------------------------------------------------------- /manuscript/images/fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig1.png -------------------------------------------------------------------------------- /manuscript/images/fig10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig10.png -------------------------------------------------------------------------------- /manuscript/images/fig10.svg: -------------------------------------------------------------------------------- 1 | 2 |
a
a
b
b
c
c
d
d
e
e
predicate function
predicate function
list 1
list 1
list 2
list 2
b
b
d
d
e
e
-------------------------------------------------------------------------------- /manuscript/images/fig11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig11.png -------------------------------------------------------------------------------- /manuscript/images/fig11.svg: -------------------------------------------------------------------------------- 1 | 2 |
a
a
b
b
c
c
d
d
e
e
reducer function
reducer function
list 1
list 1
initial
value
[Not supported by viewer]
acc0
[Not supported by viewer]
acc1
[Not supported by viewer]
acc2
[Not supported by viewer]
result
result
acc3
[Not supported by viewer]
-------------------------------------------------------------------------------- /manuscript/images/fig12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig12.png -------------------------------------------------------------------------------- /manuscript/images/fig12.svg: -------------------------------------------------------------------------------- 1 | 2 |
a
a
b
b
c
c
d
d
e
e
reducer function
reducer function
list 1
list 1
acc0
[Not supported by viewer]
acc1
[Not supported by viewer]
result
result
acc2
[Not supported by viewer]
initial value
initial value
-------------------------------------------------------------------------------- /manuscript/images/fig13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig13.png -------------------------------------------------------------------------------- /manuscript/images/fig14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig14.png -------------------------------------------------------------------------------- /manuscript/images/fig15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig15.png -------------------------------------------------------------------------------- /manuscript/images/fig15.svg: -------------------------------------------------------------------------------- 1 | 2 |
baz()

var x;
[Not supported by viewer]
baz()

var x;
[Not supported by viewer]
bar()

var y;
[Not supported by viewer]
baz()

var x;
[Not supported by viewer]
bar()

var y;
[Not supported by viewer]
foo()

var z;
[Not supported by viewer]
Step 1
[Not supported by viewer]
Step 2
[Not supported by viewer]
Step 3
[Not supported by viewer]
-------------------------------------------------------------------------------- /manuscript/images/fig16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig16.png -------------------------------------------------------------------------------- /manuscript/images/fig16.svg: -------------------------------------------------------------------------------- 1 | 2 |
baz()

var x;
[Not supported by viewer]
baz()

var x;
[Not supported by viewer]
bar()

var y;
[Not supported by viewer]
baz()

var x;
[Not supported by viewer]
bar()

var y;
[Not supported by viewer]
foo()

var z;
[Not supported by viewer]
Step 1
[Not supported by viewer]
Step 2
[Not supported by viewer]
Step 3
[Not supported by viewer]
-------------------------------------------------------------------------------- /manuscript/images/fig17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig17.png -------------------------------------------------------------------------------- /manuscript/images/fig17.svg: -------------------------------------------------------------------------------- 1 | 2 |
Imperative
Imperative
Declarative / FP
Declarative / FP
Readability
Readability
Where many developers give up on learning FP
[Not supported by viewer]
-------------------------------------------------------------------------------- /manuscript/images/fig18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig18.png -------------------------------------------------------------------------------- /manuscript/images/fig18.svg: -------------------------------------------------------------------------------- 1 | 2 |
3
3
6
6
1
1
0
0
0: 4
0: 4
3: 1
3: 1
4: 2
4: 2
[4,6,1,1,2]
[4,6,1,1,2]
-------------------------------------------------------------------------------- /manuscript/images/fig19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig19.png -------------------------------------------------------------------------------- /manuscript/images/fig19.svg: -------------------------------------------------------------------------------- 1 | 2 |
foo( 16 )
[Not supported by viewer]
foo( 8 )
[Not supported by viewer]
foo( 4 )
[Not supported by viewer]
4
[Not supported by viewer]
4
[Not supported by viewer]
4
[Not supported by viewer]
-------------------------------------------------------------------------------- /manuscript/images/fig2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig2.png -------------------------------------------------------------------------------- /manuscript/images/fig3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig3.png -------------------------------------------------------------------------------- /manuscript/images/fig3.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig4.png -------------------------------------------------------------------------------- /manuscript/images/fig4.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig5.png -------------------------------------------------------------------------------- /manuscript/images/fig5.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig6.png -------------------------------------------------------------------------------- /manuscript/images/fig6.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig7.png -------------------------------------------------------------------------------- /manuscript/images/fig7.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig8.png -------------------------------------------------------------------------------- /manuscript/images/fig8.svg: -------------------------------------------------------------------------------- 1 | 2 |
banana
banana
apple
apple
cherry
cherry
apricot
apricot
avocado
avocado
cantaloupe
cantaloupe
cucumber
cucumber
grape
grape
-------------------------------------------------------------------------------- /manuscript/images/fig9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/fig9.png -------------------------------------------------------------------------------- /manuscript/images/fig9.svg: -------------------------------------------------------------------------------- 1 | 2 |
a
a
b
b
c
c
d
d
e
e
V
V
W
W
X
X
Y
Y
Z
Z
mapper function
mapper function
list 1
list 1
list 2
list 2
-------------------------------------------------------------------------------- /manuscript/images/marketing/back-cover-hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/back-cover-hd.png -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/front-cover-hd.png -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-hd.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/front-cover-hd.tiff -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/front-cover-sd.png -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/front-cover-small.png -------------------------------------------------------------------------------- /manuscript/images/marketing/print-book-cover.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/print-book-cover.pdf -------------------------------------------------------------------------------- /manuscript/images/marketing/print-book-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/print-book-cover.png -------------------------------------------------------------------------------- /manuscript/images/marketing/social-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/social-avatar.png -------------------------------------------------------------------------------- /manuscript/images/marketing/social-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/marketing/social-header.png -------------------------------------------------------------------------------- /manuscript/images/paycode1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/paycode1.jpg -------------------------------------------------------------------------------- /manuscript/images/paycode2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simingchen/Functional-Light-JS/9f27f17f7d2ac9407a720e57823911a522258b5a/manuscript/images/paycode2.jpg -------------------------------------------------------------------------------- /manuscript/preface.md: -------------------------------------------------------------------------------- 1 | # JavaScript轻量级函数式编程 2 | # 序言 3 | 4 | > *单子只是内函子范畴中的一个单子。* 5 | 6 | 我刚刚失去你了吗?别担心,我也会迷路的!所有那些只对函数式编程中已经开始的术语有意义,对我们其他人来说都是胡说八道。 7 | 8 | 这本书不会教你这些词的意思。如果这就是你要找的,那就继续找。事实上,已经有很多伟大的书籍从上到下教授FP“正确的方法”。这些词有重要的意义,如果你深入学习FP,你一定会想要熟悉它们。 9 | 10 | 但这本书将以完全不同的方式探讨这个问题。我将用比大多数FP方法更少的特殊或非直观的术语,从头开始介绍基本的FP概念。我们将试着对每一个原则都采取实际的方法,而不是单纯从学术角度。毫无疑问,会有条款的。但我们会小心谨慎地介绍它们并解释它们为什么重要。 11 | 12 | 遗憾的是,我不是FP Cool Kids Club的会员。我从来没有正式学过FP。虽然我有计算机科学的学术背景,我的数学也不错,但数学符号并不是我大脑理解编程的方式。我从来没有写过一行Scheme、Clojure或Haskell。我不是一个老派的口齿不清的人。 13 | 14 | 我*参加过*无数次关于FP的会议,每一次都带着绝望的执着希望,最终,*这次*将是我理解整个函数式编程神秘主义的时候。每一次,当我沮丧地离开的时候,我都提醒自己那些术语都混在了我的脑海里,我不知道自己是否学到了什么,也不知道自己学到了什么。也许我学到了一些东西。但在很长一段时间里,我都不知道这些东西是什么。 15 | 16 | 渐渐地,通过这些不同的曝光,我梳理出了一些重要的概念,这些概念对于正式的函数式编程人员来说似乎太自然了。我学得很慢,而且是用实际和经验学的,而不是用专业术语学的。你是否知道一件事很久了,后来才发现它有一个你从来不知道的名字! 17 | 18 | 也许你和我一样;多年来,我在诸如“大数据”之类的行业领域里听到过"map-reduce"之类的术语,但我并不知道它们是什么。最终,我了解了`map(..)`函数的作用——这一切都发生在我意识到列表操作是函数编程人员路径的基石以及是什么让它们如此重要之前。早在我知道`map(..)`之前,我就知道`map(..)`是什么。 19 | 20 | 最终,我开始将所有这些零碎的理解汇集到我现在称为“轻量级函数式编程”的东西中。 21 | 22 | ## 任务 23 | 24 | 但是为什么学习函数式编程如此重要,即使是简单的形式? 25 | 26 | 近年来,我开始深深地相信一些东西,以至于你几乎可以把它称为一种宗教信仰。我相信编程本质上是关于人类的,而不是代码。我相信代码首先是人类交流的一种方式,它只是作为另一种方式来指导计算机。 27 | 28 | 在我看来,函数式编程的核心是在代码中使用众所周知的、可理解的,也已被证明可以避免使代码更难理解的错误的模式。从这个角度看,函数编程——或者是JavaScript轻量级函数式编程,可能是任何开发人员都能获得的最重要的工具集合之一。 29 | 30 | 我希望这本书“也许”打破了术语诅咒,直到最后的附录我们都不会谈论“monads”这样难懂的术语,。 31 | 32 | 正式的函数编程开发人员通常会断言函数编程的“真正价值”在于100%地使用它:它是一个全有或全无的命题。他们认为,如果你在项目的某一部分使用FP,而在另一部分不使用,那么整个项目就会被非FP的东西污染,因此遭受的痛苦就足够了,以至于FP可能不值得。 33 | 34 | 我将毫不含糊地说:**我认为绝对主义是虚假的**。对我来说,这就像说这本书只有在我自始至终使用完美语法和主动语态的情况下才算好一样愚蠢;如果我犯了任何错误,就会降低整本书的质量。无稽之谈。 35 | 36 | 我写得越清楚、连贯,你的阅读体验就会越好。但我不是一个100%完美的作家。有些部分会写得更好。我还可以改进的部分不会使这本书中其他有用的部分失效。 37 | 38 | 这和我们的代码是一致的。将这些原则应用到代码的更多部分越多,代码就会越好。25%的时间好好利用它们,你会得到一些好处。80%的时间使用它们,你会看到更多的好处。 39 | 40 | 除了少数例外,我认为您不会在本文中找到很多绝对词。相反,我们将谈论抱负、目标和奋斗的原则。我们将讨论函数的平衡、实用主义和权衡。 41 | 42 | 欢迎来到这趟旅程,了解FP最有用和最实用的基础。我们都有很多东西要学! 43 | --------------------------------------------------------------------------------