├── .editorconfig ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── content-question.md │ ├── foreign-translation-request.md │ ├── report-technical-mistake.md │ └── textual-grammar-typo.md ├── 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 └── 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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [getify] 4 | patreon: getify 5 | custom: ['https://www.paypal.com/paypalme2/getify','https://www.blockchain.com/btc/address/32R5dVrqirdcbiyvUw85y7YbPFZTd7YpnH'] 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/content-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Content Question 3 | about: Ask a question about something you read in the books? 4 | labels: 5 | 6 | --- 7 | 8 | **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). 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/foreign-translation-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Foreign Translation Request 3 | about: Want to request a translation into a foreign language? 4 | labels: 5 | 6 | --- 7 | 8 | Please check these issues first: 9 | 10 | * https://github.com/getify/Functional-Light-JS/issues?utf8=%E2%9C%93&q=label%3A%22foreign+language+translations%22+ 11 | * https://github.com/getify/Functional-Light-JS/issues/106 12 | * https://github.com/getify/Functional-Light-JS/issues/115 13 | 14 | To summarize, the steps for a foreign language translation are: 15 | 16 | 1. Fork this repo 17 | 2. Make your own translation entirely in your fork, preferably of all six books, but at a minimum of one whole book 18 | 3. File an issue asking for a branch to be made on our main repo, named for that [language's ISO code](http://www.lingoes.net/en/translator/langcode.htm) 19 | 4. Once the branch is created, you can PR to merge your translated work in 20 | 5. Once the merge is complete, I will promote you to a repository maintainer so you can manage any further translation maintenance work on your own branch of this repo 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-technical-mistake.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report Technical Mistake 3 | about: Help us fix a mistake in the code. 4 | labels: 5 | 6 | --- 7 | 8 | **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). 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/textual-grammar-typo.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Textual/Grammar Typo 3 | about: Help us correct a spelling or grammar error in the text. 4 | labels: 5 | 6 | --- 7 | 8 | **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). 9 | -------------------------------------------------------------------------------- /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 | # Functional-Light 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 | Book Cover 6 | 7 | This book is a balanced, pragmatic look at FP in JavaScript. The first edition is now complete. Read here online **for free**, or: 8 | 9 |

10 | Buy on Leanpub Buy on Manning Buy on Amazon 11 |

12 | 13 | "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. 14 | 15 | **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. 16 | 17 | 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. 18 | 19 | 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. 20 | 21 | 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. 22 | 23 | **To read more about the motivations and perspective behind this book, check out the [Preface](manuscript/preface.md).** 24 | 25 | ## Book 26 | 27 | [Table of Contents](manuscript/README.md/#table-of-contents) 28 | 29 | * [Foreword](manuscript/foreword.md/#foreword) (by [Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean)) 30 | * [Preface](manuscript/preface.md/#preface) 31 | * [Chapter 1: Why Functional Programming?](manuscript/ch1.md/#chapter-1-why-functional-programming) 32 | * [Chapter 2: The Nature Of Functions](manuscript/ch2.md/#chapter-2-the-nature-of-functions) 33 | * [Chapter 3: Managing Function Inputs](manuscript/ch3.md/#chapter-3-managing-function-inputs) 34 | * [Chapter 4: Composing Functions](manuscript/ch4.md/#chapter-4-composing-functions) 35 | * [Chapter 5: Reducing Side Effects](manuscript/ch5.md/#chapter-5-reducing-side-effects) 36 | * [Chapter 6: Value Immutability](manuscript/ch6.md/#chapter-6-value-immutability) 37 | * [Chapter 7: Closure vs Object](manuscript/ch7.md/#chapter-7-closure-vs-object) 38 | * [Chapter 8: Recursion](manuscript/ch8.md/#chapter-8-recursion) 39 | * [Chapter 9: List Operations](manuscript/ch9.md/#chapter-9-list-operations) 40 | * [Chapter 10: Functional Async](manuscript/ch10.md/#chapter-10-functional-async) 41 | * [Chapter 11: Putting It All Together](manuscript/ch11.md/#chapter-11-putting-it-all-together) 42 | * [Appendix A: Transducing](manuscript/apA.md/#appendix-a-transducing) 43 | * [Appendix B: The Humble Monad](manuscript/apB.md/#appendix-b-the-humble-monad) 44 | * [Appendix C: FP Libraries](manuscript/apC.md/#appendix-c-fp-libraries) 45 | 46 | ## Publishing 47 | 48 | This book has been published and is now available for purchase (in both ebook and print formats) from these sources: 49 | 50 |

