├── .editorconfig
├── .github
└── 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/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 | # Функционально-Легкий JavaScript
2 |
3 | [](http://creativecommons.org/licenses/by-nc-nd/4.0/)
4 |
5 |
6 |
7 | Эта книга представляет собой сбалансированный и прагматичный взгляд на Функциональное Программирование на языке JavaScript. Здесь вы можете прочесть первое издание, либо купить книгу онлайн:
8 |
9 |
10 |
11 |
12 |
13 | "Функционально-Лёгкий JavaScript" раскрывает основные принципы функционального программирования (ФП) в контексте их применения на языке JavaScript. Однако, данную книгу от большинства изданий отличает то, что в ней описывается исключительно практический подход к данным принципам, без погружения в узкоспециализированную терминологию. В процессе повествования мы постараемся раскрыть подмножество фундамемнтальных ФП концепций, которые я называл "Функционально-Легким Программированием" (ФЛП) и реализуем их на JavaScript.
14 |
15 | **Примечание:** Не смотря на слово "Легкий" в названии, эта книга не подходит для новичков и не рекомендуется в качестве первого шага в изучении JavaScript. От читателя ожидается уверенное знание JS, так как книга содержит множество специфических нюансов, которые могут ускользнуть от понимания человека, не достаточно глубоко знакомого с языком. "Легкий" - означает прежде всего ограниченный по объему; Вместо широкого и поверхностного описания, которое встречается в большинстве книг по ФП на JavaScript, мы более глубоко погрузимся в каждую из тематик, касающихся функционального программирования на JavaScript.
16 |
17 | Необходимо признать: если вы, как и я, не являетесь членом закрытого клуба экспертов в ФП, утверждения вида: "Монада это просто моноид в категории эндофункторов", не несут для вас ровно никакого смысла.
18 |
19 | Однако, я не хочу сказать что ФП терминология лишена смысла полностью, или что её использование является "дурным тоном". Я надеюсь, что ознакомившись с "Функционально-Легким" подходом, вы, возможно, заинтересуетесь более формальным изучением Функционально Программирования и получите достаточное количество информации о том где и как следует применять соответствующую терминологию.
20 |
21 | Я хочу чтобы вы были способны применять некоторые из фундаментальных принципов ФП уже сейчас, ибо верю, что это пожет вам писать более качественный, осымсленный и поддерживаемый код.
22 |
23 | **Более подробно с мотивацией, подтолкнувшей меня к написанию данного материала, вы можете ознакомиться в [Предисловии](manuscript/preface.md).**
24 |
25 | ## Книга
26 |
27 | [Содержание](manuscript/README.md/#table-of-contents)
28 |
29 | * [Введение](manuscript/foreword.md/#foreword) ([Brian Lonsdorf, aka "Prof Frisby"](https://twitter.com/DrBoolean))
30 | * [Предисловие](manuscript/preface.md/#preface)
31 | * [Глава 1: Почему Функциональное Программирование?](manuscript/ch1.md/#chapter-1-why-functional-programming)
32 | * [Глава 2: Природа функций](manuscript/ch2.md/#chapter-2-the-nature-of-functions)
33 | * [Глава 3: Работа с аргументами](manuscript/ch3.md/#chapter-3-managing-function-inputs)
34 | * [Глава 4: Композиция](manuscript/ch4.md/#chapter-4-composing-functions)
35 | * [Глава 5: Уменьшение побочных эффектов](manuscript/ch5.md/#chapter-5-reducing-side-effects)
36 | * [Глава 6: Ценность иммутабелности](manuscript/ch6.md/#chapter-6-value-immutability)
37 | * [Глава 7: Замыкание против Объекта](manuscript/ch7.md/#chapter-7-closure-vs-object)
38 | * [Глава 8: Рекурсия](manuscript/ch8.md/#chapter-8-recursion)
39 | * [Глава 9: Работа с массивами](manuscript/ch9.md/#chapter-9-list-operations)
40 | * [Глава 10: Асинхронность в ФП](manuscript/ch10.md/#chapter-10-functional-async)
41 | * [Глава 11: Собираем все вместе](manuscript/ch11.md/#chapter-11-putting-it-all-together)
42 | * [Приложение A: Преобразования (Transducing)](manuscript/apA.md/#appendix-a-transducing)
43 | * [Приложение B: О Скромной Монаде](manuscript/apB.md/#appendix-b-the-humble-monad)
44 | * [Приложение C: ФП Библиотеки](manuscript/apC.md/#appendix-c-fp-libraries)
45 |
46 | ## Публикация
47 |
48 | Данная книга в оригинале [была опубликована и доступна к покупке на Leanpub](https://leanpub.com/fljs/). Я также работаю над доступностью бумажной версии, но на текущий момент её судьба пока не известна.
49 |
50 | [](https://leanpub.com/fljs)
51 |
52 | Если вы хотите поддержать материально мои усилия, помимо покупки книги, это можно сделать на [patreon](https://www.patreon.com/getify).
53 |
54 | [](https://www.patreon.com/getify)
55 |
56 | ## Персональные тренинги
57 |
58 | Информация в этой книге основывается на материалах проводимого мной одноименного тренинга.
59 |
60 | Если вам понравился материал этой книги, вы можете связаться со мной по поводу проведения тренинга по этой или любой другой тематике, связанной с JS/HTML5/Node.js по email: getify @ gmail
61 |
62 | ## Онлайн тренинги
63 |
64 | У меня также есть несколько JS тренингов, доступных онлайн. Я [веду курсы](https://FrontendMasters.com/teachers/kyle-simpson) на платформе [Frontend Masters](https://FrontendMasters.com), где вы можете найти мой воркшоп [Functional-Light JavaScript v2](https://frontendmasters.com/courses/functional-javascript-v2/). Некоторые из моих курсов также доступны на [PluralSight](https://www.pluralsight.com/search?q=kyle%20simpson&categories=all).
65 |
66 |
67 | ## Лицензия
68 |
69 | Права на все материалы принадлежат Kyle Simpson. (c) 2016-2018
70 |
71 | Creative Commons Attribution-NonCommercial-NoDerivs 4.0 Unported License.
72 |
--------------------------------------------------------------------------------
/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 | [](https://leanpub.com/fljs) [](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 | * [В общих чертах](ch1.md/#at-a-glance)
13 | * [Надежность](ch1.md/#confidence)
14 | * [Взаимодействие](ch1.md/#communication)
15 | * [Читабельность](ch1.md/#readability)
16 | * [Перспектива](ch1.md/#perspective)
17 | * [Как найти баланс](ch1.md/#how-to-find-balance)
18 | * [Материалы](ch1.md/#resources)
19 | * [Глава 2: Природа функций](ch2.md/#chapter-2-the-nature-of-functions)
20 | * [Что такое функция?](ch2.md/#what-is-a-function)
21 | * [Аргументы функции](ch2.md/#function-input)
22 | * [Именованные аргументы](ch2.md/#named-arguments)
23 | * [Возвращаемые значения](ch2.md/#function-output)
24 | * [Композиция функций](ch2.md/#functions-of-functions)
25 | * [Синтакс](ch2.md/#syntax)
26 | * [Что такое This?](ch2.md/#whats-this)
27 | * [Глава 3: Работа с аргументами](ch3.md/#chapter-3-managing-function-inputs)
28 | * [Все за одного](ch3.md/#all-for-one)
29 | * [Аргументы и Параметры](ch3.md/#adapting-arguments-to-parameters)
30 | * [Последовательная передача аргументов](ch3.md/#some-now-some-later)
31 | * [По одному](ch3.md/#one-at-a-time)
32 | * [Важность порядка](ch3.md/#order-matters)
33 | * [Избавляемся от точек](ch3.md/#no-points)
34 | * [Глава 4: Композиция](ch4.md/#chapter-4-composing-functions)
35 | * [Возвращаемое значение как аргумент](ch4.md/#output-to-input)
36 | * [Прямая композиция](ch4.md/#general-composition)
37 | * [Обратная композиция](ch4.md/#reordered-composition)
38 | * [Абстракция](ch4.md/#abstraction)
39 | * [И снова о точках](ch4.md/#revisiting-points)
40 | * [Глава 5: Уменьшение побочных эффектов](ch5.md/#chapter-5-reducing-side-effects)
41 | * [Отделение сайдэффектов](ch5.md/#effects-on-the-side-please)
42 | * [И одного раза хватит](ch5.md/#once-is-enough-thanks)
43 | * [Чистое блаженство](ch5.md/#pure-bliss)
44 | * [Где да, а где - нет](ch5.md/#there-or-not)
45 | * [Очищение кода](ch5.md/#purifying)
46 | * [Глава 6: Ценность иммутабелности](ch6.md/#chapter-6-value-immutability)
47 | * [Иммутабельность примитивов](ch6.md/#primitive-immutability)
48 | * [Value To Value](ch6.md/#value-to-value)
49 | * [Переопределение](ch6.md/#reassignment)
50 | * [Производительность](ch6.md/#performance)
51 | * [Treatment](ch6.md/#treatment)
52 | * [Глава 7: Замыкание против Объекта](ch7.md/#chapter-7-closure-vs-object)
53 | * [Сверим часы](ch7.md/#the-same-page)
54 | * [В чем схожесть](ch7.md/#look-alike)
55 | * [В чем отличие](ch7.md/#two-roads-diverged-in-a-wood)
56 | * [Глава 8: Рекурсия](ch8.md/#chapter-8-recursion)
57 | * [Определение рекурсии](ch8.md/#definition)
58 | * [Декларатинвая рекурсия](ch8.md/#declarative-recursion)
59 | * [Стек](ch8.md/#stack)
60 | * [Переосмысление рекурсии](ch8.md/#rearranging-recursion)
61 | * [Глава 9: Работа с массивами](ch9.md/#chapter-9-list-operations)
62 | * [Работа с массивами в императивном стиле](ch9.md/#non-fp-list-processing)
63 | * [Map](ch9.md/#map)
64 | * [Filter](ch9.md/#filter)
65 | * [Reduce](ch9.md/#reduce)
66 | * [Продвинутые методы работы с массивами](ch9.md/#advanced-list-operations)
67 | * [Метод или функция](ch9.md/#method-vs-standalone)
68 | * [В поиске массивов](ch9.md/#looking-for-lists)
69 | * [Слияние](ch9.md/#fusion)
70 | * [Не только массивы](ch9.md/#beyond-lists)
71 | * [Глава 10: Асинхронность в FP](ch10.md/#chapter-10-functional-async)
72 | * [Время как состояние](ch10.md/#time-as-state)
73 | * [Нетерпеливое и Ленивое исполнения](ch10.md/#eager-vs-lazy)
74 | * [Реактивное функциональное программирование](ch10.md/#reactive-fp)
75 | * [Глава 11: Собираем все вместе](ch11.md/#chapter-11-putting-it-all-together)
76 | * [Настройка](ch11.md/#setup)
77 | * [События](ch11.md/#stock-events)
78 | * [Интерфейс биржевого тикера](ch11.md/#stock-ticker-ui)
79 | * [Приложение A: Преобразования (Transducing)](apA.md/#appendix-a-transducing)
80 | * [Зачем, First](apA.md/#why-first)
81 | * [Как, Next](apA.md/#how-next)
82 | * [Что, Finally](apA.md/#what-finally)
83 | * [Приложение B: О Скромной (Humble) Монаде](apB.md/#appendix-b-the-humble-monad)
84 | * [Type](apB.md/#type)
85 | * [Loose Interface](apB.md/#loose-interface)
86 | * [Just](apB.md/#just-a-monad)
87 | * [Maybe](apB.md/#maybe)
88 | * [Humble](apB.md/#humble)
89 | * [Приложение C: FP Библиотеки](apC.md/#appendix-c-fp-libraries)
90 | * [Основные FP библиотеки](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 | * [Бонус: FPO](apC.md/#bonus-fpo)
95 | * [Бонус #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 | # Функционально-Лёгкий JavaScript
2 | # Введение
3 |
4 | Ни для кого не секрет, что я являюсь адептом Функционально Программирования. И пропагандирую эти идеи, равно как и функционалные языки везде, где только могу. Стараюсь читать свежие научные статьи, изучать абстрактную алгебру в свое свободное (рабочее…) время :). И даже когда я пишу код на JavaScript, я никогда не использую нечистые выражения. Именно это в свое время привело меня к написанию *Почти адекватного руководства по Функциональному Программированию от Профессора Фрисби*. Да, такой вот я догматичный фанатик.
5 |
6 | Однако, я не всегда был таким… Когда-то я был одержим объектами. Я любил это моделирование "реального мира". Я был изобретателем синтетических автоматов, идеально работающих черных ящиков. Создателем разумных марионеток, стучащий пальцамии по клавиатуре, чтобы дать им жизнь - этакий цифровой Папа Карло 1337 h4x0r. Тем не менее, за более чем 5 лет написания объектно-ориентированного кода, я никогда не был по настоящему удовлетворен результатом. Этот подход никогда не работал достаточно хорошо. Я чувствовал себя паршивым программистом. И совсем было потерял веру в то, что возможна простая и гибкая кодовая база приличного масштаба.
7 |
8 | Тогда я решил попробовать что-то другое: функциональное программирование. Я начал баловаться функциональными идеями в своей повседневной работе и, к большому разочарованию моих коллег, не имел ни малейшего понятия о том, что я делал. Код, написанный мной в те дни, был ужасен. Монструозен. Настоящая цифровая помойка. Причиной было главным образом отсутствие цели, равно как и ясного видения того, чего же я пытался достичь. И у меня не было волшебного сверчка, который мог бы мне подсказать. Потребовалось много времени и много отвратительно написанных программ, чтобы понять, как эффективно работать с ФП.
9 |
10 | Но теперь, после всего этого грязного исследования, я чувствую, что чистое функциональное программирование выполнило свое обещание. Читаемые программы существуют! Повторное использование существует! Я больше не изобретаю, а скорее открываю свои программы. Я превратился в настоящего детектива, раскрывающего тайный заговор, в пробковую доску, заполненную математическими доказательствами. В Кусто Цифровой Эпохи, записывающего наблюдения об этой странной земле во имя науки! Мой код все ещё не идеален, и мне все ещё есть чему поучиться, но я никогда ещё не был так доволен своей работой и её результатом.
11 |
12 | Если бы эта книга существовала, когда я только начинал, мой переход в мир функционального программирования был бы намного проще и куда менее разрушительным. Эта книга представляет собой решение "два в одном": она не только научит вас эффективному использованию разнообразных ФП конструкций в вашем повседненом коде, но, что более важно, даст вам цель и руководящие принципы, которые будут непрерывно направлять вас.
13 |
14 | Вы изучите "Функционльно-Лёгкую" парадигму, которую Кайл впервые применил для декларативного функционального программирования, обеспечивая баланс и взаимодействие с остальной экосистемой JavaScript. Вы поймете основу, на которой построено чистое функциональное программирование, без необходимости полностью и безоговорочно подписываться на эту парадигму. Вы приобретете навыки, необходимые для чтобы практиковаться и изучать мир ФП без необходимости переписывания существующего кода. Вы сможете сделать шаг вперед в своей карьере разработчика, не отступая и не теряя направления, как делал я много лет назад. И да возрадуются ваши коллеги!
15 |
16 | Кайл - великий учитель, известный своей неустанной погоней за целостностью картины, не оставляющий в обучении пробелов и неясных мест, но при этом остающийся способным прочувствовать тяжелое положение ученика. Его стиль нашел отклик в индустрии, подняв качество преподавания JavaScript на новый уровень. Его работы занимают прочное место в истории, а также на панели закладок большинства JS разработчиков. Так что вы в хороших руках.
17 |
18 | Функциональное программирование имеет много разных определений. К примеру, функциональный подход в Lisp сильно отличается от такового в Haskell. ФП в OCaml имеет крайне мало общего с ФП в Erlang. Вы даже можете найти несколько конкурирующих определений для его описания в JavaScript. Но все же все они имеют одну общую связь, объединяющую разрозненные опередления в единое *ощущение*, и эта книга определенно может его передать. Конечный результат возможно не будет считаться идиоматическим в определенных кругах, но знания, полученные здесь, непосредственно относятся и могут быть применены к любой разновидности ФП.
19 |
20 | Эта книга - потрясающее место для начала вашего путешествия по миру Функционально Программирования. Удали это, Кайл...
21 |
22 | *-Brian Lonsdorf (@drboolean)*
23 |
--------------------------------------------------------------------------------
/manuscript/images/fig1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig1.png
--------------------------------------------------------------------------------
/manuscript/images/fig10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig10.png
--------------------------------------------------------------------------------
/manuscript/images/fig10.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig11.png
--------------------------------------------------------------------------------
/manuscript/images/fig11.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig12.png
--------------------------------------------------------------------------------
/manuscript/images/fig12.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig13.png
--------------------------------------------------------------------------------
/manuscript/images/fig13.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig14.png
--------------------------------------------------------------------------------
/manuscript/images/fig15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig15.png
--------------------------------------------------------------------------------
/manuscript/images/fig15.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig16.png
--------------------------------------------------------------------------------
/manuscript/images/fig16.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig17.png
--------------------------------------------------------------------------------
/manuscript/images/fig17.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig18.png
--------------------------------------------------------------------------------
/manuscript/images/fig18.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig19.png
--------------------------------------------------------------------------------
/manuscript/images/fig19.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig2.png
--------------------------------------------------------------------------------
/manuscript/images/fig3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig3.png
--------------------------------------------------------------------------------
/manuscript/images/fig3.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig4.png
--------------------------------------------------------------------------------
/manuscript/images/fig4.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig5.png
--------------------------------------------------------------------------------
/manuscript/images/fig5.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig6.png
--------------------------------------------------------------------------------
/manuscript/images/fig6.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig7.png
--------------------------------------------------------------------------------
/manuscript/images/fig7.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig8.png
--------------------------------------------------------------------------------
/manuscript/images/fig8.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/fig9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/fig9.png
--------------------------------------------------------------------------------
/manuscript/images/fig9.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/manuscript/images/marketing/back-cover-hd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/back-cover-hd.png
--------------------------------------------------------------------------------
/manuscript/images/marketing/front-cover-hd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/front-cover-hd.png
--------------------------------------------------------------------------------
/manuscript/images/marketing/front-cover-hd.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/front-cover-hd.tiff
--------------------------------------------------------------------------------
/manuscript/images/marketing/front-cover-sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/front-cover-sd.png
--------------------------------------------------------------------------------
/manuscript/images/marketing/front-cover-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/front-cover-small.png
--------------------------------------------------------------------------------
/manuscript/images/marketing/print-book-cover.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/print-book-cover.pdf
--------------------------------------------------------------------------------
/manuscript/images/marketing/print-book-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/print-book-cover.png
--------------------------------------------------------------------------------
/manuscript/images/marketing/social-avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/social-avatar.png
--------------------------------------------------------------------------------
/manuscript/images/marketing/social-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxzhukov/Functional-Light-JS-RU/4c12f5141562383e3473555274220ae1589da07a/manuscript/images/marketing/social-header.png
--------------------------------------------------------------------------------
/manuscript/preface.md:
--------------------------------------------------------------------------------
1 | # Функционально-легкий JavaScript
2 | # Предисловие
3 |
4 | > *Монада - просто моноид в категории эндофункторов.*
5 |
6 | Вы только что растерялись, верно? Не переживайте, я был таким же растерянным, когда увидел это впервые! Все эти термины имеют какое-то значение только для тех, кто уже знаком с Функциональным Программированием™ (ФП), и являются просто набором звуков для всех остальных.
7 |
8 | Эта книга не ставит своей целью научить читателя значению этих слов. Если это то, что вы хотите узнать - лучше попробовать поискать в другом месте. На самом деле, уже написано множество прекрасных книг, обучающих функциональному программированию в "правильном стиле". И конечно, эти термины исполнены смысла, так что, если вы занимаетесь глубоким формальным изучением функционального программирования, вы абсолютно обязаны разобраться в их значении.
9 |
10 | Но эта книга подходит к вопросу с другой стороны. Я собираюсь представить вам фундаментальные концепции ФП, подходя к ним с практической стороны, используя минимум специализированной и неинтуитивной терминологии. **Термины тоже будут**, не переживайте. Но мы будет подходить к ним последовательно и осторожно, сопровождая их введение подробным обсуждением и разъяснением важности.
11 |
12 | К сожалению, я не являюсь членом закрытого клуба экспертов в ФП. Я никогда не занимался формальным изучением функционального программирования. И хотя я имею образование в области Computer Science и довольно неплохо разбираюсь в математике, математические выражения - это не то, как мой мозг понимает программирование. Я до сих пор не написал ни строчки кода на Scheme, Clojure, или Haskell. И конечно, я не олдскульный Лиспер.
13 |
14 | Но что я действительно *сделал* - так это посетил неисчислимое количество докладов о ФП на разнообразных конференциях, и на каждый из них я шел с надеждой, что вот уж *теперь-то* я наконец разберусь, в чем заключается мистицизм функционального программирования. И каждый раз я выходил разочарованным с неизменным ощущением того, что вся эта терминология перемешалась в моей голове в одну сплошную кашу. И я по-прежнему не имел представления, что же все это означает. Возможно, я и узнавал там новые вещи. Но ещё долгое время я не мог осознать их практическую пользу.
15 |
16 | Однако, мало-помалу кусочки пазла в моей голове начнали собираться в единую картину важных базовых концепций, которые выглядят такими естественными для любого настоящего функциональщика. Я познавал их медленно, через призму практики и экспериментов, а не формально и с использованием подобающей терминологии. Случалось ли вам столкнуться с тем, что то, чем вы давно и успешно пользуетесь оказывается имеет своё собственное название, о котором вы никогда не знали!?
17 |
18 | Если да, то, возможно, в этом вы похожи на меня; Я слышал слова вроде "map-reduce" в различных контекстах, особенно в отношении "больших данных" на протяжении лет, не имея ни малейшего представления о том, что они действительно означают. Со временем я узнал, что делает функция `map(..)` -- до того момента я и представить себе не мог, что операции над массивами являются краеугольным камнем на пути функциональщика, и это делает их невероятно важными. Фактически, я знал, что делает *map* задолго до того, как узнал о его правильном названии.
19 |
20 | Со временем, я начал собирать все эти фрагменты знаний в то, что позже назвал "Функционально-легким программированием" (ФЛП).
21 |
22 | ## Миссия
23 |
24 | Почему же знания о функциональном программировании так важны для среднего программиста, даже в подобной легкой форме?
25 |
26 | В последние годы, я пришел к убеждению, в которое поверил очень глубоко. Настолько глубоко, что оно стало для меня *сродни* религии. Я убежден, что программирование - это в отражение человека в целом, а не про набор знаков и символов, которые мы привыкли называть кодом. Код - в первую очередь форма коммуникации, а программа, и её работа внутри компьютера - лишь *сайдэффект* (саркастический смешок).
27 |
28 | На мой взгляд, основа фунцкионального программирования - использование паттернов, которые общеизвестны, понятны и доказанно свободны от ошибок, делающих код сложнее для понимания. В этом смысле, ФП -- или, хм, ФЛП! -- является одним из наиболее важных и полезных наборов инструментов, которым разработчик может научиться пользоваться.
29 |
30 | > Проклятье монады в том... что однажда поняв... ты теряешь способность объяснить кому-либо ещё.
31 | >
32 | > Дуглас Крокфорд 2012 "Монады и Гонады"
33 | >
34 | > https://www.youtube.com/watch?v=dkZFtimgAcM
35 |
36 | Я надеюсь, что эта книга, "Возможно" разобьет это проклятье, несмотря на то что мы не будем упоминать о монадах почти до самого конца книги.
37 |
38 | Настоящие функциональщики обычно утверждают, что *настоящая ценность*, не может быть достигнута, если вы не пишете на 100% функциональный код. Считается, что если вы используете ФП только в одной части программы, и не используете в другой - то вся ваша программа загрязнена сайдэффектами настолько, что вероятно не стоило и заморачиваться с ФП стилем вообще.
39 |
40 | Скажу однозначно: **Я думаю что подобные абсолютистские заявления - полная чушь**. Для меня это так же глупо, как говорить о том, что книга хороша только тогда, когда я использую совершенную грамматику и ритм повсюду; и если я где-то сделаю ошибку, это испортит всю книгу целиком. Ерунда.
41 |
42 | Конечно, чем лучше качество написания кода и его конститентность, тем проще его будет прочитать. Но проблема в том, что я - не идеальный автор. И некоторые части удаются мне лучше, чем другие. Но те части, которые требуют улучшения вовсе не обесценивают пользу от всей книги.
43 |
44 | То же справедливо и для кода. Чем больше и чаще вы будете применять данные принципе, тем лучше будет ваш код. Даже используя их только 25% времени вы получите ощутимую выгоду. Используйте их 80% времени, вы увидите что выгода выросла в разы.
45 |
46 | За, возможно, некоторыми исключениями, в этой книге вы не найдете утверждений, претендующих на асолютную истину. Вместо этого, мы поговорим о целях и принципах, к которым нужно стремиться. Мы поговорим о балансе, прагматизме и компромиссах.
47 |
48 | Добро пожаловать в это путешествие в самые полезные и практические основы ФП. Нам обоим есть чему поучиться!
49 |
--------------------------------------------------------------------------------