51 | Buy on Leanpub Buy on Manning Buy on Amazon 52 |

53 | 54 | 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. 55 | 56 | [![patreon.png](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/getify) 57 | 58 | ## In-person Training 59 | 60 | 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. 61 | 62 | 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 63 | 64 | ## Online Video Training 65 | 66 | 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). 67 | 68 | ## Contributions 69 | 70 | Any contributions you make to this effort **are of course greatly appreciated**. 71 | 72 | But **PLEASE** read the [Contributions Guidelines](CONTRIBUTING.md) carefully before submitting a PR. 73 | 74 | ## License & Copyright 75 | 76 | The materials herein are all (c) 2016-2018 Kyle Simpson. 77 | 78 | Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 4.0 Unported License. 79 | -------------------------------------------------------------------------------- /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 | # Functional-Light 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 | ## Table of Contents 8 | 9 | * [Foreword](foreword.md/#foreword) 10 | * [Preface](preface.md/#preface) 11 | * [Chapter 1: Why Functional Programming?](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 | * [Chapter 2: The Nature Of Functions](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 | * [Chapter 3: Managing Function Inputs](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 | * [Chapter 4: Composing Functions](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 | * [Chapter 5: Reducing Side Effects](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 | * [Chapter 6: Value Immutability](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 | * [Chapter 7: Closure vs Object](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 | * [Chapter 8: Recursion](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 | * [Chapter 9: List Operations](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 | * [Chapter 10: Functional Async](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 | * [Chapter 11: Putting It All Together](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 | * [Appendix A: Transducing](apA.md/#appendix-a-transducing) 80 | * [Why, First](apA.md/#why-first) 81 | * [How, Next](apA.md/#how-next) 82 | * [What, Finally](apA.md/#what-finally) 83 | * [Appendix B: The Humble Monad](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 | * [Appendix C: FP Libraries](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 | -------------------------------------------------------------------------------- /manuscript/apC.md: -------------------------------------------------------------------------------- 1 | # Functional-Light JavaScript 2 | # Appendix C: FP Libraries 3 | 4 | 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. 5 | 6 | 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. 7 | 8 | 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. 9 | 10 | ## Stuff to Investigate 11 | 12 | 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: 13 | 14 | * [Ramda](http://ramdajs.com): General FP Utilities 15 | * [Sanctuary](https://github.com/sanctuary-js/sanctuary): Ramda Companion for FP Types 16 | * [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide): General FP Utilities 17 | * [functional.js](http://functionaljs.com/): General FP Utilities 18 | * [Immutable](https://github.com/facebook/immutable-js): Immutable Data Structures 19 | * [Mori](https://github.com/swannodette/mori): (ClojureScript Inspired) Immutable Data Structures 20 | * [Seamless-Immutable](https://github.com/rtfeldman/seamless-immutable): Immutable Data Helpers 21 | * [transducers-js](https://github.com/cognitect-labs/transducers-js): Transducers 22 | * [monet.js](https://github.com/monet/monet.js): Monadic Types 23 | 24 | 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). 25 | 26 | 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). 27 | 28 | 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. 29 | 30 | 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. 31 | 32 | ## Ramda (0.23.0) 33 | 34 | From the [Ramda documentation](http://ramdajs.com/): 35 | 36 | > Ramda functions are automatically curried. 37 | > 38 | > The parameters to Ramda functions are arranged to make it convenient for currying. The data to be operated on is generally supplied last. 39 | 40 | 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). 41 | 42 | 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: 43 | 44 | ```js 45 | function output(msg) { 46 | console.log( msg ); 47 | } 48 | 49 | function isShortEnough(str) { 50 | return str.length <= 5; 51 | } 52 | 53 | var isLongEnough = R.complement( isShortEnough ); 54 | 55 | var printIf = R.partial( R.flip( R.when ), [output] ); 56 | 57 | var msg1 = "Hello"; 58 | var msg2 = msg1 + " World"; 59 | 60 | printIf( isShortEnough, msg1 ); // Hello 61 | printIf( isShortEnough, msg2 ); 62 | 63 | printIf( isLongEnough, msg1 ); 64 | printIf( isLongEnough, msg2 ); // Hello World 65 | ``` 66 | 67 | A few differences to point out compared to [Chapter 3's approach](ch3.md/#user-content-finalshortlong): 68 | 69 | * We use `R.complement(..)` instead of `not(..)` to create a negating function `isLongEnough(..)` around `isShortEnough(..)`. 70 | 71 | * 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. 72 | 73 | * `R.partial(..)` takes all of its subsequent arguments (beyond the function) as a single array. 74 | 75 | * 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. 76 | 77 | 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. 78 | 79 | ## Lodash/fp (4.17.4) 80 | 81 | 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). 82 | 83 | 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": 84 | 85 | ```js 86 | var sum = (x,y) => x + y; 87 | var double = x => x * 2; 88 | var isOdd = x => x % 2 == 1; 89 | 90 | fp.compose( [ 91 | fp.reduce( sum )( 0 ), 92 | fp.map( double ), 93 | fp.filter( isOdd ) 94 | ] ) 95 | ( [1,2,3,4,5] ); // 18 96 | ``` 97 | 98 | 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! 99 | 100 | Notice that `fp.compose(..)` (also known as `_.flowRight(..)` in lodash proper) takes an array of functions instead of individual arguments. 101 | 102 | You cannot beat the stability, widespread community support, and performance of lodash. It's a solid bet for your FP explorations. 103 | 104 | ## Mori (0.3.2) 105 | 106 | In [Chapter 6](ch6.md), we already briefly glanced at the Immutable.js library, probably the most well-known for immutable data structures. 107 | 108 | 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. 109 | 110 | ```js 111 | var state = mori.vector( 1, 2, 3, 4 ); 112 | 113 | var newState = mori.assoc( 114 | mori.into( state, Array.from( {length: 39} ) ), 115 | 42, 116 | "meaning of life" 117 | ); 118 | 119 | state === newState; // false 120 | 121 | mori.get( state, 2 ); // 3 122 | mori.get( state, 42 ); // undefined 123 | 124 | mori.get( newState, 2 ); // 3 125 | mori.get( newState, 42 ); // "meaning of life" 126 | 127 | mori.toJs( newState ).slice( 1, 3 ); // [2,3] 128 | ``` 129 | 130 | Some interesting things to point out about Mori for this example: 131 | 132 | * 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. 133 | 134 | * 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. 135 | 136 | * 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. 137 | 138 | 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. 139 | 140 | 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. 141 | 142 | ## Bonus: FPO 143 | 144 | 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: 145 | 146 | ```js 147 | function foo( {x,y} = {} ) { 148 | console.log( x, y ); 149 | } 150 | 151 | foo( { 152 | y: 3 153 | } ); // undefined 3 154 | ``` 155 | 156 | 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: 157 | 158 | ```js 159 | function foo({ x, y, z } = {}) { 160 | console.log( `x:${x} y:${y} z:${z}` ); 161 | } 162 | 163 | var f1 = curryProps( foo, 3 ); 164 | 165 | f1( {y: 2} )( {x: 1} )( {z: 3} ); 166 | ``` 167 | 168 | 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. 169 | 170 | 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. 171 | 172 | 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. 173 | 174 | 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. 175 | 176 | From the documentation: 177 | 178 | ```js 179 | // Ramda's `reduce(..)` 180 | R.reduce( 181 | (acc,v) => acc + v, 182 | 0, 183 | [3,7,9] 184 | ); // 19 185 | 186 | // FPO named-argument method style 187 | FPO.reduce({ 188 | arr: [3,7,9], 189 | fn: ({acc,v}) => acc + v 190 | }); // 19 191 | ``` 192 | 193 | 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. 194 | 195 | 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: 196 | 197 | ```js 198 | var f = FPO.reduce({ arr: [3,7,9] }); 199 | 200 | // later 201 | 202 | f({ fn: ({acc,v}) => acc + v }); // 19 203 | ``` 204 | 205 | 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: 206 | 207 | ```js 208 | FPO.std.reduce( 209 | (acc,v) => acc + v, 210 | undefined, 211 | [3,7,9] 212 | ); // 19 213 | ``` 214 | 215 | 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! 216 | 217 | ## Bonus #2: fasy 218 | 219 | 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. 220 | 221 | 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. 222 | 223 | Let me quickly illustrate. 224 | 225 | 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: 226 | 227 | ```js 228 | var imageURLs = [ 229 | "https://some.tld/image1.png", 230 | "https://other.tld/image2.png", 231 | "https://various.tld/image3.png" 232 | ]; 233 | 234 | var images = imageURLs.map( fetchImage ); 235 | ``` 236 | 237 | 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. 238 | 239 | 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: 240 | 241 | ```js 242 | Promise.all( images ) 243 | .then(function allImages(imgObjs){ 244 | // .. 245 | }); 246 | ``` 247 | 248 | 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. 249 | 250 | 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. 251 | 252 | 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. 253 | 254 | Another way to visualize this distinction is that Observables support "vertical asynchrony", whereas what I'm talking about would be "horizontal asynchrony". 255 | 256 | Consider: 257 | 258 | ```js 259 | var obsv = Rx.Observable.from( [1,2,3,4,5] ); 260 | 261 | obsv 262 | .map( x => x * 2 ) 263 | .delay( 100 ) // <-- vertical asynchrony 264 | .map( x => x + 1 ) 265 | .subscribe( v => console.log ); 266 | // {after 100 ms} 267 | // 3 268 | // 5 269 | // 7 270 | // 9 271 | // 11 272 | ``` 273 | 274 | 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. 275 | 276 | 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. 277 | 278 | So, how do we support both serial and concurrent iteration across asynchronous operations? 279 | 280 | **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). 281 | 282 | To illustrate **fasy**, let's consider a concurrent `map(..)` versus a serial `map(..)`: 283 | 284 | ```js 285 | FA.concurrent.map( fetchImage, imageURLs ) 286 | .then( function allImages(imgObjs){ 287 | // .. 288 | } ); 289 | 290 | FA.serial.map( fetchImage, imageURLs ) 291 | .then( function allImages(imgObjs){ 292 | // .. 293 | } ); 294 | ``` 295 | 296 | 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. 297 | 298 | Your instinct might be that concurrent would always be preferable, and while that may be common, it's not always the case. 299 | 300 | 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. 301 | 302 | 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. 303 | 304 | Along with Observables, **fasy** will help you extend more FP patterns and principles to your asynchronous operations. 305 | 306 | ## Summary 307 | 308 | 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. 309 | 310 | 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! 311 | 312 | 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! 313 | -------------------------------------------------------------------------------- /manuscript/foreword.md: -------------------------------------------------------------------------------- 1 | # Functional-Light JavaScript 2 | # Foreword 3 | 4 | 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. 5 | 6 | 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. 7 | 8 | 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. 9 | 10 | 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. 11 | 12 | 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. 13 | 14 | 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! 15 | 16 | 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. 17 | 18 | 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. 19 | 20 | This book is a terrific place to begin your FP journey. Take it away, Kyle... 21 | 22 | *-Brian Lonsdorf (@drboolean)* 23 | -------------------------------------------------------------------------------- /manuscript/images/fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig1.png -------------------------------------------------------------------------------- /manuscript/images/fig10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig13.png -------------------------------------------------------------------------------- /manuscript/images/fig13.svg: -------------------------------------------------------------------------------- 1 | 2 |
function foo(x) {
   if (x < 5) return x;
   return foo(x / 2);
}
[Not supported by viewer]
16
[Not supported by viewer]
8
[Not supported by viewer]
function foo(x) {
   if (x < 5) return x;
   return foo(x / 2);
}
[Not supported by viewer]
16
[Not supported by viewer]
8
[Not supported by viewer]
4
[Not supported by viewer]
function foo(x) {
   if (x < 5) return x;
   return foo(x / 2);
}
[Not supported by viewer]
16
[Not supported by viewer]
8
[Not supported by viewer]
4
[Not supported by viewer]
4
[Not supported by viewer]
Step 1
[Not supported by viewer]
Step 2
[Not supported by viewer]
Step 3
[Not supported by viewer]
Step 4
[Not supported by viewer]
function foo(x) {
   if (x < 5) return x;
   return foo(x / 2);
}
[Not supported by viewer]
16
[Not supported by viewer]
-------------------------------------------------------------------------------- /manuscript/images/fig14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig14.png -------------------------------------------------------------------------------- /manuscript/images/fig15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig2.png -------------------------------------------------------------------------------- /manuscript/images/fig3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig3.png -------------------------------------------------------------------------------- /manuscript/images/fig3.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig4.png -------------------------------------------------------------------------------- /manuscript/images/fig4.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig5.png -------------------------------------------------------------------------------- /manuscript/images/fig5.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig6.png -------------------------------------------------------------------------------- /manuscript/images/fig6.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/fig7.png -------------------------------------------------------------------------------- /manuscript/images/fig7.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /manuscript/images/fig8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/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/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/back-cover-hd.png -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/front-cover-hd.png -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-hd.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/front-cover-hd.tiff -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/front-cover-sd.png -------------------------------------------------------------------------------- /manuscript/images/marketing/front-cover-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/front-cover-small.png -------------------------------------------------------------------------------- /manuscript/images/marketing/print-book-cover.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/print-book-cover.pdf -------------------------------------------------------------------------------- /manuscript/images/marketing/print-book-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/print-book-cover.png -------------------------------------------------------------------------------- /manuscript/images/marketing/social-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/social-avatar.png -------------------------------------------------------------------------------- /manuscript/images/marketing/social-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getify/Functional-Light-JS/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/images/marketing/social-header.png -------------------------------------------------------------------------------- /manuscript/preface.md: -------------------------------------------------------------------------------- 1 | # Functional-Light JavaScript 2 | # Preface 3 | 4 | > *A monad is just a monoid in the category of endofunctors.* 5 | 6 | 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. 7 | 8 | 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. 9 | 10 | 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. 11 | 12 | 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. 13 | 14 | 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. 15 | 16 | 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!? 17 | 18 | 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(..)`. 19 | 20 | Eventually I began to gather all these tidbits of understanding into what I now call "Functional-Light Programming" (FLP). 21 | 22 | ## Mission 23 | 24 | But why is it so important for you to learn functional programming, even the light form? 25 | 26 | 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. 27 | 28 | 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. 29 | 30 | > The curse of the monad is that... once you understand... you lose the ability to explain it to anyone else. 31 | > 32 | > Douglas Crockford 2012 "Monads and Gonads" 33 | > 34 | > https://www.youtube.com/watch?v=dkZFtimgAcM 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | 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. 43 | 44 | 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. 45 | 46 | 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. 47 | 48 | Welcome to this journey into the most useful and practical foundations of FP. We both have plenty to learn! 49 | --------------------------------------------------------------------------------