├── .DS_Store
├── .gitignore
├── CONTRIBUTING.md
├── FAQ.md
├── LICENSE.md
├── README.md
├── SUMMARY.md
├── TRANSLATIONS.md
├── ch1.md
├── ch10.md
├── ch2.md
├── ch3.md
├── ch4.md
├── ch5.md
├── ch6.md
├── ch7.md
├── ch8.md
├── ch9.md
├── code
├── lib
│ └── package.json
├── part1_demo
│ ├── flickr.html
│ └── flickr.js
├── part1_exercises
│ ├── README.md
│ ├── answers
│ │ ├── compose
│ │ │ ├── compose_exercises.js
│ │ │ └── compose_exercises_spec.js
│ │ └── curry
│ │ │ ├── curry_exercises.js
│ │ │ └── curry_exercises_spec.js
│ ├── exercises
│ │ ├── compose
│ │ │ ├── compose_exercises.js
│ │ │ └── compose_exercises_spec.js
│ │ └── curry
│ │ │ ├── curry_exercises.js
│ │ │ └── curry_exercises_spec.js
│ ├── package.json
│ └── support.js
└── part2_exercises
│ ├── README.md
│ ├── answers
│ ├── applicative
│ │ ├── applicative_exercises.js
│ │ └── applicative_exercises_spec.js
│ ├── functors
│ │ ├── functor_exercises.js
│ │ └── functor_exercises_spec.js
│ └── monads
│ │ ├── monad_exercises.js
│ │ └── monad_exercises_spec.js
│ ├── exercises
│ ├── applicative
│ │ ├── applicative_exercises.js
│ │ └── applicative_exercises_spec.js
│ ├── functors
│ │ ├── functor_exercises.js
│ │ └── functor_exercises_spec.js
│ └── monads
│ │ ├── monad_exercises.js
│ │ └── monad_exercises_spec.js
│ ├── package.json
│ └── support.js
├── cover.jpg
├── feedback_loop.md
└── images
├── canopener.jpg
├── cat.png
├── cat_comp1.png
├── cat_comp2.png
├── cat_theory.png
├── catmap copy.png
├── catmap.png
├── cats_ss.png
├── chain.jpg
├── console_ss.png
├── cover.png
├── dominoes.jpg
├── fists.jpg
├── fn_graph.png
├── function-sets.gif
├── functormap.png
├── functormapmaybe.png
├── jar.jpg
├── monad_associativity.png
├── onion.png
├── plugs.jpg
├── relation-not-function.gif
├── ship_in_a_bottle.jpg
└── triangle_identity.png
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | grunt
3 | less
4 | Gruntfile.js
5 | npm-debug.log
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributing to Mostly Adequate Guide to Funcitonal Programming
3 |
4 | ## Licensing
5 |
6 | By opening a pull request to this repository, you agree to provide your work under the [project license](LICENSE.md). Also, you agree to grant such license of your work as is required for the purposes of future print editions to @DrBoolean. Should your changes appear in a printed edition, you'll be included in the contributors list.
7 |
8 | ## Small Corrections
9 |
10 | Errata and basic clarifications will be accepted if we agree that they improve the content. You can also open an issue so we can figure out how or if it needs to be addressed. If you've never done this before, the [flow guide](https://guides.github.com/introduction/flow/) might be useful.
11 |
12 | ## Questions or Clarifications
13 |
14 | Please, have a look at the [FAQ](FAQ.md) before you open an issue. Your question may already
15 | have been answered. Should you still need to ask something? Feel free to open an issue and to
16 | explain yourself.
17 |
18 | ## Translations
19 |
20 | Translations to other languages are highly encouraged. Each official translation will be held as a separate repository in the [MostlyAdequate organization](https://github.com/MostlyAdequate) and linked from the English version book.
21 | Since each translation is a different repository, we can also have different maintainers for each project.
22 |
23 | ### Creating a new translation repo
24 |
25 | In order to create a new translation, you need to follow these steps:
26 |
27 | * Fork the [main repo](https://github.com/MostlyAdequate/mostly-adequate-guide).
28 | * Add yourself to the watch list of the main repo, to keep up with changes.
29 | * When translating chapters, **create NEW files with suffix of your language**.
30 | * For example, Spanish tranlation for `ch1.md` will be on `ch1-es.md`.
31 | * Open a [new issue](https://github.com/MostlyAdequate/mostly-adequate-guide/issues/new) and ask to be part of the organization.
32 | * Transfer the repo to the organization.
33 | * Merge new content from the main repo.
34 | * keep translating...
35 | * Rinse/repeat last too steps until the book is done.
36 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | ## FAQ
2 |
3 | - [Why are snippets written sometimes with semicolons and sometimes
4 | without?](#why-are-snippets-written-sometimes-with-semicolons-and-sometimes-without)
5 | - [Aren't external libraries like _ (ramda) or $ (jquery) making calls impure?](#arent-external-libraries-like-_-ramda-or--jquery-making-calls-impure)
6 | - [What is the meaning of `f a` in signature?](#what-is-the-meaning-of-f-a-in-signature)
7 | - [Is there any "real world" examples available?](#is-there-any-real-world-examples-available)
8 | - [Why does the book uses ES5? Is any ES6 version available?](#why-does-the-book-uses-es5-is-any-es6-version-available)
9 | - [What the heck is that reduce function about?](#what-the-heck-is-that-reduce-function-about)
10 | - [Wouldn't you use a simplified English rather than the current style?](#wouldnt-you-use-a-simplified-english-rather-than-the-current-style)
11 | - [What is Either? What is Future? What is Task?](#what-is-either-what-is-future-what-is-task)
12 | - [Where do map, filter, compose ... methods come from?](#where-do-map-filter-compose--methods-come-from)
13 |
14 | ### Why are snippets written sometimes with semicolons and sometimes without?
15 |
16 | > see [#6]
17 |
18 | There are two schools in JavaScript, people who use them, and people who don't. We've made the
19 | choice here to use them, and now, we strive to be consistent with that choice. If some are
20 | missing, please let us know and will take care of the oversight.
21 |
22 | ### Aren't external libraries like _ (ramda) or $ (jquery) making calls impure?
23 |
24 | > see [#50]
25 |
26 | Those dependencies are available as if they were in the global context, part of the language.
27 | So, no, calls can still be considered as pure.
28 | For further reading, have a look at [this article about
29 | CoEffects](http://tomasp.net/blog/2014/why-coeffects-matter/)
30 |
31 | ### What is the meaning of `f a` in signature?
32 |
33 | > see [#62]
34 |
35 | In a signature, like:
36 |
37 | `map :: Functor f => (a -> b) -> f a -> f b`
38 |
39 | `f` refers to a `functor` that could be for instance Maybe or IO. Thus, the signature abstracts
40 | the choice of that functor by using a type variable which basically means that any functor
41 | might be used where `f` stands as long as all `f` are of the same type (if the first `f a` in
42 | the signature represents a `Maybe a`, then the second one **cannot refer to** an `IO b` but
43 | should be a `Maybe b`. For instance:
44 |
45 | ```javascript
46 | let maybeString = Maybe.of("Patate")
47 | let f = function (x) { return x.length }
48 | let maybeNumber = map(f, maybeString) // Maybe(6)
49 |
50 | // With the following 'refined' signature:
51 | // map :: (string -> number) -> Maybe string -> Maybe number
52 | ```
53 |
54 | ### Is there any "real world" examples available?
55 |
56 | > see [#77], [#192]
57 |
58 | Should you haven't reached it yet, you may have a look at the [Chapter
59 | 6](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch6.md) which present a
60 | simple flickr application.
61 | Other examples are likely to come later on. By the way, feel free to share with us your
62 | experience!
63 |
64 | ### Why does the book uses ES5? Is any ES6 version available?
65 |
66 | > see [#83], [#235]
67 |
68 | The book aims at being widely accessible. It started before ES6 went out, and now, as the new
69 | standard is being more and more accepted, we are considering making two separated editions with
70 | ES5 and ES6. Members of the community are already working on the ES6 version (have a look to
71 | [#235] for more informations).
72 |
73 | ### What the heck is that reduce function about?
74 |
75 | > see [#109]
76 |
77 | Reduce, accumulate, fold, inject are all usual functions in functional programming used to
78 | combine the elements of a data structure successively. You might have a look at [this
79 | talk](https://www.youtube.com/watch?v=JZSoPZUoR58&ab_channel=NewCircleTraining) to get some
80 | more insights about the reduce function.
81 |
82 | ### Wouldn't you use a simplified English rather than the current style?
83 |
84 | > see [#176]
85 |
86 | The book is written in its own style which contributes to make it consistent as a whole. If
87 | you're not familiar with English, see it as a good training. Nevertheless, should you need help
88 | to understand the meaning sometimes, there are now [several
89 | translations](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/TRANSLATIONS.md)
90 | available that could probably help you.
91 |
92 | ### What is Either? What is Future? What is Task?
93 |
94 | > see [#194]
95 |
96 | We introduce all of those structure throughout the book. Therefore, you won't find any use of a
97 | structure that hasn't previously be defined. Do not hesitate to read again some old parts if
98 | you ever feel uncomfortable with those types.
99 | A glossary/vade mecum will come at the end to synthesize all these notions.
100 |
101 | ### Where do map, filter, compose ... methods come from?
102 |
103 | > see [#198]
104 |
105 | Most of the time, those methods are defined in specific vendor libraries such as `ramda` or
106 | `underscore`. You should also have a look to
107 | [support.js](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/code%2Fpart1_exercises%2Fsupport.js)
108 | in which we define several implementations used for the exercises. Those functions are really
109 | common in functional programming and even though their implementations may vary a bit, their
110 | meanings remain fairly consistent between libraries.
111 |
112 |
113 | [#6]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/6
114 | [#50]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/50
115 | [#62]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/62
116 | [#77]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/77
117 | [#83]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/83
118 | [#109]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/109
119 | [#176]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/176
120 | [#192]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/192
121 | [#194]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/194
122 | [#198]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/198
123 | [#235]: https://github.com/MostlyAdequate/mostly-adequate-guide/pull/235
124 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | * All text in this book is under:
2 | Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
3 | http://creativecommons.org/licenses/by-sa/4.0/
4 |
5 | * All artwork randomly stolen from google image search so the license doesn't apply. Please let me know if any artwork is yours and I'll give credit or remove.
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](SUMMARY.md)
2 |
3 | > This is the Traditional Chinese translation of *[mostly-adequate-guide](https://github.com/DrBoolean/mostly-adequate-guide)*, thank Professor [Franklin Risby](https://github.com/DrBoolean) for his great work!
4 |
5 | > 本文中文翻譯參考了 [JS函数式编程指南中文版](https://github.com/llh911001/mostly-adequate-guide-chinese),若你喜歡本書也歡迎給該 Repo 一顆 star!
6 |
7 | ## 關於本書
8 |
9 | 此書主題為 functional 模式。我們將使用世界上最熱門的 functional programming 語言:JavaScript 來講述此主題。有些人可以認為這個選擇並不明智,因為目前的主流觀點認為他是一個命令式(imperative)的語言,並不適合用來講述 functional。但我認為這是學習 FP 最好的方式,有幾點原因:
10 |
11 | * **你可能每天都在工作中使用到它。**
12 |
13 | 這讓你有機會在實際的程式開發過程中學以致用,而不是在空閒時間將一門深奧的 FP 語言用在玩票性質的專案上。
14 |
15 |
16 | * **我們不必從頭學習所有東西就能開始撰寫程式。**
17 |
18 | 在一個純 functional 的語言中,你必須使用 monads 才能印出變數或讀取 DOM 節點。JavaScript 則簡單多了,可以作弊走捷徑。JavaScript 也更容易入門,因為他是一門混合模式的語言,你可以隨時在感到吃力之時回去按你原有的習慣開發。
19 |
20 |
21 | * **這門語言完全有能力撰寫高級的 functional 程式碼。**
22 |
23 | 只需藉助一兩個小型的 library 就能幫助你模擬 Scala 或 Haskell 這類語言的所有特性。雖然物件導向程式設計(Object-oriented programing)主導著業界,但很明顯這種模式在 JavaScript 中相當的笨重,用起來像在高速公路上露營或像穿著橡膠鞋跳著踢踏舞一般。我們不得不到處使用 `bind` 以避免 `this` 在不知不覺中改變,語言中也沒有 class 可用(目前),我們還發明了各種變通的方式來避免忘記 `new` 關鍵字的怪異行為,private 成員目前只能透過閉包(closure)實現。對大多數人來說,FP 感覺起來反而更加自然。
24 |
25 | 以上說明,強型別的 functional 語言毫無疑問將會是本書所提供程式類型的最佳實驗場所。JavaScript 會是我們學習這種模式的手段之一,將它運用在何處則完全取決於你。最後你會發現你習慣了 swiftz、scalaz、haskell、purescript 及其他數學導向的語言。
26 |
27 |
28 | ### Gitbook(較佳的閱讀體驗)
29 |
30 | * [線上閱讀](https://drboolean.gitbooks.io/mostly-adequate-guide/content/)
31 | * [Download EPUB](https://www.gitbook.com/download/epub/book/drboolean/mostly-adequate-guide)
32 | * [Download Mobi (Kindle)](https://www.gitbook.com/download/mobi/book/drboolean/mostly-adequate-guide)
33 |
34 | ### 自己做
35 |
36 | ```
37 | git clone https://github.com/jigsawye/mostly-adequate-guide.git
38 |
39 | cd mostly-adequate-guide/
40 | npm install gitbook-cli -g
41 | gitbook init
42 |
43 | brew update
44 | brew install Caskroom/cask/calibre
45 |
46 | gitbook mobi . ./functional.mobi
47 | ```
48 |
49 |
50 | # 目錄
51 |
52 | 請見 [SUMMARY.md](SUMMARY.md)
53 |
54 | ### 貢獻
55 |
56 | 請見 [CONTRIBUTING.md](CONTRIBUTING.md)
57 |
58 | ### 翻譯
59 |
60 | 請見 [TRANSLATIONS.md](TRANSLATIONS.md)
61 |
62 | ### FAQ
63 |
64 | 請見 [FAQ.md](FAQ.md)
65 |
66 |
67 |
68 | # 未來計劃
69 |
70 | * **第 1 部分**(目前的章節 1 至 7)是指南的基礎知識。這是初版草稿,我會在找到錯誤時及時更正。歡迎提供幫助!
71 | * **第 2 部分**(目前的章節 8+)會講述 class 型別,像是 functors 及 monads,最後會帶到 traversable。我希望能塞一些 transformers 及一個純的應用程式。
72 | * **第 3 部分**會開始遊走於程式開發實踐與學術研究之間。我們將學習 comonad、f-algebra、free monad、yoneda 及其他類型的結構。
73 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [第 1 章:我們在做些什麼?](ch1.md)
4 | * [簡介](ch1.md#introductions)
5 | * [相見恨晚](ch1.md#a-brief-encounter)
6 | * [第 2 章:First Class Funtcion](ch2.md)
7 | * [快速導覽](ch2.md#a-quick-review)
8 | * [為何鍾愛一等公民?](ch2.md#why-favor-first-class)
9 | * [第 3 章:Pure Function-單純的幸福](ch3.md)
10 | * [再次強調「Pure」](ch3.md#oh-to-be-pure-again)
11 | * [副作用可能包含⋯](ch3.md#side-effects-may-include)
12 | * [八年級數學](ch3.md#8th-grade-math)
13 | * [追求「Pure」的理由](ch3.md#the-case-for-purity)
14 | * [總結](ch3.md#in-summary)
15 | * [第 4 章:Curry(柯里化)](ch4.md)
16 | * [Curry 的重要性](ch4.md#cant-live-if-livin-is-without-you)
17 | * [不只是雙關語 / 咖哩](ch4.md#more-than-a-pun--special-sauce)
18 | * [總結](ch4.md#in-summary)
19 | * [第 5 章:透過 Compose 開發](ch5.md)
20 | * [Functional 飼養](ch5.md#functional-husbandry)
21 | * [Pointfree](ch5.md#pointfree)
22 | * [Debug](ch5.md#debugging)
23 | * [範疇論](ch5.md#category-theory)
24 | * [總結](ch5.md#in-summary)
25 | * [第 6 章:範例應用程式](ch6.md)
26 | * [宣告式開發](ch6.md#declarative-coding)
27 | * [一個 FP 的 flickr](ch6.md#a-flickr-of-functional-programming)
28 | * [有原則的重構](ch6.md#a-principled-refactor)
29 | * [總結](ch6.md#in-summary)
30 | * [第 7 章:Hindley-Milner 與我](ch7.md)
31 | * [你的類型?](ch7.md#whats-your-type)
32 | * [神祕的傳奇故事](ch7.md#tales-from-the-cryptic)
33 | * [縮小可能性範圍](ch7.md#narrowing-the-possibility)
34 | * [自由定理](ch7.md#free-as-in-theorem)
35 | * [總結](ch7.md#in-summary)
36 | * [第 8 章:Tupperware](ch8.md)
37 | * [強大的 Container](ch8.md#the-mighty-container)
38 | * [第一個 Functor](ch8.md#my-first-functor)
39 | * [薛丁格的 Maybe](ch8.md#schrodingers-maybe)
40 | * [Pure Error 處理](ch8.md#pure-error-handling)
41 | * [王老先生有作用…](ch8.md#old-mcdonald-had-effects)
42 | * [非同步任務](ch8.md#asynchronous-tasks)
43 | * [一點理論](ch8.md#a-spot-of-theory)
44 | * [總結](ch8.md#in-summary)
45 | * [第 9 章:Monadic Onions](ch9.md)
46 | * [Pointed Functor](ch9.md#pointy-functor-factory)
47 | * [混合比喻](ch9.md#mixing-metaphors)
48 | * [chain](ch9.md#my-chain-hits-my-chest)
49 | * [理論](ch9.md#theory)
50 | * [總結](ch9.md#in-summary)
51 | * [第 10 章:Applicative Functor](ch10.md)
52 | * [套用 Applicative Functor](ch10.md#applying-applicatives)
53 | * [瓶中船](ch10.md#ships-in-bottles)
54 | * [Coordination Motivation](ch10.md#coordination-motivation)
55 | * [lift](ch10.md#bro-do-you-even-lift)
56 | * [免費開瓶器](ch10.md#free-can-openers)
57 | * [定律](ch10.md#laws)
58 | * [總結](ch10.md#in-summary)
59 |
--------------------------------------------------------------------------------
/TRANSLATIONS.md:
--------------------------------------------------------------------------------
1 | # Available Translations
2 |
3 | * [中文版 (Chinese)](https://github.com/llh911001/mostly-adequate-guide-chinese) by Linghao Li @llh911001
4 | * [Русский (Russian)](https://github.com/MostlyAdequate/mostly-adequate-guide-ru) by Maksim Filippov @maksimf
5 | * [Français (French)](https://github.com/MostlyAdequate/mostly-adequate-guide-fr) by Benkort Matthias @KtorZ
6 | * [Português (Portuguese)](https://github.com/MostlyAdequate/mostly-adequate-guide-pt-BR) by Palmer Oliveira @expalmer
7 | * [Español (Spanish)](https://github.com/MostlyAdequate/mostly-adequate-guide-es) by Gustavo Marin @guumaster
8 | * [Italiano (Italian)](https://github.com/MostlyAdequate/mostly-adequate-guide-it) by Elia Gentili @eliagentili
9 |
10 | ## Creating new Translations
11 |
12 | See [Creating new translations](CONTRIBUTING.md#Translations)
13 |
--------------------------------------------------------------------------------
/ch1.md:
--------------------------------------------------------------------------------
1 | # 第 1 章:我們在做些什麼?
2 |
3 | ## 簡介
4 |
5 | 嗨!我是 Franklin Risby 教授,很高興認識你。接下來我們將共度一段時光,因為我要教你一些 functional programming 的知識。關於我就到此為止,那麼你呢?我希望你已經熟悉了 JavaScript 語言,關於物件導向也有一些經驗,而且自認為是一名合格的程式設計師。你並不需要擁有昆蟲學博士學位,你只需要知道如何找到並殺死一些 bugs。
6 |
7 | 我不假設你之前有任何 functional programming 的知識,我們都知道假設的後果是什麼。但我猜想你在使用可變狀態(mutable state)、不受限的副作用(unrestricted side effects)及無原則設計(unprincipled design)的過程中已經遇過一些麻煩。現在我們已經介紹的差不多了,接著讓我們切入正題吧。
8 |
9 | 本章節的目的是讓你了解為何我們要撰寫 functional 的程式。我們必須了解如何讓程式 *functional*,否則我們會發現自己漫無目的的避免使用物件-無疑是在做白工。寫程式時需要遵照一定的原則,就像在《激戰》遊戲中當水變成石頭時你需要天國羅盤來指引。
10 |
11 | 現在已經有一些通用的程式開發原則-各種縮寫詞帶領我們在應用程式的黑暗隧道中前進:DRY(don't repeat yourself,不重複程式),YAGNI(ya ain't gonna need it,你不會需要它),高內聚低耦合(loose coupling high cohesion),最少意外原則(principle of least surprise),單一責任原則(single responsibility)等等。
12 |
13 | 我當然不會囉唆的把這些年我所聽到的原則都列舉出來⋯重點是這些原則都適用於 functional 的情況,只是它們與我們的目的不太有關係。在我們深入主題前,我想先讓你有一種感覺,當你在敲打鍵盤時內心就能強烈感受到 functional 的氛圍。
14 |
15 |
16 |
17 | ## 相見恨晚
18 |
19 | 讓我們以一個簡單的例子開始。這是一個海鷗(seagull)的應用程式。當鳥群合併(conjoin)後牠們會變成更大的鳥群,繁殖(breed)則會增加他們的數量,所增加的數目為他們自身所繁殖出的數量。目前這不是物件導向的良好實踐,只是為了強調這種賦值方式所造成的弊端。看看吧:
20 |
21 | ```js
22 | var Flock = function(n) {
23 | this.seagulls = n;
24 | };
25 |
26 | Flock.prototype.conjoin = function(other) {
27 | this.seagulls += other.seagulls;
28 | return this;
29 | };
30 |
31 | Flock.prototype.breed = function(other) {
32 | this.seagulls = this.seagulls * other.seagulls;
33 | return this;
34 | };
35 |
36 | var flock_a = new Flock(4);
37 | var flock_b = new Flock(2);
38 | var flock_c = new Flock(0);
39 |
40 | var result = flock_a.conjoin(flock_c)
41 | .breed(flock_b).conjoin(flock_a.breed(flock_b)).seagulls;
42 | //=> 32
43 | ```
44 |
45 | 誰的手法會寫出這麼的令人退步三舍的程式?內部的可變狀態相當的難以追蹤,而且我的天啊,答案居然還是錯的!正確答案應該是 `16`,但是因為 `flock_a` 在計算過程中被永久改變了,可憐的 `flock_a`。這是 I.T. 部門混亂的表現,非常粗暴的計算方式!
46 |
47 | 如果你看不懂這支程式,是沒關係的,因為我也看不懂。重點是狀態及可變值非常難追蹤,即使這是一個小小的範例。
48 |
49 | 讓我們試試另一種更 functional 的方式:
50 |
51 | ```js
52 | var conjoin = function(flock_x, flock_y) { return flock_x + flock_y; };
53 | var breed = function(flock_x, flock_y) { return flock_x * flock_y; };
54 |
55 | var flock_a = 4;
56 | var flock_b = 2;
57 | var flock_c = 0;
58 |
59 | var result = conjoin(
60 | breed(flock_b, conjoin(flock_a, flock_c)), breed(flock_a, flock_b)
61 | );
62 | //=>16
63 | ```
64 |
65 | 很好,這次我們得到正確的答案。而且少了很多程式碼。不過巢狀 function 有點讓人難以理解⋯(我們會在第 5 章解決這個情形)。這種寫法更好,不過程式碼肯定是越直白越好,所以讓我們更深入探討。瞭解之後,我們會發現我們只是很簡單的進行相加(`conjoin`)及相乘(`breed`)。
66 |
67 | 除了兩個 function 的名稱比較特殊外,其他沒有任何難以理解之處。讓我們重新命名這些 function 來揭曉它們的真面目。
68 |
69 | ```js
70 | var add = function(x, y) { return x + y; };
71 | var multiply = function(x, y) { return x * y; };
72 |
73 | var flock_a = 4;
74 | var flock_b = 2;
75 | var flock_c = 0;
76 |
77 | var result = add(
78 | multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b)
79 | );
80 | //=>16
81 | ```
82 | 這麼一來,我們會發現這些只是古人所流傳下來的知識:
83 |
84 | ```js
85 | // 結合率(associative)
86 | add(add(x, y), z) === add(x, add(y, z));
87 |
88 | // 交換律(commutative)
89 | add(x, y) === add(y, x);
90 |
91 | // 同一律(identity)
92 | add(x, 0) === x;
93 |
94 | // 分配律(distributive)
95 | multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));
96 | ```
97 |
98 | 是的,這些古老又堅定的數學特性早晚會派上用場。若你一時想不起來也沒關係,大多數的人已經很久沒複習這些資訊了。讓我們看看是否能利用這些定律簡化這個海鷗程式。
99 |
100 | ```js
101 | // 原有程式碼
102 | add(multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b));
103 |
104 | // 套用同一律來移除多餘的相加 (add(flock_a, flock_c) == flock_a)
105 | add(multiply(flock_b, flock_a), multiply(flock_a, flock_b));
106 |
107 | // 套用分配律來達到我們的結果
108 | multiply(flock_b, add(flock_a, flock_a));
109 | ```
110 |
111 | 漂亮!除了呼叫的 function 外我們不必再多寫多餘的程式碼。我們定義了 `add` 及 `multiply` 只是為了完整性,但實際上我們並不需要撰寫-在某些已經寫好的 library 裡一定包含了 `add` 及 `multiply`。
112 |
113 | 你可能在想「你前面舉這種助學例子也太曲解論點了吧」。或者「真實的程式才不會這麼簡單,不能以這樣的方式來推斷」。我會選擇這個例子是因為大部分的人已經知道如何相加及相乘,所以這很容易讓我們發現可以在這裡使用數學。
114 |
115 | 別感到絕望-在本書中還會穿插一些範疇論(category theory)、集合論(set theory)及 lamdba 運算的知識,教你著寫更複雜的程式碼,而且一點也不輸本章海鷗程式的簡潔性與準確性。你也不需成為一名數學家,本書要交給你的程式設計模式實踐起來,就像是使用一個普通的框架或 API 一般。
116 |
117 | 你也許會感到訝異,我們可以向上例那樣遵循 functional 的模式去撰寫完整且日常的應用程式,有著優異效能的程式、簡潔易推理的程式,以及不用每次都重新造輪子的程式。如果你是罪犯,那違法對你來說是件好事;但在本書中,我們希望能夠承認並遵守數學之法。
118 |
119 | 我們希望去實踐每一部份都能完美結合的理論,希望能以一種通用、可組合的元件來描述我們的特定問題,然後利用這些元件的特性來解決這些問題。對於 imperative programming(指令式程式開發,稍後本書將會介紹 imperative 的經確定義,我們暫時還是先將重點放在 functional 上)那種「某某去做某事」的方式,functional programming 將會有更多的約束,不過你會震攝於這種強約束、數學性的框架所帶來的回報。
120 |
121 | 我們已經看到 functional 的點點星光了,但在真正開始我們的旅程之前,必須先掌握一些具體的概念。
122 |
123 | [第 2 章:First Class Function](ch2.md)
124 |
--------------------------------------------------------------------------------
/ch10.md:
--------------------------------------------------------------------------------
1 | # Chapter 10: Applicative Functors
2 |
3 | ## Applying Applicatives
4 |
5 | The name **applicative functor** is pleasantly descriptive given its functional origins. Functional programmers are notorious for coming up with names like `mappend` or `liftA4`, which seem perfectly natural when viewed in the math lab, but hold the clarity of an indecisive darth vader at the drive thru in any other context.
6 |
7 | Anyhow, the name should spill the beans on what this interface gives us: *the ability to apply functors to each other*.
8 |
9 | Now, why would a normal, rational person, such as yourself, want such a thing? What does it even *mean* to apply one functor to another?
10 |
11 | To answer these questions, we'll start with a situation you may have already encountered in your functional travels. Let's say, hypothetically, that we have two functors (of the same type) and we'd like to call a function with both of their values as arguments. Something simple like adding the values of two `Container`s.
12 |
13 | ```js
14 | // we can't do this because the numbers are bottled up.
15 | add(Container.of(2), Container.of(3));
16 | //NaN
17 |
18 | // Let's use our trusty map
19 | var container_of_add_2 = map(add, Container.of(2));
20 | // Container(add(2))
21 | ```
22 |
23 | We have ourselves a `Container` with a partially applied function inside. More specifically, we have a `Container(add(2))` and we'd like to apply its `add(2)` to the `3` in `Container(3)` to complete the call. In other words, we'd like to apply one functor to another.
24 |
25 | Now, it just so happens that we already have the tools to accomplish this task. We can `chain` and then `map` the partially applied `add(2)` like so:
26 |
27 | ```js
28 | Container.of(2).chain(function(two) {
29 | return Container.of(3).map(add(two));
30 | });
31 | ```
32 |
33 | The issue here is that we are stuck in the sequential world of monads wherein nothing may be evaluated until the previous monad has finished its business. We have ourselves two strong, independent values and I should think it unnecessary to delay the creation of `Container(3)` merely to satisfy the monad's sequential demands.
34 |
35 | In fact, it would be lovely if we could succinctly apply one functor's contents to another's value without these needless functions and variables should we find ourselves in this pickle jar.
36 |
37 |
38 | ## Ships in bottles
39 |
40 |
41 |
42 | `ap` is a function that can apply the function contents of one functor to the value contents of another. Say that 5 times fast.
43 |
44 | ```js
45 | Container.of(add(2)).ap(Container.of(3));
46 | // Container(5)
47 |
48 | // all together now
49 | Container.of(2).map(add).ap(Container.of(3));
50 | // Container(5)
51 | ```
52 |
53 | There we are, nice and neat. Good news for `Container(3)` as it's been set free from the jail of the nested monadic function. It's worth mentioning again that `add`, in this case, gets partially applied during the first `map` so this only works when `add` is curried.
54 |
55 | We can define `ap` like so:
56 |
57 | ```js
58 | Container.prototype.ap = function(other_container) {
59 | return other_container.map(this.__value);
60 | }
61 | ```
62 |
63 | Remember, `this.__value` will be a function and we'll be accepting another functor so we need only `map` it. And with that we have our interface definition:
64 |
65 |
66 | > An *applicative functor* is a pointed functor with an `ap` method
67 |
68 | Note the dependence on **pointed**. The pointed interface is crucial here as we'll see throughout the following examples.
69 |
70 | Now, I sense your skepticism (or perhaps confusion and horror), but keep an open mind; this `ap` character will prove useful. Before we get into it, let's explore a nice property.
71 |
72 | ```js
73 | F.of(x).map(f) == F.of(f).ap(F.of(x))
74 | ```
75 |
76 | In proper English, mapping `f` is equivalent to `ap`ing a functor of `f`. Or in properer English, we can place `x` into our container and `map(f)` OR we can lift both `f` and `x` into our container and `ap` them. This allows us to write in a left-to-right fashion:
77 |
78 | ```js
79 | Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
80 | // Maybe(5)
81 |
82 | Task.of(add).ap(Task.of(2)).ap(Task.of(3));
83 | // Task(5)
84 | ```
85 |
86 | One might even recognise the vague shape of a normal function call if viewed mid squint. We'll look at the pointfree version later in the chapter, but for now, this is the preferred way to write such code. Using `of`, each value gets transported to the magical land of containers, this parallel universe where each application can be async or null or what have you and `ap` will apply functions within this fantastical place. It's like building a ship in a bottle.
87 |
88 | Did you see there? We used `Task` in our example. This is a prime situation where applicative functors pull their weight. Let's look at a more in-depth example.
89 |
90 | ## Coordination Motivation
91 |
92 | Say we're building a travel site and we'd like to retrieve both a list of tourist destinations and local events. Each of these are separate, stand-alone api calls.
93 |
94 | ```js
95 | // Http.get :: String -> Task Error HTML
96 |
97 | var renderPage = curry(function(destinations, events) { /* render page */ });
98 |
99 | Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'))
100 | // Task("
some page with dest and events
")
101 | ```
102 |
103 | Both `Http` calls will happen instantly and `renderPage` will be called when both are resolved. Contrast this with the monadic version where one `Task` must finish before the next fires off. Since we don't need the destinations to retrieve events, we are free from sequential evaluation.
104 |
105 | Again, because we're using partial application to achieve this result, we must ensure `renderPage` is curried or it will not wait for both `Tasks` to finish. Incidentally, if you've ever had to do such a thing manually, you'll appreciate the astonishing simplicity of this interface. This is the kind of beautiful code that takes us one step closer to the singularity.
106 |
107 | Let's look at another example.
108 |
109 | ```js
110 | // Helpers:
111 | // ==============
112 | // $ :: String -> IO DOM
113 | var $ = function(selector) {
114 | return new IO(function() { return document.querySelector(selector)});
115 | }
116 |
117 | // getVal :: String -> IO String
118 | var getVal = compose(map(_.prop('value')), $);
119 |
120 | // Example:
121 | // ===============
122 | // signIn :: String -> String -> Bool -> User
123 | var signIn = curry(function(username, password, remember_me) { /* signing in */ })
124 |
125 | IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false));
126 | // IO({id: 3, email: "gg@allin.com"})
127 | ```
128 |
129 | `signIn` is a curried function of 3 arguments so we have to `ap` accordingly. With each `ap`, `signIn` receives one more argument until it is complete and runs. We can continue this pattern with as many arguments as necessary. Another thing to note is that two arguments end up naturally in `IO` whereas the last one needs a little help from `of` to lift it into `IO` since `ap` expects the function and all its arguments to be in the same type.
130 |
131 | ## Bro, do you even lift?
132 |
133 | Let's examine a pointfree way to write these applicative calls. Since we know `map` is equal to `of/ap`, we can write generic functions that will `ap` as many times as we specify:
134 |
135 | ```js
136 | var liftA2 = curry(function(f, functor1, functor2) {
137 | return functor1.map(f).ap(functor2);
138 | });
139 |
140 | var liftA3 = curry(function(f, functor1, functor2, functor3) {
141 | return functor1.map(f).ap(functor2).ap(functor3);
142 | });
143 |
144 | //liftA4, etc
145 | ```
146 |
147 | `liftA2` is a strange name. It sounds like one of the finicky freight elevators in a rundown factory or a vanity plate for a cheap limo company. Once enlightened, however, it's self explanatory: lift these pieces into the applicative functor world.
148 |
149 | When I first saw this 2-3-4 nonsense it struck me as ugly and unnecessary, after all, we can check arity of functions in JavaScript and build this up dynamically. However, it is often useful to partially apply `liftA(N)` itself, so it cannot vary in argument length.
150 |
151 | Let's see this in use:
152 |
153 | ```js
154 | // checkEmail :: User -> Either String Email
155 | // checkName :: User -> Either String String
156 |
157 | // createUser :: Email -> String -> IO User
158 | var createUser = curry(function(email, name) { /* creating... */ });
159 |
160 | Either.of(createUser).ap(checkEmail(user)).ap(checkName(user));
161 | // Left("invalid email")
162 |
163 | liftA2(createUser, checkEmail(user), checkName(user));
164 | // Left("invalid email")
165 | ```
166 |
167 | Since `createUser` takes two arguments, we use the corresponding `liftA2`. The two statements are equivalent, but the `liftA2` version has no mention of `Either`. This makes it more generic and flexible since we are no longer married to a specific type.
168 |
169 |
170 | Let's see the previous examples written this way:
171 |
172 | ```js
173 | liftA2(add, Maybe.of(2), Maybe.of(3));
174 | // Maybe(5)
175 |
176 | liftA2(renderPage, Http.get('/destinations'), Http.get('/events'))
177 | // Task("some page with dest and events
")
178 |
179 | liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false));
180 | // IO({id: 3, email: "gg@allin.com"})
181 | ```
182 |
183 |
184 | ## Operators
185 |
186 | In languages like haskell, scala, PureScript, and swift, where it is possible to create your own infix operators you may see syntax like this:
187 |
188 | ```hs
189 | -- haskell
190 | add <$> Right 2 <*> Right 3
191 | ```
192 |
193 | ```js
194 | // JavaScript
195 | map(add, Right.of(2)).ap(Right.of(3))
196 | ```
197 |
198 | It's helpful to know that `<$>` is `map` (aka `fmap`) and `<*>` is just `ap`. This allows for a more natural function application style and can help remove some parenthesis.
199 |
200 | # Free can openers
201 |
202 |
203 | We haven't spoken much about derived functions. Seeing as all of these interfaces are built off of each other and obey a set of laws, we can define some weaker interfaces in terms of the stronger ones.
204 |
205 | For instance, we know that an applicative is first a functor, so if we have an applicative instance, surely we can define a functor for our type.
206 |
207 | This kind of perfect computational harmony is possible because we're working within a mathematical framework. Mozart couldn't have done better even if he had torrented ableton as a child.
208 |
209 | I mentioned earlier that `of/ap` is equivalent to `map`. We can use this knowledge to define `map` for free:
210 |
211 | ```js
212 | // map derived from of/ap
213 | X.prototype.map = function(f) {
214 | return this.constructor.of(f).ap(this);
215 | }
216 | ```
217 |
218 | Monads are at the top of the food chain, so to speak, so if we have `chain`, we get functor and applicative for free:
219 |
220 | ```js
221 | // map derived from chain
222 | X.prototype.map = function(f) {
223 | var m = this;
224 | return m.chain(function(a) {
225 | return m.constructor.of(f(a));
226 | });
227 | }
228 |
229 | // ap derived from chain/map
230 | X.prototype.ap = function(other) {
231 | return this.chain(function(f) {
232 | return other.map(f);
233 | });
234 | };
235 | ```
236 |
237 | If we can define a monad, we can define both the applicative and functor interfaces. This is quite remarkable as we get all of these can openers for free. We can even examine a type and automate this process.
238 |
239 | It should be pointed out that part of `ap`'s appeal is the ability to run things concurrently so defining it via `chain` is missing out on that optimization. Despite that, it's good to have an immediate working interface while one works out the best possible implementation.
240 |
241 | Why not just use monads and be done with it, you ask? It's good practice to work with the level of power you need, no more, no less. This keeps cognitive load to a minimum by ruling out possible functionality. For this reason, it's good to favor applicatives over monads.
242 |
243 | Monads have the unique ability to sequence computation, assign variables, and halt further execution all thanks to the downward nesting structure. When one sees applicatives in use, they needn't concern themselves with any of that business.
244 |
245 | Now, on to the legalities...
246 |
247 | # Laws
248 |
249 | Like the other mathematical constructs we've explored, applicative functors hold some useful properties for us to rely on in our daily code. First off, you should know that applicatives are "closed under composition", meaning `ap` will never change container types on us(yet another reason to favor over monads). That's not to say we cannot have multiple different effects - we can stack our types knowing that they will remain the same during the entirety of our application.
250 |
251 | To demonstrate:
252 |
253 | ```js
254 | var tOfM = compose(Task.of, Maybe.of);
255 |
256 | liftA2(liftA2(_.concat), tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
257 | // Task(Maybe('Rainy Days and Mondays always get me down'))
258 | ```
259 |
260 | See, no need to worry about different types getting in the mix.
261 |
262 | Time to look at our favorite categorical law: *identity*:
263 |
264 | ### Identity
265 |
266 | ```js
267 | // identity
268 | A.of(id).ap(v) == v
269 | ```
270 |
271 | Right, so applying `id` all from within a functor shouldn't alter the value in `v`. For example:
272 |
273 | ```js
274 | var v = Identity.of("Pillow Pets");
275 | Identity.of(id).ap(v) == v
276 | ```
277 | `Identity.of(id)` makes me chuckle at its futility. Anyway, what's interesting is that, as we've already established, `of/ap` is the same as `map` so this law follows directly from functor identity: `map(id) == id`.
278 |
279 | The beauty in using these laws is that, like a militant kindergarten gym coach, they force all of our interfaces to play well together.
280 |
281 | ### Homomorphism
282 |
283 | ```js
284 | // homomorphism
285 | A.of(f).ap(A.of(x)) == A.of(f(x))
286 | ```
287 |
288 | A *homomorphism* is just a structure preserving map. In fact, a functor is just a *homomorphism* between categories as it preserves the original category's structure under the mapping.
289 |
290 |
291 |
292 | We're really just stuffing our normal functions and values into a container and running the computation in there so it should come as no surprise that we will end up with the same result if we apply the whole thing inside the container (left side of the equation) or apply it outside, then place it in there (right side).
293 |
294 | A quick example:
295 |
296 | ```js
297 | Either.of(_.toUpper).ap(Either.of("oreos")) == Either.of(_.toUpper("oreos"))
298 | ```
299 |
300 | ### Interchange
301 |
302 | The *interchange* states that it doesn't matter if we choose to lift our function into the left or right side of `ap`.
303 |
304 | ```js
305 | // interchange
306 | v.ap(A.of(x)) == A.of(function(f) { return f(x) }).ap(v)
307 | ```
308 |
309 | Here is an example:
310 |
311 | ```js
312 | var v = Task.of(_.reverse);
313 | var x = 'Sparklehorse';
314 |
315 | v.ap(Task.of(x)) == Task.of(function(f) { return f(x) }).ap(v)
316 | ```
317 |
318 | ### Composition
319 |
320 | And finally composition which is just a way to check that our standard function composition holds when applying inside of containers.
321 |
322 | ```js
323 | // composition
324 | A.of(compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w));
325 | ```
326 |
327 | ```js
328 | var u = IO.of(_.toUpper);
329 | var v = IO.of(_.concat("& beyond"));
330 | var w = IO.of("blood bath ");
331 |
332 | IO.of(_.compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w))
333 | ```
334 |
335 | ## In Summary
336 |
337 | A good use case for applicatives is when one has multiple functor arguments. They give us the ability to apply functions to arguments all within the functor world. Though we could already do so with monads, we should prefer applicative functors when we aren't in need of monadic specific functionality.
338 |
339 | We're almost finished with container APIs. We've learned how to `map`, `chain`, and now `ap` functions. In the next chapter, we'll learn how to work better with multiple functors and disassemble them in a principled way.
340 |
341 | [Chapter 11: Traversable/Foldable Functors](ch11.md)
342 |
343 |
344 |
345 | ## Exercises
346 |
347 | ```js
348 | require('./support');
349 | var Task = require('data.task');
350 | var _ = require('ramda');
351 |
352 | // fib browser for test
353 | var localStorage = {};
354 |
355 |
356 |
357 | // Exercise 1
358 | // ==========
359 | // Write a function that adds two possibly null numbers together using Maybe and ap().
360 |
361 | // ex1 :: Number -> Number -> Maybe Number
362 | var ex1 = function(x, y) {
363 | // write me
364 | };
365 |
366 |
367 | // Exercise 2
368 | // ==========
369 | // Now write a function that takes 2 Maybe's and adds them. Use liftA2 instead of ap().
370 |
371 | // ex2 :: Maybe Number -> Maybe Number -> Maybe Number
372 | var ex2 = undefined;
373 |
374 |
375 |
376 | // Exercise 3
377 | // ==========
378 | // Run both getPost(n) and getComments(n) then render the page with both. (The n arg is arbitrary.)
379 | var makeComments = _.reduce(function(acc, c) { return acc+""+c+"" }, "");
380 | var render = _.curry(function(p, cs) { return ""+p.title+"
"+makeComments(cs); });
381 |
382 | // ex3 :: Task Error HTML
383 | var ex3 = undefined;
384 |
385 |
386 |
387 | // Exercise 4
388 | // ==========
389 | // Write an IO that gets both player1 and player2 from the cache and starts the game.
390 | localStorage.player1 = "toby";
391 | localStorage.player2 = "sally";
392 |
393 | var getCache = function(x) {
394 | return new IO(function() { return localStorage[x]; });
395 | };
396 | var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; });
397 |
398 | // ex4 :: IO String
399 | var ex4 = undefined;
400 |
401 |
402 |
403 |
404 |
405 | // TEST HELPERS
406 | // =====================
407 |
408 | function getPost(i) {
409 | return new Task(function (rej, res) {
410 | setTimeout(function () { res({ id: i, title: 'Love them futures' }); }, 300);
411 | });
412 | }
413 |
414 | function getComments(i) {
415 | return new Task(function (rej, res) {
416 | setTimeout(function () {
417 | res(["This book should be illegal", "Monads are like space burritos"]);
418 | }, 300);
419 | });
420 | }
421 | ```
422 |
--------------------------------------------------------------------------------
/ch2.md:
--------------------------------------------------------------------------------
1 | # 第 2 章:First Class Funtcion
2 |
3 | ## 快速導覽
4 | 當我們說 function 是「First Class(頭等)」時,意指他們就像其他人一樣⋯所以就是 Normal Class(坐經濟艙的人?)。function 真的沒什麼特別的地方,你可以像對待其他資料型別一樣對待它們-他們可以被存在陣列中,當作參數傳遞,賦予至變數⋯等等。
5 |
6 | 這是 JavaScript 的基礎概念,不過還是值得一提的,因為在 Github 上隨便搜尋就能看到對這個概念的集體無視,或者也可能是無知。我們來看看假設的例子嗎:
7 |
8 | ```js
9 | var hi = function(name) {
10 | return 'Hi ' + name;
11 | };
12 |
13 | var greeting = function(name) {
14 | return hi(name);
15 | };
16 | ```
17 |
18 | 在這裡 `greeting` 將 `hi` 封裝一次的 function 完全是多餘的。為何?因為在 JavaScript 中 function 是可呼叫的(callable)。當 `hi` 後方接著 `()` 時就會執行並回傳一個值;若無,它就會簡單的回傳儲存在這個變數裡的 function。讓我們來確認一下:
19 |
20 |
21 | ```js
22 | hi;
23 | // function(name) {
24 | // return 'Hi ' + name
25 | // }
26 |
27 | hi('jonas');
28 | // "Hi jonas"
29 | ```
30 |
31 | `greeting` 只是轉個彎後使用相同的參數呼叫 `hi` 而已,所以我們可以簡單的寫成:
32 |
33 | ```js
34 | var greeting = hi;
35 |
36 |
37 | greeting('times');
38 | // "Hi times"
39 | ```
40 |
41 | 換句換說,`hi` 已經是個只接受一個參數的 function 了,為何要將它再額外封裝進 function,而僅僅是使用同樣的參數呼叫 `hi`?完全沒有道理。這就像是在夏天穿上你最厚的大衣,只是為了跟熱空氣過意不去,然後再吃冰棒。簡直多此一舉。
42 |
43 | 用一個 function 將另一個 function 封裝起來的目的,只是為了延遲執行,這真的是非常糟糕的習慣。(稍後我會告訴你為何,這與可維護性密切相關。)
44 |
45 | 充分理解這個問題對讀懂本書後面的內容至關重要,所以我們再來看看幾個來自 npm module 的例子。
46 |
47 | ```js
48 | // 無知
49 | var getServerStuff = function(callback) {
50 | return ajaxCall(function(json) {
51 | return callback(json);
52 | });
53 | };
54 |
55 | // 合理
56 | var getServerStuff = ajaxCall;
57 | ```
58 |
59 | 世界上到處都充斥著這樣的垃圾 ajax 程式碼。以下是為何兩種寫法等價的原因:
60 |
61 | ```js
62 | // 這行
63 | return ajaxCall(function(json) {
64 | return callback(json);
65 | });
66 |
67 | // 等價於這行
68 | return ajaxCall(callback);
69 |
70 | // 所以重構一下 getServerStuff
71 | var getServerStuff = function(callback) {
72 | return ajaxCall(callback);
73 | };
74 |
75 | // ...就等於
76 | var getServerStuff = ajaxCall; // <-- 看,沒有 () 喔
77 | ```
78 |
79 | 各位,以上才是寫 function 的正確方式。晚點再告訴你為何我對此如此執著。
80 |
81 | ```js
82 | var BlogController = (function() {
83 | var index = function(posts) {
84 | return Views.index(posts);
85 | };
86 |
87 | var show = function(post) {
88 | return Views.show(post);
89 | };
90 |
91 | var create = function(attrs) {
92 | return Db.create(attrs);
93 | };
94 |
95 | var update = function(post, attrs) {
96 | return Db.update(post, attrs);
97 | };
98 |
99 | var destroy = function(post) {
100 | return Db.destroy(post);
101 | };
102 |
103 | return {
104 | index: index,
105 | show: show,
106 | create: create,
107 | update: update,
108 | destroy: destroy,
109 | };
110 | })();
111 | ```
112 |
113 | 這個可笑的 controller 有 99% 的程式碼都是垃圾。我們可以將它重寫成這樣:
114 |
115 | ```js
116 | var BlogController = {
117 | index: Views.index,
118 | show: Views.show,
119 | create: Db.create,
120 | update: Db.update,
121 | destroy: Db.destroy,
122 | };
123 | ```
124 |
125 | ⋯或是直接全部刪掉,因為它的作用只是將 Views 與 DB 打包在一起而已。
126 |
127 | ## 為何鍾愛 First Class?
128 |
129 | Okay,現在讓我們來看看鍾愛 First Class 的原因為何。前面我們看過了 `getServerStuff` 與 `BlogController` 兩個例子,雖然增加一些沒有實際用途的間接層相當容易,但這樣做除了徒增加程式碼量,提高維護及查詢的成本外,沒有任何用處。
130 |
131 | 此外,如果一個 function 被不必要的封裝起來,當它發生變更時,我們也必須變更封裝它的 function。
132 |
133 | ```js
134 | httpGet('/post/2', function(json) {
135 | return renderPost(json);
136 | });
137 | ```
138 |
139 | 如果 `httpGet` 要改成可能送出一個 `err`,我們必須回頭修改「glue」。
140 |
141 | ```js
142 | // 將應用程式中每個 httpGet 的呼叫都要改成這樣,才能傳遞 err
143 | httpGet('/post/2', function(json, err) {
144 | return renderPost(json, err);
145 | });
146 | ```
147 |
148 | 寫成 first class function,要做得更動將會少很多:
149 |
150 | ```js
151 | // renderPost 會在 httpGet 中被呼叫,想要多少參數都可以
152 | httpGet('/post/2', renderPost);
153 | ```
154 |
155 | 為了刪除不必要的 function,正確地為參數命名也必不可少。當然命名不是什麼大問題。但還是有可能存在一些不當的命名-尤其隨著程式碼的增長及需求的變更時。
156 |
157 | 專案中常見的混淆原因是,針對一個相同的概念使用不同的命名。在通用的程式碼也有相同的問題。舉例來說,下方兩個 function 做的事情是相同的,但後者比前者更加通用,可重用性也更高:
158 |
159 |
160 | ```js
161 | // 針對目前的 blog
162 | var validArticles = function(articles) {
163 | return articles.filter(function(article) {
164 | return article !== null && article !== undefined;
165 | });
166 | };
167 |
168 | // 對未來的專案友好多了
169 | var compact = function(xs) {
170 | return xs.filter(function(x) {
171 | return x !== null && x !== undefined;
172 | });
173 | };
174 | ```
175 |
176 | 在命名時,我們特別容易將自己限定在特定的資料(在本例中是 `articles`)。這種現象很常見,也是重複造輪子的一大原因。
177 |
178 | 有一點我必須指出,就像物件導向的程式碼,你必須非常小心 `this` 反咬你一口。如果一個底層 function 使用了 `this`,而且是以 first class 的方式被呼叫,我們都被這個充滿漏洞的抽象概念給惹怒。
179 |
180 | ```js
181 | var fs = require('fs');
182 |
183 | // 太可怕了
184 | fs.readFile('freaky_friday.txt', Db.save);
185 |
186 | // 好一些
187 | fs.readFile('freaky_friday.txt', Db.save.bind(Db));
188 |
189 | ```
190 |
191 | 將 `Db` bind 到他自身後,你就可以隨心所欲的呼叫它的原型鏈式垃圾程式碼了。`this` 就像一片髒尿布,我盡可能地避免使用它,因為在 functional 的程式碼中根本用不到它。但是,在使用其它的 library 時,你卻不得不向這個瘋狂的世界低頭。
192 |
193 | 也有人會反駁說為了速度 `this` 是必須的。如果你是這種對速度吹毛求疵的人,那你還是合上這本書吧。如果你沒辦法退款,也許你能去換一本更入門的書來讀。
194 |
195 | 至此,我們才準備好繼續後面的章節。
196 |
197 | [第 3 章:Pure Function-單純的幸福](ch3.md)
198 |
--------------------------------------------------------------------------------
/ch3.md:
--------------------------------------------------------------------------------
1 | # 第 3 章:Pure Function-單純的幸福
2 |
3 | ## 再次強調「Pure」
4 |
5 | 首先,我們要釐清 pure function 的概念。
6 |
7 | > Pure function 意指相同的輸入,永遠會得到相同的輸出,而且沒有任何顯著的副作用。
8 |
9 | 例如 `slice` 及 `splice`。這兩個 function 的作用並無二致-但是注意,它們各自的方式卻大不相同,但結果還是一樣的。為何說 `slice` 是 *pure*,因為對於相同的輸入它能保證回傳的輸出是相同的。但 `splice` 卻會嚼爛呼叫它的陣列,然後吐出來;這產生了顯著的副作用,即這個陣列被永久改變了。
10 |
11 | ```js
12 | var xs = [1, 2, 3, 4, 5];
13 |
14 | // pure(純)
15 | xs.slice(0, 3);
16 | //=> [1, 2, 3]
17 |
18 | xs.slice(0, 3);
19 | //=> [1, 2, 3]
20 |
21 | xs.slice(0, 3);
22 | //=> [1, 2, 3]
23 |
24 |
25 | // impure(不純)
26 | xs.splice(0, 3);
27 | //=> [1, 2, 3]
28 |
29 | xs.splice(0, 3);
30 | //=> [4, 5]
31 |
32 | xs.splice(0, 3);
33 | //=> []
34 | ```
35 |
36 | 在 functional programming 中,我們討厭像 `splice` 這種會*改變*資料的笨 function。我們追求的是那種可靠的,每次都能回傳相同結果的 function,而不是像 `splice` 這種每次呼叫後留下一堆爛攤子的 function。
37 |
38 | 讓我們看看另一個例子。
39 |
40 | ```js
41 | // impure
42 | var minimum = 21;
43 |
44 | var checkAge = function(age) {
45 | return age >= minimum;
46 | };
47 |
48 |
49 |
50 | // pure
51 | var checkAge = function(age) {
52 | var minimum = 21;
53 | return age >= minimum;
54 | };
55 | ```
56 |
57 | 在 impure 的版本中,`checkAge` 的結果將取決於 `minimum` 這個可變變數。換句話說,他將取決於系統狀態;這點令人失望,因為它引用了外部的環境,從而增加了認知負擔。
58 |
59 | 這個例子可能還不是那麼明顯,但這種依賴狀態是影響系統複雜度的罪魁禍首()。輸入值之外的因素能夠左右 `checkAge` 的回傳值,不僅讓他變得不符合 pure,還導致每次我思考整個軟體時都痛苦不堪。
60 |
61 | 另一方面,使用 pure 的形式就能做到完全自給自足。我們可以讓 `minimum` 變為 immutable(不可變的),這樣就可保留純粹性,因為狀態不會有變化。我們可以建立一個 object 並 freeze 它來做到這件事。
62 |
63 | ```js
64 | var immutableState = Object.freeze({
65 | minimum: 21,
66 | });
67 | ```
68 |
69 | ## 副作用可能包含⋯
70 |
71 | 讓我們來仔細研究一下「副作用」以加深理解。那麼,在 `pure function` 定義中所提到萬惡的*副作用*指的是什麼?我們可以將*作用*理解為除了結果計算之外所發生的事。
72 |
73 | 作用本身並沒有什麼壞處,而且在本書後面的章節隨處可看到他的影子。真正帶有負面含義的是*副*。就像一灘死水的水並不是幼蟲的培養皿,*死*才是產生蟲群的主因。我向你保證,*副*作用是你程式中滋生 bug 的溫床。
74 |
75 | >*副作用*是在計算結果的過程中,系統狀態的一種改變,或是外部世界可觀察的*交互作用*。
76 |
77 | 副作用可以包含,但不限於:
78 |
79 | * 更改檔案系統
80 | * 在資料庫寫入紀錄
81 | * 發送一個 http 請求
82 | * 可變資料
83 | * 印出至畫面 / log
84 | * 取得使用者輸入
85 | * DOM 查詢
86 | * 存取系統狀態
87 |
88 | 這個列表還能繼續寫下去。概括來說,只要與 function 外部環境發生交互作用的都是副作用,這可能會讓你懷疑在程式設計上無副作用的可行性。functional programming 的哲學就是假設副作用是造成不正確行為的主要原因
89 |
90 | 這並不代表我們要禁止使用一切的副作用,而是說要讓他們在可控制的範圍內發生。在後面說到 functor 及 monad 時我們會學習如何控制它們,但目前還是盡量遠離這些陰險的 function 較好。
91 |
92 | 副作用會讓一個 function 不再 *pure* 是有道理的:從定義上來說,pure function 必須要能夠根據相同的輸入回傳相同的輸出,若 function 必須與外部的事物來往時就無法保證此定義。
93 |
94 | 讓我們來仔細了解為何要堅持這種相同輸入得到相同輸出的原則。注意,我們要來複習一些八年級的數學了。
95 |
96 | ## 八年級數學
97 |
98 | 根據 mathisfun.com:
99 |
100 | > function 與不同數值間的特殊關係:對每一個輸入值回傳一個輸出值。
101 |
102 | 換句話說,這只是兩個數值之間的關係:輸入及輸出。即使每個輸入都只會有一個輸出,但不同的輸入卻可以有相同的輸出。下圖展示了由 `x` 到 `y` 完全合法的 function。
103 |
104 |
(http://www.mathsisfun.com/sets/function.html)
105 |
106 | 反之,下圖則展示了`非` function 的關係,因為輸入值的 `5` 指向了多個輸出:
107 |
108 |
(http://www.mathsisfun.com/sets/function.html)
109 |
110 | Function 可以被描述為一個集合,這個集合中的的內容為(輸入,輸出):`[(1,2), (3,6), (5,10)]`(看來這個 function 是將輸入加倍)。
111 |
112 | 或是一張表:
113 |
114 |
115 | 甚至是一個以 `x` 為輸入 `y` 為輸出的圖形:
116 |
117 |
118 |
119 |
120 | 如果輸入直接指名了輸出為何,那麼就不必實作細節了。因為 function 只是將輸入 mapping 至輸出,所以簡單的寫一個 object ,將 `[]` 代替 `()` 就能執行了。
121 |
122 | ```js
123 | var toLowerCase = {
124 | 'A': 'a',
125 | 'B': 'b',
126 | 'C': 'c',
127 | 'D': 'd',
128 | 'E': 'e',
129 | 'D': 'd',
130 | };
131 |
132 | toLowerCase['C'];
133 | //=> 'c'
134 |
135 | var isPrime = {
136 | 1: false,
137 | 2: true,
138 | 3: true,
139 | 4: false,
140 | 5: true,
141 | 6: false,
142 | };
143 |
144 | isPrime[3];
145 | //=> true
146 | ```
147 |
148 | 當然了,你可能需要計算而不是手動將這些東西寫出來,不過這樣說明了透過不同方式來思考 function。(你可能會想「要是 function 有多個參數呢?」。的確,這種情況表明了以數學方式思考的一些不便。我們可以暫時將他們打包進陣列中,或者把 `arguments` object 看成是輸入。當我們學習 `curry` 的概念後,你就知道如何直接為 function 在數學上的定義建立 model。)
149 |
150 | 戲劇性的事:Pure function *就是*數學上的 function,而且是 functional programming 的全部。使用這些 pure function 來進行程式設計會帶來大量的好處。讓我們來看看為何要不遺餘力的保留 function 純粹性的原因。
151 |
152 | ## 追求「Pure」的理由
153 |
154 | ### 可快取性(Cacheable)
155 |
156 | 首先,pure function 可以根據輸入來做快取。其中一種典型的方式就是透過 memoization。
157 |
158 | ```js
159 | var squareNumber = memoize(function(x) {
160 | return x * x;
161 | });
162 |
163 | squareNumber(4);
164 | //=> 16
165 |
166 | squareNumber(4); // 回傳輸入為 4 的快取結果
167 | //=> 16
168 |
169 | squareNumber(5);
170 | //=> 25
171 |
172 | squareNumber(5); // 回傳輸入為 5 的快取結果
173 | //=> 25
174 | ```
175 |
176 | 下方是一個簡單的實作,即便有很多更強大的版本可做選擇。
177 |
178 | ```js
179 | var memoize = function(f) {
180 | var cache = {};
181 |
182 | return function() {
183 | var arg_str = JSON.stringify(arguments);
184 | cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
185 | return cache[arg_str];
186 | };
187 | };
188 | ```
189 |
190 | 值得注意的是可以透過延遲執行的方式將 impure 的 function 轉換為 pure function:
191 |
192 | ```js
193 | var pureHttpCall = memoize(function(url, params) {
194 | return function() {
195 | return $.getJSON(url, params);
196 | };
197 | });
198 | ```
199 |
200 | 這裡有趣的地方在於我們並沒有真正的發送 http 請求-只是回傳了一個 function,當呼叫它的時候才會發送請求。這個 function 之所以 pure 是因為它會根據相同的輸入回傳相同的輸出:給定了 `url` 及 `params` 後,它只會回傳同一個 http 請求的 function。
201 |
202 | 我們的 `memoize` function 執行起來沒有任何問題,雖然他快取的不是 http 請求的結果,而是產生的 function。
203 |
204 | 目前看來這種方式的意義不大,但我們很快會學習一些技巧來發掘它的用處。重點是我們可以快取任何一個 function,不管它們看起來多麽具有破壞性。
205 |
206 | ### 可移植性(Portable) / 本身即文件(Self-Documenting)
207 |
208 | Pure function 完全是自給自足的,它所需要的所有東西都能輕易取得。仔細想想⋯這種特性的好處為何呢?首先,function 的依賴會很明確,因此更易於觀察和理解-沒有偷偷摸摸的小動作。
209 |
210 | ```js
211 | //impure
212 | var signUp = function(attrs) {
213 | var user = saveUser(attrs);
214 | welcomeUser(user);
215 | };
216 |
217 | var saveUser = function(attrs) {
218 | var user = Db.save(attrs);
219 | ...
220 | };
221 |
222 | var welcomeUser = function(user) {
223 | Email(user, ...);
224 | ...
225 | };
226 |
227 | //pure
228 | var signUp = function(Db, Email, attrs) {
229 | return function() {
230 | var user = saveUser(Db, attrs);
231 | welcomeUser(Email, user);
232 | };
233 | };
234 |
235 | var saveUser = function(Db, attrs) {
236 | ...
237 | };
238 |
239 | var welcomeUser = function(Email, user) {
240 | ...
241 | };
242 | ```
243 |
244 | 這個例子說明了 pure function 對於依賴必須要誠實,這樣我們就能知道他的目的。從 `signUp` 就可以得知它會用到 `Db`、`Email` 即 `attrs`,這在最小程度上給了我們足夠的訊息。
245 |
246 | 往後我們會學習如何不透過這種延遲執行的方式讓一個 function 變為 pure function,不過這裡的重點是,對比 impure function,pure function 能夠提供更多的訊息,前者背後做了什麼事只有老天爺才知道。
247 |
248 | 此外要注意的是,我們透過強制「注入」依賴,或把他們當作參數傳遞,會讓我們的 app 變成更加靈活,因為資料庫或 mail client 等等都已經參數化了(別擔心,我們會有別種方式讓它不那麼單調乏味)。若要使用另一個 Db,我們只需要將他傳給 function 即可。若想在新的應用程式中使用這個可靠的 function,只要在那時將 `Db` 與 `Email` 傳遞進 function 就行了,相當容易。
249 |
250 | 在 JavaScript 設定中,可移植性代表可將 function serializing(序列化)並透過 socket 傳動。也可以表示程式碼能夠在所有的 app worker 中執行。總之,可移植性是一個相當強大的特性。
251 |
252 | Imperative programming 中「典型」的方式與過程都深深的根值在他們的環境中,透過狀態、依賴即有效作用(available effects)達成;Pure function 則與此相反,只要我們願意,可以在任何地方執行它。
253 |
254 | 你上一次將類別方法複製到新的 app 中是何時?我最喜歡的名言是 Erlang 的作者 Joe Armstrong 所說的一句話:「物件導向語言的問題在於,它們隨身攜帶那些癮式的環境。你只要一根香蕉,但卻得到一個拿著香蕉的大猩猩⋯以及整片叢林」。
255 |
256 | ### 可測試性(Testable)
257 |
258 | 下一點,pure function 讓測試更加的容易。我們不需 mock 一個「真實的」付款閘道,或每次測試前都要 setup,測試之後都要 assert 狀態。我們只需要給定一個輸入,在 assert 輸出即可。
259 |
260 | 事實上,我們會發現 functional 的社群正在開創一個新的測試工具,能夠幫助我們自動產生輸入並 assert 輸出。這超出了本書的範圍,不過我強烈建議你去試試 *Quickcheck*-一個為 pure functional 環境量身打造的測試工具。
261 |
262 | ### 合理性(Reasonable)
263 |
264 | 很多人相信使用 pure function 最大的好處就是*引用透明性(referential transparency)*。如果一段程式碼可以替換成它執行後所得到的結果,而且是在不改變整個程式行為的前提下替換的,那麼我們可以說這段程式碼是引用透明的。
265 |
266 | 由於 pure function 能夠根據相同的輸入回傳相同的輸出,所以它們就能夠保證總是回傳同一個結果,這也就保證了引用透明性。我們來看一個例子。
267 |
268 | ```js
269 |
270 | var Immutable = require('immutable');
271 |
272 | var decrementHP = function(player) {
273 | return player.set('hp', player.get('hp') - 1);
274 | };
275 |
276 | var isSameTeam = function(player1, player2) {
277 | return player1.get('team') === player2.get('team');
278 | };
279 |
280 | var punch = function(player, target) {
281 | return isSameTeam(player, target) ? target : decrementHP(target);
282 | };
283 |
284 | var jobe = Immutable.Map({
285 | name: 'Jobe',
286 | hp: 20,
287 | team: 'red',
288 | });
289 | var michael = Immutable.Map({
290 | name: 'Michael',
291 | hp: 20,
292 | team: 'green',
293 | });
294 |
295 | punch(jobe, michael);
296 | //=> Immutable.Map({name:'Michael', hp:19, team: 'green'})
297 | ```
298 |
299 | `decrementHP`、`isSameTeam` 及 `punch` 都是 pure function,所以是引用透明的。我們可以透過一種稱做為*等式推導(equational reasoning)*的技術來分析程式碼,這技術就是以「一對一」進行替換,有點像是在不考慮程式性執行的怪異行為的情況下,手動執行相關程式碼。我們借助引用透明性來解析這段程式碼。
300 |
301 | 首先我們將 `isSameTeam` function 替換。
302 |
303 | ```js
304 | var punch = function(player, target) {
305 | return player.get('team') === target.get('team') ? target : decrementHP(target);
306 | };
307 | ```
308 |
309 | 因為我們資料是 immutable,所以可以直接將 teams 替換成實際值。
310 |
311 | ```js
312 | var punch = function(player, target) {
313 | return 'red' === 'green' ? target : decrementHP(target);
314 | };
315 | ```
316 |
317 | 因為執行結果為 false,所以可以將整行 if 刪除。
318 |
319 | ```js
320 | var punch = function(player, target) {
321 | return decrementHP(target);
322 | };
323 |
324 | ```
325 |
326 | 如果我們替換 `decrementHP`,我們會發現在這個情況下,punch 變為一個讓 `hp` 遞減 1 的 function。
327 |
328 | ```js
329 | var punch = function(player, target) {
330 | return target.set('hp', target.get('hp') - 1);
331 | };
332 | ```
333 |
334 | 總之,等式推導所帶來分析程式碼能力對重構與理解程式碼非常重要。事實上,我們重構 flock of seagulls 程式使用的正是這種技巧:透過等式推導利用加與乘的特性。我們會使用這些技巧貫穿全書。
335 |
336 | ### 並行程式碼
337 |
338 | 最後,也是決定性的一點:我們可以並行執行任何 pure function,因為 pure function 根本不需要存取共享的記憶體,而且根據其定義,它也不會因副作用而進入競爭狀態(race condition)。
339 |
340 | 這在伺服器端 js 環境及使用 web worker 的瀏覽器中是相當容易實現的,因為他們使用的執行緒(thread)。不過出於對 impure function 複雜度的考慮,目前主流的觀點還是避免使用並行。
341 |
342 |
343 | ## 總結
344 |
345 | 我們已經瞭解了何為 pure function,也看到作為 functional programmer 的我們,為何深信它們是不同凡響的。從這裡開始,我們將盡力以 pure 的方式撰寫所有 function。對此我們需要一些額外的工具來達成這個目的,同時也盡量把 impure function 從 pure 程式碼中分離。
346 |
347 | 如果手邊沒有一些額外的工具,那麼撰寫 pure function 的程式就會有點費力。我們必須透過到處傳遞參數來操作資料,何況狀態是禁止使用的,更別說作用了。誰願意這樣自虐自己寫程式?所以讓我們來學習一個叫做 curry 的新工具。
348 |
349 | [第 4 章:Curry(柯里化)](ch4.md)
350 |
--------------------------------------------------------------------------------
/ch4.md:
--------------------------------------------------------------------------------
1 | # 第 4 章:Curry(柯里化)
2 |
3 | ## Curry 的重要性
4 | 我爸以前跟我說過,有些東西在你得到以前是可有可無的,但得到之後就不可或缺了。微波爐、智慧型手機皆是如此。老人在沒有網路時也過得很充實。對我來說,curry 也是這樣。
5 |
6 | Curry 的概念很簡單:你可以只透過部分的參數呼叫一個 function,它會回傳一個 function 去處理剩下的參數。
7 |
8 | 你可以一次性的呼叫 curry function,也可以每次只傳遞一個參數。
9 |
10 | ```js
11 | var add = function(x) {
12 | return function(y) {
13 | return x + y;
14 | };
15 | };
16 |
17 | var increment = add(1);
18 | var addTen = add(10);
19 |
20 | increment(2);
21 | // 3
22 |
23 | addTen(2);
24 | // 12
25 | ```
26 |
27 | 這裡我們建立了一個 `add` function,它接受一個參數並回傳一個新的 function。當呼叫它後,回傳的 function 會透過 closure(閉包)的方式儲存 `add` 第一個參數。一次透過兩個參數呼叫實在是有點繁複,幸虧我們可以使用一個名為 `curry` 的特殊 helper function 讓定義並呼叫這種 function 變得更容易。
28 |
29 | 讓我們建立一些 curry function 來享受一下。
30 |
31 | ```js
32 | var curry = require('lodash/curry');
33 |
34 | var match = curry(function(what, str) {
35 | return str.match(what);
36 | });
37 |
38 | var replace = curry(function(what, replacement, str) {
39 | return str.replace(what, replacement);
40 | });
41 |
42 | var filter = curry(function(f, ary) {
43 | return ary.filter(f);
44 | });
45 |
46 | var map = curry(function(f, ary) {
47 | return ary.map(f);
48 | });
49 | ```
50 |
51 | 上方程式碼所遵循的是一種簡單,但也重要的模式。這裡戰略性的將欲操作的資料(String,Array)作為最後一個參數傳入。當使用時就知道為何要這麼做了。
52 |
53 | ```js
54 | match(/\s+/g, 'hello world');
55 | // [ ' ' ]
56 |
57 | match(/\s+/g)('hello world');
58 | // [ ' ' ]
59 |
60 | var hasSpaces = match(/\s+/g);
61 | // function(x) { return x.match(/\s+/g) }
62 |
63 | hasSpaces('hello world');
64 | // [ ' ' ]
65 |
66 | hasSpaces('spaceless');
67 | // null
68 |
69 | filter(hasSpaces, ['tori_spelling', 'tori amos']);
70 | // ['tori amos']
71 |
72 | var findSpaces = filter(hasSpaces);
73 | // function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
74 |
75 | findSpaces(['tori_spelling', 'tori amos']);
76 | // ['tori amos']
77 |
78 | var noVowels = replace(/[aeiouy]/ig);
79 | // function(replacement, x) { return x.replace(/[aeiouy]/ig, replacement) }
80 |
81 | var censored = noVowels("*");
82 | // function(x) { return x.replace(/[aeiouy]/ig, '*') }
83 |
84 | censored('Chocolate Rain');
85 | // 'Ch*c*l*t* R**n'
86 | ```
87 |
88 | 這裡示範的是一種「pre-load(預先載入)」的能力,透過傳遞一至兩個參數,就能得到一個記住這些參數的新 function。
89 |
90 | 我建議你透過 `npm install lodash` 安裝 lodash,複製上方的程式碼至 REPL 執行看看。你也可以在能使用 lodash 或 ramda 的瀏覽器環境中執行。
91 |
92 | ## 不只是雙關語 / 咖哩
93 |
94 | Curry 的用處非常廣泛。就像在 `hasSpaces`、`findSpaces` 及 `censored` 看到的,只需傳遞一些參數至 function,就能得到一個新的 function。
95 |
96 | 只要透過 `map` 封裝單一元素的 function,即可將它轉換成參數維陣列的 function:
97 |
98 | ```js
99 | var getChildren = function(x) {
100 | return x.childNodes;
101 | };
102 |
103 | var allTheChildren = map(getChildren);
104 | ```
105 |
106 | 只傳遞一部分參數至 function 通常稱做*部分應用(partial application)*,能夠大量減少樣板程式碼(boiler plate code)。考慮上方的 `allTheChildren` function 若使用 lodash 非 curry 的 `map` 來寫會如何(請注意參數的順序也不同):
107 |
108 | ```js
109 | var allTheChildren = function(elements) {
110 | return _.map(elements, getChildren);
111 | };
112 | ```
113 |
114 | 一般來說我們不會定義直接操作陣列的 function,因為我們只需要行內呼叫 `map(getChildren)` 即可。此點也同樣適用於 `sort`、`filter` 及其他高階 function(Higher order function:一個 function 使用或者是回傳另一個 function)。
115 |
116 | 當我們討論 *pure function* 時,我們會說它接受一個輸入並對應一個輸出。Curry 所做的事也是如此:每傳遞一個參數就會回傳一個新的 function 處理剩餘的參數。這就是一個輸入對應一個輸出。
117 |
118 | 不論輸出是否為另一個 function,它也是 pure function。我們也接受一次傳遞多個參數,不過這樣也只是為了方便減少多餘的 `()`。
119 |
120 |
121 | ## 總結
122 |
123 | Curry 使用起來相當得心應手,每天使用它對我來說簡直是一種享受。它是一種必備的工具,讓 functional programming 不那麼繁冗。
124 |
125 | 透過簡單的傳遞一些參數,就能夠動態的建立實用的新 function,即便有多個參數,也保留了數學 function 的定義。
126 |
127 | 讓我們來介紹另一個必備工具 `compose(組合)`
128 |
129 | [第 5 章:透過 Compose 開發](ch5.md)
130 |
131 | ## 練習
132 |
133 | 在開始前先說明一下。我們預設會使用名為 [Ramda](http://ramdajs.com) 的 library 將 function 轉換為 curry function。或者你能使用由 lodash 撰寫與維護的 [lodash/fp](https://github.com/lodash/lodash/wiki/FP-Guide) 做到一樣的事。兩者都運作得相當好,你可以根據偏好做選擇。
134 |
135 | 你還能對自己的練習程式碼進行[unit test(單元測試)](https://github.com/DrBoolean/mostly-adequate-guide/tree/master/code/part1_exercises),或者只將程式碼複製到 javascript REPL 執行看看。
136 |
137 | 練習的答案已經放在[本書的 repository](https://github.com/DrBoolean/mostly-adequate-guide/tree/master/code/part1_exercises/answers)。做本練習最佳方式就是透過[及時回饋環線(immediate feedback loop)](feedback_loop.md)。
138 |
139 | ```js
140 | var _ = require('ramda');
141 |
142 |
143 | // 練習 1
144 | //==============
145 | // 透過部分套用(partially applying)重構此 function 移除所有參數。
146 |
147 | var words = function(str) {
148 | return _.split(' ', str);
149 | };
150 |
151 | // 練習 1a
152 | //==============
153 | // 使用 map 建立一個新的 words fn,讓它可以操作字串的陣列。
154 |
155 | var sentences = undefined;
156 |
157 |
158 | // 練習 2
159 | //==============
160 | // 透過部分套用(partially applying)重構此 function 移除所有參數。
161 |
162 | var filterQs = function(xs) {
163 | return _.filter(function(x) {
164 | return match(/q/i, x);
165 | }, xs);
166 | };
167 |
168 |
169 | // 練習 3
170 | //==============
171 | // 使用 helper function _keepHighest 重構 max,讓它不需參考任何參數。
172 |
173 | // 不需更動:
174 | var _keepHighest = function(x, y) {
175 | return x >= y ? x : y;
176 | };
177 |
178 | // 重構這段:
179 | var max = function(xs) {
180 | return _.reduce(function(acc, x) {
181 | return _keepHighest(acc, x);
182 | }, -Infinity, xs);
183 | };
184 |
185 |
186 | // 加分題 1:
187 | // ============
188 | // 封裝陣列的 slice 讓它變為 functional 的 curry function。
189 | // //[1, 2, 3].slice(0, 2)
190 | var slice = undefined;
191 |
192 |
193 | // 加分題 2:
194 | // ============
195 | // 使用 slice 定義一個「take」function,讓它擷取字串從頭開始的的 n 個元素。此 function 必須為 curry function。
196 | // // 輸入「Something」且 n=4 時結果必須為「Some」
197 | var take = undefined;
198 | ```
199 |
--------------------------------------------------------------------------------
/ch5.md:
--------------------------------------------------------------------------------
1 | # 第 5 章:使用 Compose 開發
2 |
3 | ## Functional 飼養
4 |
5 | 這就是 `compose`:
6 |
7 | ```js
8 | var compose = function(f, g) {
9 | return function(x) {
10 | return f(g(x));
11 | };
12 | };
13 | ```
14 |
15 | `f` 和 `g` 都是 function,`x` 則是通過它們之間「管道」的值。
16 |
17 | Compose 感覺起來就像在飼養 function。你就是 function 的飼養員,選擇兩個有你喜歡特色的 function 並將它們組合,產生一個新的 function。使用起來如下:
18 |
19 | ```js
20 | var toUpperCase = function(x) {
21 | return x.toUpperCase();
22 | };
23 | var exclaim = function(x) {
24 | return x + '!';
25 | };
26 | var shout = compose(exclaim, toUpperCase);
27 |
28 | shout("send in the clowns");
29 | //=> "SEND IN THE CLOWNS!"
30 | ```
31 |
32 | 組合兩個 function 並回傳一個新的 function 是很容易理解的:組合某種類型(在本例中為 function)的兩個元素應該產生一個該類型的新元素。你將兩個樂高積木組合起來並不會得到林肯積木。所以這是有跡可循的,我們會在適當的時候探討這方面的一些底層理論。
33 |
34 | 在 `composer` 的定義中,`g` 會在 `f` 之前執行,而建立一個由右至左的資料流。這麼做的可讀性遠高於巢狀的 function 呼叫。若不用 composer,那麼會像以下這樣:
35 |
36 | ```js
37 | var shout = function(x) {
38 | return exclaim(toUpperCase(x));
39 | };
40 | ```
41 |
42 | 程式由右而左執行,而不是由內而外,我認為這可以稱之為「左傾(left direction)」。讓我們看看一個順序重要的例子:
43 |
44 | ```js
45 | var head = function(x) {
46 | return x[0];
47 | };
48 | var reverse = reduce(function(acc, x) {
49 | return [x].concat(acc);
50 | }, []);
51 | var last = compose(head, reverse);
52 |
53 | last(['jumpkick', 'roundhouse', 'uppercut']);
54 | //=> 'uppercut'
55 | ```
56 |
57 | `reverse` 會將列表反轉,`head` 則會取得列表的第一個元素。結果就得到了一個效率不高的 `last` function。這個組合中 function 的執行順序是顯而易見的。雖然我們可以定義一個由左而右的版本,但是由右而左更能反映出數學上的含義。沒錯,compose 的概念直接來自於數學課本。事實上,現在是時候看看所有 compose 都有的一個特性了。
58 |
59 | ```js
60 | // 結合律(associativity)
61 | var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
62 | // true
63 | ```
64 |
65 | Compose 有結合律的特性,意指不管你將哪兩個分為一組都不重要。所以,如果我們想將字串轉為大寫,可以這樣寫:
66 |
67 | ```js
68 | compose(toUpperCase, compose(head, reverse));
69 |
70 | // 或
71 | compose(compose(toUpperCase, head), reverse);
72 | ```
73 |
74 | 因為呼叫 `compose` 時的分組方式不重要,所以結果都會是相同的。因此,這也讓我們可以撰寫一個參數數量可變的 compose,用法如下:
75 |
76 | ```js
77 | // 在前面的例子中我們寫了兩個 compose,不過因為 compose 符合結合律,我們可以讓 compose 接受多個 function,並讓它自己決定如何分組。
78 | var lastUpper = compose(toUpperCase, head, reverse);
79 |
80 | lastUpper(['jumpkick', 'roundhouse', 'uppercut']);
81 | //=> 'UPPERCUT'
82 |
83 |
84 | var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);
85 |
86 | loudLastUpper(['jumpkick', 'roundhouse', 'uppercut']);
87 | //=> 'UPPERCUT!'
88 | ```
89 |
90 | 運用結合律的屬性能夠為我們帶來強大的靈活性,及當結果相同時的所帶來的安心感。稍微複雜一點,參數數量可變的 compose 都已經包含在本書的提供的 library 中,你也可以在像是 [lodash][lodash-website]、[underscore][underscore-website] 及 [ramda][ramda-website] 的 library 中也可以找到它們。
91 |
92 | 結合率的一大好處是任何一個 function 的分組都可以被拆開,然後再以他們自己的 compose 方式封裝在一起。讓我們來重構前面的例子:
93 |
94 | ```js
95 | var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);
96 |
97 | // 或
98 | var last = compose(head, reverse);
99 | var loudLastUpper = compose(exclaim, toUpperCase, last);
100 |
101 | // 或
102 | var last = compose(head, reverse);
103 | var angry = compose(exclaim, toUpperCase);
104 | var loudLastUpper = compose(angry, last);
105 |
106 | // 更多變種⋯
107 | ```
108 |
109 | 這沒有標準答案-我們只是以自己喜歡的方式玩樂高積木而已。一般來說,分組的最佳方式就是讓它可重用,像是 `last` 及 `angry`。如果熟悉的 Fowler 的《[Refactoring][refactoring-book]》這本書的話,你可能會知道這個過程稱之為「[extract method(抽出方法)][extract-method-refactor]」⋯只不過不需要擔心object的所有狀態。
110 |
111 | ## Pointfree
112 |
113 | Pointfree 模式指的是永遠不必說出你的資料。呃抱歉(譯註:此處原文是「Pointfree style means never having to say your data」,源自 1970 年的電影 Love Story 裡的一句著名台詞「Love means never having to say you're sorry」。緊接著作者又說了一句「Excuse me」,大概是一種幽默)。意思是指,function 不必提及要操作的資料是什麼樣的。First Class Function、curry 及 compose 協作起來非常有助於建立這種模式。
114 |
115 | ```js
116 | // 非 pointfree,因為我們提到資料:word
117 | var snakeCase = function(word) {
118 | return word.toLowerCase().replace(/\s+/ig, '_');
119 | };
120 |
121 | // pointfree
122 | var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);
123 | ```
124 |
125 | 看到 `replace` 是如何被部分呼叫了嗎?這裡做的事情就是將資料傳遞至每個接收單一參數的 function。Curry 讓每個 function 都先接收資料,再操作資料,最後再將資料傳遞至下一個 function。另外要注意在 pointfree 的版本中,我們不需資料來建構 function,而在非 pointfree 的版本中,我們必須先擁有 `word` 才能進行其他操作。
126 |
127 | 讓我們看看另一個例子。
128 |
129 | ```js
130 | // 非 pointfree,因為我們提到資料:name
131 | var initials = function(name) {
132 | return name.split(' ').map(compose(toUpperCase, head)).join('. ');
133 | };
134 |
135 | // pointfree
136 | var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));
137 |
138 | initials("hunter stockton thompson");
139 | // 'H. S. T'
140 | ```
141 |
142 | Pointfree 幫助我們移除不必要的命名,讓程式碼保持簡潔和通用。對 functional 的程式碼來說,pointfree 是個很好的試金石,因為它能告訴我們一個 function 是否為接受輸入回傳輸出的小 function。像是 compose 無法用於 while 迴圈上。不過請注意,pointfree 就像一把雙刃劍,有時會混淆視聽。並不是所有的 functional 程式碼都為 pointfree,不過這沒關係。可以使用他的時候就使用,不能使用的時候就用普通的 function。
143 |
144 | ## Debug
145 | Compose 常見的錯誤就是,在沒有第一次部分呼叫前,就 compose 像是 `map` 接受兩個參數的 function。
146 |
147 | ```js
148 | // 不正確-我們傳遞 array 給 angry,但不知道部分呼叫的 map 接收到什麼。
149 | var latin = compose(map, angry, reverse);
150 |
151 | latin(['frog', 'eyes']);
152 | // error
153 |
154 |
155 | // 正確-每個 function 都預期接受一個參數。
156 | var latin = compose(map(angry), reverse);
157 |
158 | latin(['frog', 'eyes']);
159 | // ['EYES!', 'FROG!'])
160 | ```
161 |
162 | 如果你在 debug compose 時遇到了問題,我們可以使用下面這個實用,但 impure 的 trace function 追蹤執行情況。
163 |
164 | ```js
165 | var trace = curry(function(tag, x) {
166 | console.log(tag, x);
167 | return x;
168 | });
169 |
170 | var dasherize = compose(join('-'), toLower, split(' '), replace(/\s{2,}/ig, ' '));
171 |
172 | dasherize('The world is a vampire');
173 | // TypeError: Cannot read property 'apply' of undefined
174 | ```
175 |
176 | 這裡出錯了,讓我們 `trace` 看看
177 |
178 | ```js
179 | var dasherize = compose(join('-'), toLower, trace('after split'), split(' '), replace(/\s{2,}/ig, ' '));
180 | // after split [ 'The', 'world', 'is', 'a', 'vampire' ]
181 | ```
182 |
183 | 啊!因為 `toLower` 執行於 array,我們需要透過 `map` 呼叫它。
184 |
185 | ```js
186 | var dasherize = compose(join('-'), map(toLower), split(' '), replace(/\s{2,}/ig, ' '));
187 |
188 | dasherize('The world is a vampire');
189 |
190 | // 'the-world-is-a-vampire'
191 | ```
192 |
193 | `trace` function 讓我們在某個特定的點觀察資料,以便進行 debug。像是 haskell 與 purescript 的語言為了方便開發,也都提供了相似的 function。
194 |
195 | Compose 會成為我們建構程式的工具,且幸運的是,他背後有個強大的理論做支撐。讓我們來研究一下這個理論。
196 |
197 |
198 | ## 範疇論
199 |
200 | 範疇論(category theory)是個數學的抽象分支,能夠形式化集合論(set theory)、類型論(type theory)、群論(group theory)及邏輯學(logic)等數學分支的一些概念。範疇學主要處理 object、態射(morphism)及轉化(transformation),而這些概念跟程式設計的關係非常密切。下圖是一些同樣概念分別在不同理論下的形式:
201 |
202 |
203 |
204 | 抱歉,我沒有任何要嚇你的意思。我不假設你對這些概念瞭若指掌,我的重點只想讓你知道這裡有多少重複的內容,讓你知道為何範疇學要統一這些概念。
205 |
206 | 在範疇學中,有一個概念稱之為⋯範疇。有以下 component 的 collection 就構成一個範疇:
207 |
208 | * object 的 collection
209 | * 態射的 collection
210 | * 態射的組合
211 | * 一個名為 identity 獨特的態射
212 |
213 | 範疇學抽象到可以模擬任何事物,不過我們目前最關心的還是類型及 function,所以讓我們將範疇學運用到它們身上看看。
214 |
215 | **Object 的 collection**
216 | Object 就是資料類型。例如:``String``、``Boolean``、``Number`` 及 ``Object`` 等等。通常我們把資料類型作為所有可能值的一個集合(set)。像是 ``Boolean`` 就是 `[true, false]` 的集合,``Number`` 可以是所有實數的集合。把類型當作集合是有好處的,因為我們可以利用集合論處理類型。
217 |
218 |
219 | **態射的 collection**
220 | 態射會是標準的 pure function。
221 |
222 | **態射的組合**
223 | 你可以已經猜到了,這就是本章所介紹的新玩具-`compose`。我們已經討論過 `compose` function 是符合結合律,這並不是巧合,結合律是範疇學中對任何組合都適用的一個特性。
224 |
225 | 下圖展示了何為組合:
226 |
227 |
228 |
229 |
230 | 下方的程式碼是個具體的例子:
231 |
232 | ```js
233 | var g = function(x) {
234 | return x.length;
235 | };
236 | var f = function(x) {
237 | return x === 4;
238 | };
239 | var isFourLetterWord = compose(f, g);
240 | ```
241 |
242 | **一個名為 identity 獨特的態射**
243 | 讓我們來介紹一個名為 `id` 的實用 function。這個 function 只是接受隨便的輸入然後原封不動的還給你。如下:
244 |
245 | ```js
246 | var id = function(x) {
247 | return x;
248 | };
249 | ```
250 |
251 | 你可能想問「這到底哪裡有用了?」。我們會在之後的幾個章節擴增這個 function,暫時將它當作一個可以替代給定值的 function-一個假裝自己是資料的 function。
252 |
253 | `id` 與 compose 簡直是天作之合。下面這個特性對所有的 unary function(一元 function:只接受一個參數的 function)f 都成立:
254 |
255 | ```js
256 | // identity
257 | compose(id, f) == compose(f, id) == f;
258 | // true
259 | ```
260 |
261 | 嘿,這不就是實數的單一律(identity property)阿!如過這還不夠清楚明瞭,就慢慢理解它的無用性吧。我們很快會到處使用 `id`,但現在我們暫時將它當作是個替代給定值得 function 。這對於撰寫 pointfree 的程式碼相當有用。
262 |
263 | 好了,以上就是類型和 function 的範疇。如果這是你第一次聽說這些概念,我猜測你現在還有些不瞭解,不懂範疇為何及為何有用。沒關係,本書都會借助這些知識。至於現在,本章的本行中,你至少可以認為它向我們提供了有關 compose 的知識-例如結合律與單一律。
264 |
265 | 除了這些,還有哪些範疇呢?當然,我們可以定義一個向量圖,以結點為 object,邊為態射,以路徑連接為組合。我們可以定義一個實數為 object,`>=` 為態射(事實上任何偏序(partial order)及全序(total order)都可成為一個範疇)。範疇的總數是無上限的,但是要達到本書的目的,我們只需關心上方所定義的範疇即可。到目前我們已經瀏覽了一些表面的東西,接著必須進入下一章節了。
266 |
267 |
268 | ## 總結
269 | Compose 將我們的的 function 連結在一起,就像一條管線一樣。資料也會在我們的應用程式中流動-畢竟 pure function 就是輸入對輸出,所以打破這個鏈結就是不遵重輸出,會讓我們的應用程式一無是處。
270 |
271 | 我們認為 compose 是高於其他原則的設計模式,這是因為 compose 讓我們的程式簡單而可讀。範疇學會在應用程式架構、模擬副作用及保證正確性方面扮演重要的角色。
272 |
273 | 現在我們已經有足夠的知識去進行一些實際的練習了,讓我們來撰寫一個範例應用程式。
274 |
275 | [第 6 章:範例應用程式](ch6.md)
276 |
277 | ## 練習
278 |
279 | ```js
280 | var _ = require('ramda');
281 | var accounting = require('accounting');
282 |
283 | // 範例資料
284 | var CARS = [{
285 | name: 'Ferrari FF',
286 | horsepower: 660,
287 | dollar_value: 700000,
288 | in_stock: true,
289 | }, {
290 | name: 'Spyker C12 Zagato',
291 | horsepower: 650,
292 | dollar_value: 648000,
293 | in_stock: false,
294 | }, {
295 | name: 'Jaguar XKR-S',
296 | horsepower: 550,
297 | dollar_value: 132000,
298 | in_stock: false,
299 | }, {
300 | name: 'Audi R8',
301 | horsepower: 525,
302 | dollar_value: 114200,
303 | in_stock: false,
304 | }, {
305 | name: 'Aston Martin One-77',
306 | horsepower: 750,
307 | dollar_value: 1850000,
308 | in_stock: true,
309 | }, {
310 | name: 'Pagani Huayra',
311 | horsepower: 700,
312 | dollar_value: 1300000,
313 | in_stock: false,
314 | }];
315 |
316 | // 練習 1:
317 | // ============
318 | // 使用 _.compose() 重寫以下的 function。提示: _.prop() 是 curry function。
319 | var isLastInStock = function(cars) {
320 | var last_car = _.last(cars);
321 | return _.prop('in_stock', last_car);
322 | };
323 |
324 | // 練習 2:
325 | // ============
326 | // 使用 _.compose()、 _.prop() 及 _.head() 來取得第一筆 car 的 name。
327 | var nameOfFirstCar = undefined;
328 |
329 |
330 | // 練習 3:
331 | // ============
332 | // 使用 helper function _average 來重構 averageDollarValue 使之為 compose function。
333 | var _average = function(xs) {
334 | return _.reduce(_.add, 0, xs) / xs.length;
335 | }; // <- 不需改動
336 |
337 | var averageDollarValue = function(cars) {
338 | var dollar_values = _.map(function(c) {
339 | return c.dollar_value;
340 | }, cars);
341 | return _average(dollar_values);
342 | };
343 |
344 |
345 | // 練習 4:
346 | // ============
347 | // 使用 compose 撰寫一個 function:sanitizeNames(),回傳一個 car 的 name 為全小寫及底線連接的列表:例如:sanitizeNames([{name: 'Ferrari FF', horsepower: 660, dollar_value: 700000, in_stock: true}]) //=> ['ferrari_ff']。
348 |
349 | var _underscore = _.replace(/\W+/g, '_'); //<-- leave this alone and use to sanitize
350 |
351 | var sanitizeNames = undefined;
352 |
353 |
354 | // 加分題 1:
355 | // ============
356 | // 使用 compose 重構 availablePrices。
357 |
358 | var availablePrices = function(cars) {
359 | var available_cars = _.filter(_.prop('in_stock'), cars);
360 | return available_cars.map(function(x) {
361 | return accounting.formatMoney(x.dollar_value);
362 | }).join(', ');
363 | };
364 |
365 |
366 | // 加分題 2:
367 | // ============
368 | // 重構使它 pointfree。提示: 你可以使用 _.flip()。
369 |
370 | var fastestCar = function(cars) {
371 | var sorted = _.sortBy(function(car) {
372 | return car.horsepower;
373 | }, cars);
374 | var fastest = _.last(sorted);
375 | return fastest.name + ' is the fastest';
376 | };
377 | ```
378 |
379 | [lodash-website]: https://lodash.com/
380 | [underscore-website]: http://underscorejs.org/
381 | [ramda-website]: http://ramdajs.com/
382 | [refactoring-book]: http://martinfowler.com/books/refactoring.html
383 | [extract-method-refactor]: http://refactoring.com/catalog/extractMethod.html
384 |
--------------------------------------------------------------------------------
/ch6.md:
--------------------------------------------------------------------------------
1 | # 第 6 章:範例應用程式
2 |
3 | ## 宣告式開發
4 |
5 | 從本章節開始,我們要開始轉變我們的觀念了,我們會停止告訴電腦該要怎麼去工作,而是透過撰寫規範來得到我們要的結果。我相信透過這種方式與無時無刻去關心所有程式碼細節相比,這會讓你感到輕鬆許多。
6 |
7 | 不同於命令式,宣告式意指我們將撰寫一些表達式的程式,而不是一步一步指示。
8 |
9 | 像是 SQL,就沒有「先做這個,再做那個」的命令。它有一個表達式來指定我們想要從哪個資料庫使用資料,我們不確定是如何執行的,要看表達式本身。當資料庫更新和 SQL 引擎優化,我們不需要改變我們查詢的方式。這是因為有許多方式來解析我們規範的表達式並得到相同的結果。
10 |
11 | 對於一些人來說,包含我自己,第一次很難掌握這種宣告式的概念,所讓我們列出一些範例來感受一下。
12 |
13 | ```js
14 | // 命令式
15 | var makes = [];
16 | for (var i = 0; i < cars.length; i++) {
17 | makes.push(cars[i].make);
18 | }
19 |
20 |
21 | // 宣告式
22 | var makes = cars.map(function(car) { return car.make; });
23 | ```
24 |
25 | 命令式第一次必須先實例化陣列。在執行後面的程式碼之前,直譯器必須先評估這個語句,然後才迭代整個 cars 的清單,在顯式的迭代中,手動增加計數器並顯示零碎的資訊給我們實在是不怎麼好。
26 |
27 | `map` 版本是一個表達式。它對執行的順序沒有要求。在 map function 迭代並回傳的陣列集合,對於指定**做什麼**而不是**怎麼做**有很大的自由度。因此,它完全是一個宣告式的程式。
28 |
29 | 除了更簡潔明瞭外,map function 還可以進行優化,這樣我們的應用程式的程式碼就不需要改變了。
30 |
31 | 對那些認為「對啊,但是使用命令式迴圈比較快」的人,我建議你先去了解關於 JIT 優化的相關程式碼。這裡有一個[非常棒的影片](https://www.youtube.com/watch?v=65-RbBwZQdU),或許可以有一些啟發。
32 |
33 | 這裡是另一個範例。
34 |
35 | ```js
36 | // 命令式
37 | var authenticate = function(form) {
38 | var user = toUser(form);
39 | return logIn(user);
40 | };
41 |
42 | // 宣告式
43 | var authenticate = compose(logIn, toUser);
44 | ```
45 |
46 | 雖然命令式的版本不是絕對錯誤的,但還是像存在那種一步一步的硬編碼方式。`compose` 表達式只是簡單的指出一個事實:驗證是 `toUser` 和 `logIn` 兩個行為的組合。再者,宣告式的程式支援更新程式碼,使我們的應用程式可以成為一個高級的規範。
47 |
48 | 因為宣告式的程式不指定執行順序,所以適合運用在平行計算。它與 pure function 一起解釋了 functional programming 對於平行計算的未來是一個很好的選擇 - 我們真的不需要做什麼就能實現平行化的系統。
49 |
50 | ## 一個 FP 的 flickr
51 |
52 | 我們使用宣告式和可組合的方式來建立一個應用程式範例。我們現在還是會使到一些 side effects,但我們會把 side effects 降到最低,讓他與 pure function 的部份分離開來。我們要建立一個瀏覽器的 widget,從 flickr 上取得圖片並顯示。讓我們從 app 的 scaffolding 開始。這裡是 html 部份:
53 |
54 |
55 | ```html
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | ```
65 |
66 | 這裡是 flickr.js 的 skeleton:
67 |
68 | ```js
69 | requirejs.config({
70 | paths: {
71 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min',
72 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min'
73 | },
74 | });
75 |
76 | require([
77 | 'ramda',
78 | 'jquery',
79 | ],
80 | function(_, $) {
81 | var trace = _.curry(function(tag, x) {
82 | console.log(tag, x);
83 | return x;
84 | });
85 | // app 在此處
86 | });
87 | ```
88 |
89 | 這裡我們使用了 [ramda](http://ramdajs.com) 而不是 lodash 或其他的 library。它包含了 `compose`、`curry` 等等。我以前使用過 requirejs,或許看起來有點矯枉過正,但為了保持一致性,在本書我們會一直使用它。另外,我已經將 `trace` function 寫好,讓我們可以方便的 debug。
90 |
91 | 有點離題了,言歸正傳,我們的 app 需要做以下這四件事情:
92 |
93 | 1. 對於我們特定的搜尋條件來建構一個 url
94 | 2. 讓 flickr api 呼叫
95 | 3. 將回傳的 json 結果轉換成 html 的圖片
96 | 4. 將圖片放置在螢幕上
97 |
98 | 如上面所述,有兩個 impure 的行為。你看到了嗎?就是從 flickr api 取得資料和在螢幕上放置圖片。我們先來定義這兩個動作,這樣就可以隔離他們。
99 |
100 | ```js
101 | var Impure = {
102 | getJSON: _.curry(function(callback, url) {
103 | $.getJSON(url, callback);
104 | }),
105 |
106 | setHtml: _.curry(function(sel, html) {
107 | $(sel).html(html);
108 | })
109 | };
110 | ```
111 |
112 | 這裡我們簡單的將 jQuery 的方法包裝成 curry,這有便於幫助參數位置的交換。我已經使用了 `Impure` 的命名空間,這樣我們就知道這些 function 不安全。在之後的範例,我們將會讓這兩個 function 變為 pure function。
113 |
114 | 接下來我們必須建構一個 url 來傳送我們的 `Impure.getJson` function。
115 |
116 | ```js
117 | var url = function(term) {
118 | return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' +
119 | term + '&format=json&jsoncallback=?';
120 | };
121 | ```
122 |
123 | 使用 monoids(我們在之後會學習到)或 combinator 可以使用一些奇技淫巧讓 `url` function 成為 `pointfree` function。但是為了可讀性,我們還是選擇以普通非 pointfree 的方式拼接字串。
124 |
125 | 讓我們撰寫一個 app function 來發送呼叫,並將內容顯示在螢幕上。
126 |
127 | ```js
128 | var app = _.compose(Impure.getJSON(trace('response')), url);
129 |
130 | app('cats');
131 | ```
132 |
133 | 這會呼叫 `url` function,然後將字串傳送給 `getJSON` function,在某些部份上已經應用到了 `trace`。載入 app 後,從 api 呼叫的 response 會顯示在 console 上。
134 |
135 |
136 |
137 | 我們想要從 json 來建構圖片。看起来 src 都在 `items` 陣列中的每個 `media` 物件的 `m` 屬性上。
138 |
139 | 不管如何,如果要取得這些巢狀的屬性,我們可以從 ramda 中使用一個很棒的通用 getter function 叫做 `_.prop()`。不過為了讓你了解這個 function 做了些什麼,我們先自己實現一個 prop:
140 |
141 | ```js
142 | var prop = _.curry(function(property, object) {
143 | return object[property];
144 | });
145 | ```
146 |
147 | 這實際上有點傻,我們只是使用 `[]` 語法來存取物件的屬性。讓我們使用它來取得我們的 src:
148 |
149 | ```js
150 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
151 |
152 | var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
153 | ```
154 |
155 | 一旦得到 `items`,我們必須使用 `map` 來提取每個 media 的 url。這樣就會得到一個 src 的陣列。讓我們將它加到 app 上,並將圖片顯示在螢幕上。
156 |
157 | ```js
158 | var renderImages = _.compose(Impure.setHtml('body'), srcs);
159 | var app = _.compose(Impure.getJSON(renderImages), url);
160 | ```
161 |
162 | 這裡所做的只不過是建立了一個組合,這個组合會呼叫 `srcs` function,並把回傳结果設定為 body 的 html。我們也把 `trace` 替換成了 `renderImages`,現在我們除了 render 原始的 json,會將我們的 src 直接顯示在我們的 body。
163 |
164 | 我們最後一步是將這些 src 轉換成真正的圖片。在大型應用程式中,我們使用像是 Handlebars 或 React 這樣的 template/dom library。但是對於這個應用程式來說,我們只需要一個 img 標籤,所以只要使用 jQuery 就可以了。
165 |
166 | ```js
167 | var img = function(url) {
168 | return $('
', {
169 | src: url
170 | });
171 | };
172 | ```
173 |
174 | jQuery 的 `html()` 方法接受一個標籤陣列。我們只要將 src 轉換成圖片並傳送給 `setHtml` 就可以了。
175 |
176 | ```js
177 | var images = _.compose(_.map(img), srcs);
178 | var renderImages = _.compose(Impure.setHtml('body'), images);
179 | var app = _.compose(Impure.getJSON(renderImages), url);
180 | ```
181 |
182 | 我們完成了!
183 |
184 |
185 |
186 | 這裡是完成後的程式碼:
187 | ```js
188 | requirejs.config({
189 | paths: {
190 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min',
191 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min',
192 | },
193 | });
194 |
195 | require([
196 | 'ramda',
197 | 'jquery',
198 | ],
199 | function(_, $) {
200 | ////////////////////////////////////////////
201 | // Utils
202 |
203 | var Impure = {
204 | getJSON: _.curry(function(callback, url) {
205 | $.getJSON(url, callback);
206 | }),
207 |
208 | setHtml: _.curry(function(sel, html) {
209 | $(sel).html(html);
210 | }),
211 | };
212 |
213 | var img = function(url) {
214 | return $('
', {
215 | src: url,
216 | });
217 | };
218 |
219 | var trace = _.curry(function(tag, x) {
220 | console.log(tag, x);
221 | return x;
222 | });
223 |
224 | ////////////////////////////////////////////
225 |
226 | var url = function(t) {
227 | return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' +
228 | t + '&format=json&jsoncallback=?';
229 | };
230 |
231 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
232 |
233 | var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
234 |
235 | var images = _.compose(_.map(img), srcs);
236 |
237 | var renderImages = _.compose(Impure.setHtml('body'), images);
238 |
239 | var app = _.compose(Impure.getJSON(renderImages), url);
240 |
241 | app('cats');
242 | });
243 | ```
244 |
245 | 現在看看這些,多麼美妙的宣告式規範啊!現在我們可以把每一行程式碼都視為方程式和屬性。我們可以使用這些屬性去合理判斷關於我們應用程式以及重構。
246 |
247 | ## 有原則的重構
248 |
249 | 上面的程式碼還是可以優化的,我們 map 每個項目將它們轉換成 media url,然後我們再 map src 將它們轉換成 img 的標籤。關於 map 和組合是有定律的:
250 |
251 |
252 | ```js
253 | // map 的結合律
254 | var law = compose(map(f), map(g)) === map(compose(f, g));
255 | ```
256 |
257 | 我們可以利用這個定律優化程式碼,進行一次有原則的重構。
258 |
259 | ```js
260 | // 原始程式碼
261 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
262 |
263 | var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
264 |
265 | var images = _.compose(_.map(img), srcs);
266 |
267 | ```
268 |
269 | 讓我們將 map 排序吧。感謝等式推導(equational reasoning)以及 pure function 的特性,我們可以在 `images` 呼叫 `srcs`。
270 |
271 | ```js
272 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
273 |
274 | var images = _.compose(_.map(img), _.map(mediaUrl), _.prop('items'));
275 | ```
276 |
277 | 把 `map` 排成一列後,就可以應用結合律了。
278 |
279 | ```js
280 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
281 |
282 | var images = _.compose(_.map(_.compose(img, mediaUrl)), _.prop('items'));
283 | ```
284 |
285 | 現在只需要一次迴圈,就可以把每個物件都轉換成一個 img 了。我們透過提取 function,讓 function 可以變得更具可讀性。
286 |
287 | ```js
288 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
289 |
290 | var mediaToImg = _.compose(img, mediaUrl);
291 |
292 | var images = _.compose(_.map(mediaToImg), _.prop('items'));
293 | ```
294 |
295 | ## 總結
296 |
297 | 我們已經看到如何使用一個小而巧的新技術放入我們的真實應用的 app。我們使用我們的數學框架來思考和重構我們的程式碼。但是錯誤處理的程式碼部分呢?我們如何讓整個應用程式都是 pure 的,而不是將破壞性的 function 放入到命名空間下?我們如何使我們的應用程式更安全且更具有表現?這是本書在第二部分將要處理的問題。
298 |
299 | [第 7 章:Hindley-Milner 與我](ch7.md)
300 |
--------------------------------------------------------------------------------
/ch7.md:
--------------------------------------------------------------------------------
1 | # Chapter 7: Hindley-Milner and Me
2 |
3 | ## What's your type?
4 | If you're new to the functional world, it won't be long before you find yourself knee deep in type signatures. Types are the meta language that enables people from all different backgrounds to communicate succinctly and effectively. For the most part, they're written with a system called "Hindley-Milner", which we'll be examining together in this chapter.
5 |
6 | When working with pure functions, type signatures have an expressive power to which the English language cannot hold a candle. These signatures whisper in your ear the intimate secrets of a function. In a single, compact line, they expose behaviour and intention. We can derive "free theorems" from them. Types can be inferred so there's no need for explicit type annotations. They can be tuned to fine point precision or left general and abstract. They are not only useful for compile time checks, but also turn out to be the best possible documentation available. Type signatures thus play an important part in functional programming - much more than you might first expect.
7 |
8 | JavaScript is a dynamic language, but that does not mean we avoid types all together. We're still working with strings, numbers, booleans, and so on. It's just that there isn't any language level integration so we hold this information in our heads. Not to worry, since we're using signatures for documentation, we can use comments to serve our purpose.
9 |
10 | There are type checking tools available for JavaScript such as [Flow](http://flowtype.org/) or the typed dialect, [TypeScript](http://www.typescriptlang.org/). The aim of this book is to equip one with the tools to write functional code so we'll stick with the standard type system used across FP languages.
11 |
12 |
13 | ## Tales from the cryptic
14 |
15 | From the dusty pages of math books, across the vast sea of white papers, amongst casual saturday morning blog posts, down into the source code itself, we find Hindley-Milner type signatures. The system is quite simple, but warrants a quick explanation and some practice to fully absorb the little language.
16 |
17 | ```js
18 | // capitalize :: String -> String
19 | var capitalize = function(s) {
20 | return toUpperCase(head(s)) + toLowerCase(tail(s));
21 | }
22 |
23 | capitalize("smurf");
24 | //=> "Smurf"
25 | ```
26 |
27 | Here, `capitalize` takes a `String` and returns a `String`. Never mind the implementation, it's the type signature we're interested in.
28 |
29 | In HM, functions are written as `a -> b` where `a` and `b` are variables for any type. So the signatures for `capitalize` can be read as "a function from `String` to `String`". In other words, it takes a `String` as its input and returns a `String` as its output.
30 |
31 | Let's look at some more function signatures:
32 |
33 | ```js
34 | // strLength :: String -> Number
35 | var strLength = function(s) {
36 | return s.length;
37 | }
38 |
39 | // join :: String -> [String] -> String
40 | var join = curry(function(what, xs) {
41 | return xs.join(what);
42 | });
43 |
44 | // match :: Regex -> String -> [String]
45 | var match = curry(function(reg, s) {
46 | return s.match(reg);
47 | });
48 |
49 | // replace :: Regex -> String -> String -> String
50 | var replace = curry(function(reg, sub, s) {
51 | return s.replace(reg, sub);
52 | });
53 | ```
54 |
55 | `strLength` is the same idea as before: we take a `String` and return you a `Number`.
56 |
57 | The others might perplex you at first glance. Without fully understanding the details, you could always just view the last type as the return value. So for `match` you can interpret as: It takes a `Regex` and a `String` and returns you `[String]`. But an interesting thing is going on here that I'd like to take a moment to explain if I may.
58 |
59 | For `match` we are free to group the signature like so:
60 |
61 | ```js
62 | // match :: Regex -> (String -> [String])
63 | var match = curry(function(reg, s) {
64 | return s.match(reg);
65 | });
66 | ```
67 |
68 | Ah yes, grouping the last part in parenthesis reveals more information. Now it is seen as a function that takes a `Regex` and returns us a function from `String` to `[String]`. Because of currying, this is indeed the case: give it a `Regex` and we get a function back waiting for its `String` argument. Of course, we don't have to think of it this way, but it is good to understand why the last type is the one returned.
69 |
70 | ```js
71 | // match :: Regex -> (String -> [String])
72 |
73 | // onHoliday :: String -> [String]
74 | var onHoliday = match(/holiday/ig);
75 | ```
76 |
77 | Each argument pops one type off the front of the signature. `onHoliday` is `match` that already has a `Regex`.
78 |
79 | ```js
80 | // replace :: Regex -> (String -> (String -> String))
81 | var replace = curry(function(reg, sub, s) {
82 | return s.replace(reg, sub);
83 | });
84 | ```
85 |
86 | As you can see with the full parenthesis on `replace`, the extra notation can get a little noisy and redundant so we simply omit them. We can give all the arguments at once if we choose so it's easier to just think of it as: `replace` takes a `Regex`, a `String`, another `String` and returns you a `String`.
87 |
88 | A few last things here:
89 |
90 |
91 | ```js
92 | // id :: a -> a
93 | var id = function(x) {
94 | return x;
95 | }
96 |
97 | // map :: (a -> b) -> [a] -> [b]
98 | var map = curry(function(f, xs) {
99 | return xs.map(f);
100 | });
101 | ```
102 |
103 | The `id` function takes any old type `a` and returns something of the same type `a`. We're able to use variables in types just like in code. Variable names like `a` and `b` are convention, but they are arbitrary and can be replaced with whatever name you'd like. If they are the same variable, they have to be the same type. That's an important rule so let's reiterate: `a -> b` can be any type `a` to any type `b`, but `a -> a` means it has to be the same type. For example, `id` may be `String -> String` or `Number -> Number`, but not `String -> Bool`.
104 |
105 | `map` similarly uses type variables, but this time we introduce `b` which may or may not be the same type as `a`. We can read it as: `map` takes a function from any type `a` to the same or different type `b`, then takes an array of `a`'s and results in an array of `b`'s.
106 |
107 | Hopefully, you've been overcome by the expressive beauty in this type signature. It literally tells us what the function does almost word for word. It's given a function from `a` to `b`, an array of `a`, and it delivers us an array of `b`. The only sensible thing for it to do is call the bloody function on each `a`. Anything else would be a bold face lie.
108 |
109 | Being able to reason about types and their implications is a skill that will take you far in the functional world. Not only will papers, blogs, docs, etc, become more digestible, but the signature itself will practically lecture you on its functionality. It takes practice to become a fluent reader, but if you stick with it, heaps of information will become available to you sans RTFMing.
110 |
111 | Here's a few more just to see if you can decipher them on your own.
112 |
113 | ```js
114 | // head :: [a] -> a
115 | var head = function(xs) {
116 | return xs[0];
117 | };
118 |
119 | // filter :: (a -> Bool) -> [a] -> [a]
120 | var filter = curry(function(f, xs) {
121 | return xs.filter(f);
122 | });
123 |
124 | // reduce :: (b -> a -> b) -> b -> [a] -> b
125 | var reduce = curry(function(f, x, xs) {
126 | return xs.reduce(f, x);
127 | });
128 | ```
129 |
130 | `reduce` is perhaps, the most expressive of all. It's a tricky one, however, so don't feel inadequate should you struggle with it. For the curious, I'll try to explain in English though working through the signature on your own is much more instructive.
131 |
132 | Ahem, here goes nothing....looking at the signature, we see the first argument is a function that expects a `b`, an `a`, and produces a `b`. Where might it get these `a`s and `b`s? Well, the following arguments in the signature are a `b` and an array of `a`s so we can only assume that the `b` and each of those `a`s will be fed in. We also see that the result of the function is a `b` so the thinking here is our final incantation of the passed in function will be our output value. Knowing what reduce does, we can state that the above investigation is accurate.
133 |
134 |
135 | ## Narrowing the possibility
136 |
137 | Once a type variable is introduced, there emerges a curious property called *parametricity*(http://en.wikipedia.org/wiki/Parametricity). This property states that a function will *act on all types in a uniform manner*. Let's investigate:
138 |
139 | ```js
140 | // head :: [a] -> a
141 | ```
142 |
143 | Looking at `head`, we see that it takes `[a]` to `a`. Besides the concrete type `array`, it has no other information available and, therefore, its functionality is limited to working on the array alone. What could it possibly do with the variable `a` if it knows nothing about it? In other words, `a` says it cannot be a *specific* type, which means it can be *any* type, which leaves us with a function that must work uniformly for *every* conceivable type. This is what *parametricity* is all about. Guessing at the implementation, the only reasonable assumptions are that it takes the first, last, or a random element from that array. The name `head` should tip us off.
144 |
145 | Here's another one:
146 |
147 | ```js
148 | // reverse :: [a] -> [a]
149 | ```
150 |
151 | From the type signature alone, what could `reverse` possibly be up to? Again, it cannot do anything specific to `a`. It cannot change `a` to a different type or we'd introduce a `b`. Can it sort? Well, no, it wouldn't have enough information to sort every possible type. Can it re-arrange? Yes, I suppose it can do that, but it has to do so in exactly the same predictable way. Another possibility is that it may decide to remove or duplicate an element. In any case, the point is, the possible behaviour is massively narrowed by its polymorphic type.
152 |
153 | This narrowing of possibility allows us to use type signature search engines like [Hoogle](https://www.haskell.org/hoogle) to find a function we're after. The information packed tightly into a signature is quite powerful indeed.
154 |
155 | ## Free as in theorem
156 |
157 | Besides deducing implementation possibilities, this sort of reasoning gains us *free theorems*. What follows are a few random example theorems lifted directly from [Wadler's paper on the subject](http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf).
158 |
159 | ```js
160 | // head :: [a] -> a
161 | compose(f, head) == compose(head, map(f));
162 |
163 | // filter :: (a -> Bool) -> [a] -> [a]
164 | compose(map(f), filter(compose(p, f))) === compose(filter(p), map(f));
165 | ```
166 |
167 |
168 | You don't need any code to get these theorems, they follow directly from the types. The first one says that if we get the `head` of our array, then run some function `f` on it, that is equivalent to, and incidentally, much faster than, if we first `map(f)` over every element then take the `head` of the result.
169 |
170 | You might think, well that's just common sense. But last I checked, computers don't have common sense. Indeed, they must have a formal way to automate these kind of code optimizations. Maths has a way of formalizing the intuitive, which is helpful amidst the rigid terrain of computer logic.
171 |
172 | The `filter` theorem is similar. It says that if we compose `f` and `p` to check which should be filtered, then actually apply the `f` via `map` (remember filter, will not transform the elements - its signature enforces that `a` will not be touched), it will always be equivalent to mapping our `f` then filtering the result with the `p` predicate.
173 |
174 | These are just two examples, but you can apply this reasoning to any polymorphic type signature and it will always hold. In JavaScript, there are some tools available to declare rewrite rules. One might also do this via the `compose` function itself. The fruit is low hanging and the possibilities are endless.
175 |
176 | ## Constraints
177 |
178 | One last thing to note is that we can constrain types to an interface.
179 |
180 | ```js
181 | // sort :: Ord a => [a] -> [a]
182 | ```
183 |
184 | What we see on the left side of our fat arrow here is the statement of a fact: `a` must be an `Ord`. Or in other words, `a` must implement the `Ord` interface. What is `Ord` and where did it come from? In a typed language it would be a defined interface that says we can order the values. This not only tells us more about the `a` and what our `sort` function is up to, but also restricts the domain. We call these interface declarations *type constraints*.
185 |
186 | ```js
187 | // assertEqual :: (Eq a, Show a) => a -> a -> Assertion
188 | ```
189 |
190 | Here, we have two constraints: `Eq` and `Show`. Those will ensure that we can check equality of our `a`s and print the difference if they are not equal.
191 |
192 | We'll see more examples of constraints and the idea should take more shape in later chapters.
193 |
194 |
195 | ## In Summary
196 |
197 | Hindley-Milner type signatures are ubiquitous in the functional world. Though they are simple to read and write, it takes time to master the technique of understanding programs through signatures alone. We will add type signatures to each line of code from here on out.
198 |
199 | [Chapter 8: Tupperware](ch8.md)
200 |
--------------------------------------------------------------------------------
/ch8.md:
--------------------------------------------------------------------------------
1 | # Chapter 8: Tupperware
2 |
3 | ## The Mighty Container
4 |
5 |
6 |
7 | We've seen how to write programs which pipe data through a series of pure functions. They are declarative specifications of behaviour. But what about control flow, error handling, asynchronous actions, state and, dare I say, effects?! In this chapter, we will discover the foundation upon which all of these helpful abstractions are built.
8 |
9 | First we will create a container. This container must hold any type of value; a ziplock that holds only tapioca pudding is rarely useful. It will be an object, but we will not give it properties and methods in the OO sense. No, we will treat it like a treasure chest - a special box that cradles our valuable data.
10 |
11 | ```js
12 | var Container = function(x) {
13 | this.__value = x;
14 | }
15 |
16 | Container.of = function(x) { return new Container(x); };
17 | ```
18 |
19 | Here is our first container. We've thoughtfully named it `Container`. We will use `Container.of` as a constructor which saves us from having to write that god awful `new` keyword all over the place. There's more to the `of` function than meets the eye, but for now, think of it as the proper way to place values into our container.
20 |
21 | Let's examine our brand new box...
22 |
23 | ```js
24 | Container.of(3);
25 | //=> Container(3)
26 |
27 |
28 | Container.of('hotdogs');
29 | //=> Container("hotdogs")
30 |
31 |
32 | Container.of(Container.of({
33 | name: 'yoda',
34 | }));
35 | //=> Container(Container({name: "yoda" }))
36 | ```
37 |
38 | If you are using node, you will see `{__value: x}` even though we've got ourselves a `Container(x)`. Chrome will output the type properly, but no matter; as long as we understand what a `Container` looks like, we'll be fine. In some environments you can overwrite the `inspect` method if you'd like, but we will not be so thorough. For this book, we will write the conceptual output as if we'd overwritten `inspect` as it's much more instructive than `{__value: x}` for pedagogical as well as aesthetic reasons.
39 |
40 | Let's make a few things clear before we move on:
41 |
42 | * `Container` is an object with one property. Lots of containers just hold one thing, though they aren't limited to one. We've arbitrarily named its property `__value`.
43 |
44 | * The `__value` cannot be one specific type or our `Container` would hardly live up to the name.
45 |
46 | * Once data goes into the `Container` it stays there. We *could* get it out by using `.__value`, but that would defeat the purpose.
47 |
48 | The reasons we're doing this will become clear as a mason jar, but for now, bear with me.
49 |
50 | ## My First Functor
51 |
52 | Once our value, whatever it may be, is in the container, we'll need a way to run functions on it.
53 |
54 | ```js
55 | // (a -> b) -> Container a -> Container b
56 | Container.prototype.map = function(f) {
57 | return Container.of(f(this.__value));
58 | }
59 | ```
60 |
61 | Why, it's just like Array's famous `map`, except we have `Container a` instead of `[a]`. And it works essentially the same way:
62 |
63 | ```js
64 | Container.of(2).map(function(two) {
65 | return two + 2;
66 | });
67 | //=> Container(4)
68 |
69 |
70 | Container.of("flamethrowers").map(function(s) {
71 | return s.toUpperCase();
72 | });
73 | //=> Container("FLAMETHROWERS")
74 |
75 |
76 | Container.of("bombs").map(_.concat(' away')).map(_.prop('length'));
77 | //=> Container(10)
78 | ```
79 |
80 | We can work with our value without ever having to leave the `Container`. This is a remarkable thing. Our value in the `Container` is handed to the `map` function so we can fuss with it and afterward, returned to its `Container` for safe keeping. As a result of never leaving the `Container`, we can continue to `map` away, running functions as we please. We can even change the type as we go along as demonstrated in the latter of the three examples.
81 |
82 | Wait a minute, if we keep calling `map`, it appears to be some sort of composition! What mathematical magic is at work here? Well chaps, we've just discovered *Functors*.
83 |
84 | > A Functor is a type that implements `map` and obeys some laws
85 |
86 | Yes, *Functor* is simply an interface with a contract. We could have just as easily named it *Mappable*, but now, where's the *fun* in that? Functors come from category theory and we'll look at the maths in detail toward the end of the chapter, but for now, let's work on intuition and practical uses for this bizarrely named interface.
87 |
88 | What reason could we possibly have for bottling up a value and using `map` to get at it? The answer reveals itself if we choose a better question: What do we gain from asking our container to apply functions for us? Well, abstraction of function application. When we `map` a function, we ask the container type to run it for us. This is a very powerful concept, indeed.
89 |
90 | ## Schrödinger's Maybe
91 |
92 |
93 |
94 | `Container` is fairly boring. In fact, it is usually called `Identity` and has about the same impact as our `id` function(again there is a mathematical connection we'll look at when the time is right). However, there are other functors, that is, container-like types that have a proper `map` function, which can provide useful behaviour whilst mapping. Let's define one now.
95 |
96 | ```js
97 | var Maybe = function(x) {
98 | this.__value = x;
99 | };
100 |
101 | Maybe.of = function(x) {
102 | return new Maybe(x);
103 | };
104 |
105 | Maybe.prototype.isNothing = function() {
106 | return (this.__value === null || this.__value === undefined);
107 | };
108 |
109 | Maybe.prototype.map = function(f) {
110 | return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
111 | };
112 | ```
113 |
114 | Now, `Maybe` looks a lot like `Container` with one minor change: it will first check to see if it has a value before calling the supplied function. This has the effect of side stepping those pesky nulls as we `map`(Note that this implementation is simplified for teaching).
115 |
116 | ```js
117 | Maybe.of('Malkovich Malkovich').map(match(/a/ig));
118 | //=> Maybe(['a', 'a'])
119 |
120 | Maybe.of(null).map(match(/a/ig));
121 | //=> Maybe(null)
122 |
123 | Maybe.of({
124 | name: 'Boris',
125 | }).map(_.prop('age')).map(add(10));
126 | //=> Maybe(null)
127 |
128 | Maybe.of({
129 | name: 'Dinah',
130 | age: 14,
131 | }).map(_.prop('age')).map(add(10));
132 | //=> Maybe(24)
133 | ```
134 |
135 | Notice our app doesn't explode with errors as we map functions over our null values. This is because `Maybe` will take care to check for a value each and every time it applies a function.
136 |
137 | This dot syntax is perfectly fine and functional, but for reasons mentioned in Part 1, we'd like to maintain our pointfree style. As it happens, `map` is fully equipped to delegate to whatever functor it receives:
138 |
139 | ```js
140 | // map :: Functor f => (a -> b) -> f a -> f b
141 | var map = curry(function(f, any_functor_at_all) {
142 | return any_functor_at_all.map(f);
143 | });
144 | ```
145 |
146 | This is delightful as we can carry on with composition per usual and `map` will work as expected. This is the case with ramda's `map` as well. We'll use dot notation when it's instructive and the pointfree version when it's convenient. Did you notice that? I've sneakily introduced extra notation into our type signature. The `Functor f =>` tells us that `f` must be a Functor. Not that difficult, but I felt I should mention it.
147 |
148 | ## Use cases
149 |
150 | In the wild, we'll typically see `Maybe` used in functions which might fail to return a result.
151 |
152 | ```js
153 | // safeHead :: [a] -> Maybe(a)
154 | var safeHead = function(xs) {
155 | return Maybe.of(xs[0]);
156 | };
157 |
158 | var streetName = compose(map(_.prop('street')), safeHead, _.prop('addresses'));
159 |
160 | streetName({
161 | addresses: [],
162 | });
163 | // Maybe(null)
164 |
165 | streetName({
166 | addresses: [{
167 | street: 'Shady Ln.',
168 | number: 4201,
169 | }],
170 | });
171 | // Maybe("Shady Ln.")
172 | ```
173 |
174 | `safeHead` is like our normal `_.head`, but with added type safety. A curious thing happens when `Maybe` is introduced into our code; we are forced to deal with those sneaky `null` values. The `safeHead` function is honest and up front about its possible failure - there's really nothing to be ashamed of - and so it returns a `Maybe` to inform us of this matter. We are more than merely *informed*, however, because we are forced to `map` to get at the value we want since it is tucked away inside the `Maybe` object. Essentially, this is a `null` check enforced by the `safeHead` function itself. We can now sleep better at night knowing a `null` value won't rear its ugly, decapitated head when we least expect it. APIs like this will upgrade a flimsy application from paper and tacks to wood and nails. They will guarantee safer software.
175 |
176 |
177 | Sometimes a function might return a `Maybe(null)` explicitly to signal failure. For instance:
178 |
179 | ```js
180 | // withdraw :: Number -> Account -> Maybe(Account)
181 | var withdraw = curry(function(amount, account) {
182 | return account.balance >= amount ?
183 | Maybe.of({
184 | balance: account.balance - amount,
185 | }) :
186 | Maybe.of(null);
187 | });
188 |
189 | // finishTransaction :: Account -> String
190 | var finishTransaction = compose(remainingBalance, updateLedger); // <- these composed functions are hypothetical, not implemented here...
191 |
192 | // getTwenty :: Account -> Maybe(String)
193 | var getTwenty = compose(map(finishTransaction), withdraw(20));
194 |
195 |
196 | getTwenty({
197 | balance: 200.00,
198 | });
199 | // Maybe("Your balance is $180.00")
200 |
201 | getTwenty({
202 | balance: 10.00,
203 | });
204 | // Maybe(null)
205 | ```
206 |
207 | `withdraw` will tip its nose at us and return `Maybe(null)` if we're short on cash. This function also communicates its fickleness and leaves us no choice, but to `map` everything afterwards. The difference is that the `null` was intentional here. Instead of a `Maybe(String)`, we get the `Maybe(null)` back to signal failure and our application effectively halts in its tracks. This is important to note: if the `withdraw` fails, then `map` will sever the rest of our computation since it doesn't ever run the mapped functions, namely `finishTransaction`. This is precisely the intended behaviour as we'd prefer not to update our ledger or show a new balance if we hadn't successfully withdrawn funds.
208 |
209 | ## Releasing the value
210 |
211 | One thing people often miss is that there will always be an end of the line; some effecting function that sends JSON along, or prints to the screen, or alters our filesystem, or what have you. We cannot deliver the output with `return`, we must run some function or another to send it out into the world. We can phrase it like a Zen Buddhist koan: "If a program has no observable effect, does it even run?". Does it run correctly for its own satisfaction? I suspect it merely burns some cycles and goes back to sleep...
212 |
213 | Our application's job is to retrieve, transform, and carry that data along until it's time to say goodbye and the function which does so may be mapped, thus the value needn't leave the warm womb of its container. Indeed, a common error is to try to remove the value from our `Maybe` one way or another as if the possible value inside will suddenly materialize and all will be forgiven. We must understand it may be a branch of code where our value is not around to live up to its destiny. Our code, much like Schrödinger's cat, is in two states at once and should maintain that fact until the final function. This gives our code a linear flow despite the logical branching.
214 |
215 | There is, however, an escape hatch. If we would rather return a custom value and continue on, we can use a little helper called `maybe`.
216 |
217 | ```js
218 | // maybe :: b -> (a -> b) -> Maybe a -> b
219 | var maybe = curry(function(x, f, m) {
220 | return m.isNothing() ? x : f(m.__value);
221 | });
222 |
223 | // getTwenty :: Account -> String
224 | var getTwenty = compose(
225 | maybe("You're broke!", finishTransaction), withdraw(20)
226 | );
227 |
228 |
229 | getTwenty({
230 | balance: 200.00,
231 | });
232 | // "Your balance is $180.00"
233 |
234 | getTwenty({
235 | balance: 10.00,
236 | });
237 | // "You're broke!"
238 | ```
239 |
240 | We will now either return a static value (of the same type that `finishTransaction` returns) or continue on merrily finishing up the transaction sans `Maybe`. With `maybe`, we are witnessing the equivalent of an `if/else` statement whereas with `map`, the imperative analog would be: `if (x !== null) { return f(x) }`.
241 |
242 | The introduction of `Maybe` can cause some initial discomfort. Users of Swift and Scala will know what I mean as it's baked right into the core libraries under the guise of `Option(al)`. When pushed to deal with `null` checks all the time (and there are times we know with absolute certainty the value exists), most people can't help, but feel it's a tad laborious. However, with time, it will become second nature and you'll likely appreciate the safety. After all, most of the time it will prevent cut corners and save our hides.
243 |
244 | Writing unsafe software is like taking care to paint each egg with pastels before hurling it into traffic; like building a retirement home with materials warned against by three little pigs. It will do us well to put some safety into our functions and `Maybe` helps us do just that.
245 |
246 | I'd be remiss if I didn't mention that the "real" implementation will split `Maybe` into two types: one for something and the other for nothing. This allows us to obey parametricity in `map` so values like `null` and `undefined` can still be mapped over and the universal qualification of the value in a functor will be respected. You'll often see types like `Some(x) / None` or `Just(x) / Nothing` instead of a `Maybe` that does a `null` check on its value.
247 |
248 | ## Pure Error Handling
249 |
250 |
251 |
252 | It may come as a shock, but `throw/catch` is not very pure. When an error is thrown, instead of returning an output value, we sound the alarms! The function attacks, spewing thousands of 0's and 1's like shields & spears in an electric battle against our intruding input. With our new friend `Either`, we can do better than to declare war on input, we can respond with a polite message. Let's take a look:
253 |
254 | ```js
255 | var Left = function(x) {
256 | this.__value = x;
257 | };
258 |
259 | Left.of = function(x) {
260 | return new Left(x);
261 | };
262 |
263 | Left.prototype.map = function(f) {
264 | return this;
265 | };
266 |
267 | var Right = function(x) {
268 | this.__value = x;
269 | };
270 |
271 | Right.of = function(x) {
272 | return new Right(x);
273 | };
274 |
275 | Right.prototype.map = function(f) {
276 | return Right.of(f(this.__value));
277 | }
278 | ```
279 |
280 | `Left` and `Right` are two subclasses of an abstract type we call `Either`. I've skipped the ceremony of creating the `Either` superclass as we won't ever use it, but it's good to be aware. Now then, there's nothing new here besides the two types. Let's see how they act:
281 |
282 | ```js
283 | Right.of('rain').map(function(str) {
284 | return 'b' + str;
285 | });
286 | // Right('brain')
287 |
288 | Left.of('rain').map(function(str) {
289 | return 'b' + str;
290 | });
291 | // Left('rain')
292 |
293 | Right.of({
294 | host: 'localhost',
295 | port: 80,
296 | }).map(_.prop('host'));
297 | // Right('localhost')
298 |
299 | Left.of('rolls eyes...').map(_.prop('host'));
300 | // Left('rolls eyes...')
301 | ```
302 |
303 | `Left` is the teenagery sort and ignores our request to `map` over it. `Right` will work just like `Container` (a.k.a Identity). The power comes from the ability to embed an error message within the `Left`.
304 |
305 | Suppose we have a function that might not succeed. How about we calculate an age from a birth date. We could use `Maybe(null)` to signal failure and branch our program, however, that doesn't tell us much. Perhaps, we'd like to know why it failed. Let's write this using `Either`.
306 |
307 | ```js
308 | var moment = require('moment');
309 |
310 | // getAge :: Date -> User -> Either(String, Number)
311 | var getAge = curry(function(now, user) {
312 | var birthdate = moment(user.birthdate, 'YYYY-MM-DD');
313 | if (!birthdate.isValid()) return Left.of('Birth date could not be parsed');
314 | return Right.of(now.diff(birthdate, 'years'));
315 | });
316 |
317 | getAge(moment(), {
318 | birthdate: '2005-12-12',
319 | });
320 | // Right(9)
321 |
322 | getAge(moment(), {
323 | birthdate: '20010704',
324 | });
325 | // Left('Birth date could not be parsed')
326 | ```
327 |
328 | Now, just like `Maybe(null)`, we are short circuiting our app when we return a `Left`. The difference, is now we have a clue as to why our program has derailed. Something to notice is that we return `Either(String, Number)`, which holds a `String` as its left value and a `Number` as its `Right`. This type signature is a bit informal as we haven't taken the time to define an actual `Either` superclass, however, we learn a lot from the type. It informs us that we're either getting an error message or the age back.
329 |
330 | ```js
331 | // fortune :: Number -> String
332 | var fortune = compose(concat('If you survive, you will be '), add(1));
333 |
334 | // zoltar :: User -> Either(String, _)
335 | var zoltar = compose(map(console.log), map(fortune), getAge(moment()));
336 |
337 | zoltar({
338 | birthdate: '2005-12-12',
339 | });
340 | // 'If you survive, you will be 10'
341 | // Right(undefined)
342 |
343 | zoltar({
344 | birthdate: 'balloons!',
345 | });
346 | // Left('Birth date could not be parsed')
347 | ```
348 |
349 | When the `birthdate` is valid, the program outputs its mystical fortune to the screen for us to behold. Otherwise, we are handed a `Left` with the error message plain as day though still tucked away in its container. That acts just as if we'd thrown an error, but in a calm, mild manner fashion as opposed to losing its temper and screaming like a child when something goes wrong.
350 |
351 | In this example, we are logically branching our control flow depending on the validity of the birth date, yet it reads as one linear motion from right to left rather than climbing through the curly braces of a conditional statement. Usually, we'd move the `console.log` out of our `zoltar` function and `map` it at the time of calling, but it's helpful to see how the `Right` branch differs. We use `_` in the right branch's type signature to indicate it's a value that should be ignored(In some browsers you have to use `console.log.bind(console)` to use it first class).
352 |
353 | I'd like to take this opportunity to point out something you may have missed: `fortune`, despite its use with `Either` in this example, is completely ignorant of any functors milling about. This was also the case with `finishTransaction` in the previous example. At the time of calling, a function can be surrounded by `map`, which transforms it from a non-functory function to a functory one, in informal terms. We call this process *lifting*. Functions tend to be better off working with normal data types rather than container types, then *lifted* into the right container as deemed necessary. This leads to simpler, more reusable functions that can be altered to work with any functor on demand.
354 |
355 | `Either` is great for casual errors like validation as well as more serious, stop the show errors like missing files or broken sockets. Try replacing some of the `Maybe` examples with `Either` to give better feedback.
356 |
357 | Now, I can't help, but feel I've done `Either` a disservice by introducing it as merely a container for error messages. It captures logical disjunction (a.k.a `||`) in a type. It also encodes the idea of a *Coproduct* from category theory, which won't be touched on in this book, but is well worth reading up on as there's properties to be exploited. It is the canonical sum type (or disjoint union of sets) because its amount of possible inhabitants is the sum of the two contained types(I know that's a bit hand wavy so here's a [great article](https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types). There are many things `Either` can be, but as a functor, it is used for its error handling.
358 |
359 | Just like with `Maybe`, we have little `either`, which behaves similarly, but takes two functions instead of one and a static value. Each function should return the same type:
360 |
361 | ```js
362 | // either :: (a -> c) -> (b -> c) -> Either a b -> c
363 | var either = curry(function(f, g, e) {
364 | switch (e.constructor) {
365 | case Left:
366 | return f(e.__value);
367 | case Right:
368 | return g(e.__value);
369 | }
370 | });
371 |
372 | // zoltar :: User -> _
373 | var zoltar = compose(console.log, either(id, fortune), getAge(moment()));
374 |
375 | zoltar({
376 | birthdate: '2005-12-12',
377 | });
378 | // "If you survive, you will be 10"
379 | // undefined
380 |
381 | zoltar({
382 | birthdate: 'balloons!',
383 | });
384 | // "Birth date could not be parsed"
385 | // undefined
386 | ```
387 |
388 | Finally, a use for that mysterious `id` function. It simply parrots back the value in the `Left` to pass the error message to `console.log`. We've made our fortune telling app more robust by enforcing error handling from within `getAge`. We either slap the user with a hard truth like a high five from a palm reader or we carry on with our process. And with that, we're ready to move on to an entirely different type of functor.
389 |
390 | ## Old McDonald had Effects...
391 |
392 |
393 |
394 | In our chapter about purity we saw a peculiar example of a pure function. This function contained a side-effect, but we dubbed it pure by wrapping its action in another function. Here's another example of this:
395 |
396 | ```js
397 | // getFromStorage :: String -> (_ -> String)
398 | var getFromStorage = function(key) {
399 | return function() {
400 | return localStorage[key];
401 | };
402 | };
403 | ```
404 |
405 | Had we not surrounded its guts in another function, `getFromStorage` would vary its output depending on external circumstance. With the sturdy wrapper in place, we will always get the same output per input: a function that, when called, will retrieve a particular item from `localStorage`. And just like that (maybe throw in a few Hail Mary's) we've cleared our conscience and all is forgiven.
406 |
407 | Except, this isn't particularly useful now is it. Like a collectible action figure in its original packaging, we can't actually play with it. If only there were a way to reach inside of the container and get at its contents... Enter `IO`.
408 |
409 | ```js
410 | var IO = function(f) {
411 | this.__value = f;
412 | };
413 |
414 | IO.of = function(x) {
415 | return new IO(function() {
416 | return x;
417 | });
418 | };
419 |
420 | IO.prototype.map = function(f) {
421 | return new IO(_.compose(f, this.__value));
422 | };
423 | ```
424 |
425 | `IO` differs from the previous functors in that the `__value` is always a function. We don't think of its `__value` as a function, however - that is an implementation detail and we best ignore it. What is happening is exactly what we saw with the `getFromStorage` example: `IO` delays the impure action by capturing it in a function wrapper. As such, we think of `IO` as containing the return value of the wrapped action and not the wrapper itself. This is apparent in the `of` function: we have an `IO(x)`, the `IO(function(){ return x })` is just necessary to avoid evaluation.
426 |
427 | Let's see it in use:
428 |
429 | ```js
430 | // io_window :: IO Window
431 | var io_window = new IO(function() {
432 | return window;
433 | });
434 |
435 | io_window.map(function(win) {
436 | return win.innerWidth;
437 | });
438 | // IO(1430)
439 |
440 | io_window.map(_.prop('location')).map(_.prop('href')).map(_.split('/'));
441 | // IO(["http:", "", "localhost:8000", "blog", "posts"])
442 |
443 |
444 | // $ :: String -> IO [DOM]
445 | var $ = function(selector) {
446 | return new IO(function() {
447 | return document.querySelectorAll(selector);
448 | });
449 | };
450 |
451 | $('#myDiv').map(head).map(function(div) {
452 | return div.innerHTML;
453 | });
454 | // IO('I am some inner html')
455 | ```
456 |
457 | Here, `io_window` is an actual `IO` that we can `map` over straight away, whereas `$` is a function that returns an `IO` after its called. I've written out the *conceptual* return values to better express the `IO`, though, in reality, it will always be `{ __value: [Function] }`. When we `map` over our `IO`, we stick that function at the end of a composition which, in turn, becomes the new `__value` and so on. Our mapped functions do not run, they get tacked on the end of a computation we're building up, function by function, like carefully placing dominoes that we don't dare tip over. The result is reminiscent of Gang of Four's command pattern or a queue.
458 |
459 | Take a moment to channel your functor intuition. If we see past the implementation details, we should feel right at home mapping over any container no matter its quirks or idiosyncrasies. We have the functor laws, which we will explore toward the end of the chapter, to thank for this pseudo-psychic power. At any rate, we can finally play with impure values without sacrificing our precious purity.
460 |
461 | Now, we've caged the beast, but we'll still have to set it free at some point. Mapping over our `IO` has built up a mighty impure computation and running it is surely going to disturb the peace. So where and when can we pull the trigger? Is it even possible to run our `IO` and still wear white at our wedding? The answer is yes, if we put the onus on the calling code. Our pure code, despite the nefarious plotting and scheming, maintains its innocence and it's the caller who gets burdened with the responsibility of actually running the effects. Let's see an example to make this concrete.
462 |
463 | ```js
464 |
465 | ////// Our pure library: lib/params.js ///////
466 |
467 | // url :: IO String
468 | var url = new IO(function() {
469 | return window.location.href;
470 | });
471 |
472 | // toPairs = String -> [[String]]
473 | var toPairs = compose(map(split('=')), split('&'));
474 |
475 | // params :: String -> [[String]]
476 | var params = compose(toPairs, last, split('?'));
477 |
478 | // findParam :: String -> IO Maybe [String]
479 | var findParam = function(key) {
480 | return map(compose(Maybe.of, filter(compose(eq(key), head)), params), url);
481 | };
482 |
483 | ////// Impure calling code: main.js ///////
484 |
485 | // run it by calling __value()!
486 | findParam("searchTerm").__value();
487 | // Maybe([['searchTerm', 'wafflehouse']])
488 | ```
489 |
490 | Our library keeps its hands clean by wrapping `url` in an `IO` and passing the buck to the caller. You might have also noticed that we have stacked our containers; it's perfectly reasonable to have a `IO(Maybe([x]))`, which is three functors deep(`Array` is most definitely a mappable container type) and exceptionally expressive.
491 |
492 | There's something that's been bothering me and we should rectify it immediately: `IO`'s `__value` isn't really its contained value, nor is it a private property as the underscore prefix suggests. It is the pin in the grenade and it is meant to be pulled by a caller in the most public of ways. Let's rename this property to `unsafePerformIO` to remind our users of its volatility.
493 |
494 | ```js
495 | var IO = function(f) {
496 | this.unsafePerformIO = f;
497 | };
498 |
499 | IO.prototype.map = function(f) {
500 | return new IO(_.compose(f, this.unsafePerformIO));
501 | };
502 | ```
503 |
504 | There, much better. Now our calling code becomes `findParam("searchTerm").unsafePerformIO()`, which is clear as day to users (and readers) of the application.
505 |
506 | `IO` will be a loyal companion, helping us tame those feral impure actions. Next, we'll see a type similar in spirit, but has a drastically different use case.
507 |
508 |
509 | ## Asynchronous Tasks
510 |
511 | Callbacks are the narrowing spiral staircase to hell. They are control flow as designed by M.C. Escher. With each nested callback squeezed in between the jungle gym of curly braces and parenthesis, they feel like limbo in an oubliette(how low can we go!). I'm getting claustrophobic chills just thinking about them. Not to worry, we have a much better way of dealing with asynchronous code and it starts with an "F".
512 |
513 | The internals are a bit too complicated to spill out all over the page here so we will use `Data.Task` (previously `Data.Future`) from Quildreen Motta's fantastic [Folktale](http://folktalejs.org/). Behold some example usage:
514 |
515 | ```js
516 | // Node readfile example:
517 | //=======================
518 |
519 | var fs = require('fs');
520 |
521 | // readFile :: String -> Task Error String
522 | var readFile = function(filename) {
523 | return new Task(function(reject, result) {
524 | fs.readFile(filename, 'utf-8', function(err, data) {
525 | err ? reject(err) : result(data);
526 | });
527 | });
528 | };
529 |
530 | readFile('metamorphosis').map(split('\n')).map(head);
531 | // Task("One morning, as Gregor Samsa was waking up from anxious dreams, he discovered that
532 | // in bed he had been changed into a monstrous verminous bug.")
533 |
534 |
535 | // jQuery getJSON example:
536 | //========================
537 |
538 | // getJSON :: String -> {} -> Task Error JSON
539 | var getJSON = curry(function(url, params) {
540 | return new Task(function(reject, result) {
541 | $.getJSON(url, params, result).fail(reject);
542 | });
543 | });
544 |
545 | getJSON('/video', {
546 | id: 10,
547 | }).map(_.prop('title'));
548 | // Task("Family Matters ep 15")
549 |
550 | // We can put normal, non futuristic values inside as well
551 | Task.of(3).map(function(three) {
552 | return three + 1,
553 | });
554 | // Task(4)
555 | ```
556 |
557 | The functions I'm calling `reject` and `result` are our error and success callbacks, respectively. As you can see, we simply `map` over the `Task` to work on the future value as if it was right there in our grasp. By now `map` should be old hat.
558 |
559 | If you're familiar with promises, you might recognize the function `map` as `then` with `Task` playing the role of our promise. Don't fret if you aren't familiar with promises, we won't be using them anyhow because they are not pure, but the analogy holds nonetheless.
560 |
561 | Like `IO`, `Task` will patiently wait for us to give it the green light before running. In fact, because it waits for our command, `IO` is effectively subsumed by `Task` for all things asynchronous; `readFile` and `getJSON` don't require an extra `IO` container to be pure. What's more, `Task` works in a similar fashion when we `map` over it: we're placing instructions for the future like a chore chart in a time capsule - an act of sophisticated technological procrastination.
562 |
563 | To run our `Task`, we must call the method `fork`. This works like `unsafePerformIO`, but as the name suggests, it will fork our process and evaluation continues on without blocking our thread. This can be implemented in numerous ways with threads and such, but here it acts as a normal async call would and the big wheel of the event loop keeps on turning. Let's look at `fork`:
564 |
565 | ```js
566 | // Pure application
567 | //=====================
568 | // blogTemplate :: String
569 |
570 | // blogPage :: Posts -> HTML
571 | var blogPage = Handlebars.compile(blogTemplate);
572 |
573 | // renderPage :: Posts -> HTML
574 | var renderPage = compose(blogPage, sortBy('date'));
575 |
576 | // blog :: Params -> Task Error HTML
577 | var blog = compose(map(renderPage), getJSON('/posts'));
578 |
579 |
580 | // Impure calling code
581 | //=====================
582 | blog({}).fork(
583 | function(error) {
584 | $("#error").html(error.message);
585 | },
586 | function(page) {
587 | $("#main").html(page);
588 | }
589 | );
590 |
591 | $('#spinner').show();
592 | ```
593 |
594 | Upon calling `fork`, the `Task` hurries off to find some posts and render the page. Meanwhile, we show a spinner since `fork` does not wait for a response. Finally, we will either display an error or render the page onto the screen depending if the `getJSON` call succeeded or not.
595 |
596 | Take a moment to consider how linear the control flow is here. We just read bottom to top, right to left even though the program will actually jump around a bit during execution. This makes reading and reasoning about our application simpler than having to bounce between callbacks and error handling blocks.
597 |
598 | Goodness, would you look at that, `Task` has also swallowed up `Either`! It must do so in order to handle futuristic failures since our normal control flow does not apply in the async world. This is all well and good as it provides sufficient and pure error handling out of the box.
599 |
600 | Even with `Task`, our `IO` and `Either` functors are not out of a job. Bear with me on a quick example that leans toward the more complex and hypothetical side, but is useful for illustrative purposes.
601 |
602 | ```js
603 | // Postgres.connect :: Url -> IO DbConnection
604 | // runQuery :: DbConnection -> ResultSet
605 | // readFile :: String -> Task Error String
606 |
607 | // Pure application
608 | //=====================
609 |
610 | // dbUrl :: Config -> Either Error Url
611 | var dbUrl = function(c) {
612 | return (c.uname && c.pass && c.host && c.db)
613 | ? Right.of('db:pg://'+c.uname+':'+c.pass+'@'+c.host+'5432/'+c.db)
614 | : Left.of(Error('Invalid config!'));
615 | }
616 |
617 | // connectDb :: Config -> Either Error (IO DbConnection)
618 | var connectDb = compose(map(Postgres.connect), dbUrl);
619 |
620 | // getConfig :: Filename -> Task Error (Either Error (IO DbConnection))
621 | var getConfig = compose(map(compose(connectDb, JSON.parse)), readFile);
622 |
623 |
624 | // Impure calling code
625 | //=====================
626 | getConfig('db.json').fork(
627 | logErr('couldn\'t read file'), either(console.log, map(runQuery))
628 | );
629 | ```
630 |
631 | In this example, we still make use of `Either` and `IO` from within the success branch of `readFile`. `Task` takes care of the impurities of reading a file asynchronously, but we still deal with validating the config with `Either` and wrangling the db connection with `IO`. So you see, we're still in business for all things synchronous.
632 |
633 | I could go on, but that's all there is to it. Simple as `map`.
634 |
635 | In practice, you'll likely have multiple asynchronous tasks in one workflow and we haven't yet acquired the full container APIs to tackle this scenario. Not to worry, we'll look at monads and such soon, but first, we must examine the maths that make this all possible.
636 |
637 |
638 | ## A Spot of Theory
639 |
640 | As mentioned before, functors come from category theory and satisfy a few laws. Let's first explore these useful properties.
641 |
642 | ```js
643 | // identity
644 | map(id) === id;
645 |
646 | // composition
647 | compose(map(f), map(g)) === map(compose(f, g));
648 | ```
649 |
650 | The *identity* law is simple, but important. These laws are runnable bits of code so we can try them on our own functors to validate their legitimacy.
651 |
652 | ```js
653 | var idLaw1 = map(id);
654 | var idLaw2 = id;
655 |
656 | idLaw1(Container.of(2));
657 | //=> Container(2)
658 |
659 | idLaw2(Container.of(2));
660 | //=> Container(2)
661 | ```
662 |
663 | You see, they are equal. Next let's look at composition.
664 |
665 | ```js
666 | var compLaw1 = compose(map(concat(' world')), map(concat(' cruel')));
667 | var compLaw2 = map(compose(concat(' world'), concat(' cruel')));
668 |
669 | compLaw1(Container.of('Goodbye'));
670 | //=> Container(' world cruelGoodbye')
671 |
672 | compLaw2(Container.of('Goodbye'));
673 | //=> Container(' world cruelGoodbye')
674 | ```
675 |
676 | In category theory, functors take the objects and morphisms of a category and map them to a different category. By definition, this new category must have an identity and the ability to compose morphisms, but we needn't check because the aforementioned laws ensure these are preserved.
677 |
678 | Perhaps our definition of a category is still a bit fuzzy. You can think of a category as a network of objects with morphisms that connect them. So a functor would map the one category to the other without breaking the network. If an object `a` is in our source category `C`, when we map it to category `D` with functor `F`, we refer to that object as `F a` (If you put it together what does that spell?!). Perhaps, it's better to look at a diagram:
679 |
680 |
681 |
682 | For instance, `Maybe` maps our category of types and functions to a category where each object may not exist and each morphism has a `null` check. We accomplish this in code by surrounding each function with `map` and each type with our functor. We know that each of our normal types and functions will continue to compose in this new world. Technically, each functor in our code maps to a sub category of types and functions which makes all functors a particular brand called endofunctors, but for our purposes, we'll think of it as a different category.
683 |
684 | We can also visualize the mapping of a morphism and its corresponding objects with this diagram:
685 |
686 |
687 |
688 | In addition to visualizing the mapped morphism from one category to another under the functor `F`, we see that the diagram commutes, which is to say, if you follow the arrows each route produces the same result. The different routes means different behavior, but we always end at the same type. This formalism gives us principled ways to reason about our code - we can boldly apply formulas without having to parse and examine each individual scenario. Let's take a concrete example.
689 |
690 | ```js
691 | // topRoute :: String -> Maybe String
692 | var topRoute = compose(Maybe.of, reverse);
693 |
694 | // bottomRoute :: String -> Maybe String
695 | var bottomRoute = compose(map(reverse), Maybe.of);
696 |
697 |
698 | topRoute('hi');
699 | // Maybe('ih')
700 |
701 | bottomRoute('hi');
702 | // Maybe('ih')
703 | ```
704 |
705 | Or visually:
706 |
707 |
708 |
709 | We can instantly see and refactor code based on properties held by all functors.
710 |
711 | Functors can stack:
712 |
713 | ```js
714 | var nested = Task.of([Right.of('pillows'), Left.of('no sleep for you')]);
715 |
716 | map(map(map(toUpperCase)), nested);
717 | // Task([Right('PILLOWS'), Left('no sleep for you')])
718 | ```
719 |
720 | What we have here with `nested` is a future array of elements that might be errors. We `map` to peel back each layer and run our function on the elements. We see no callbacks, if/else's, or for loops; just an explicit context. We do, however, have to `map(map(map(f)))`. We can instead compose functors. You heard me correctly:
721 |
722 | ```js
723 | var Compose = function(f_g_x) {
724 | this.getCompose = f_g_x;
725 | };
726 |
727 | Compose.prototype.map = function(f) {
728 | return new Compose(map(map(f), this.getCompose));
729 | };
730 |
731 | var tmd = Task.of(Maybe.of('Rock over London'));
732 |
733 | var ctmd = new Compose(tmd);
734 |
735 | map(concat(', rock on, Chicago'), ctmd);
736 | // Compose(Task(Maybe('Rock over London, rock on, Chicago')))
737 |
738 | ctmd.getCompose;
739 | // Task(Maybe('Rock over London, rock on, Chicago'))
740 | ```
741 |
742 | There, one `map`. Functor composition is associative and earlier, we defined `Container`, which is actually called the `Identity` functor. If we have identity and associative composition we have a category. This particular category has categories as objects and functors as morphisms, which is enough to make one's brain perspire. We won't delve too far into this, but it's nice to appreciate the architectural implications or even just the simple abstract beauty in the pattern.
743 |
744 |
745 | ## In Summary
746 |
747 | We've seen a few different functors, but there are infinitely many. Some notable omissions are iterable data structures like trees, lists, maps, pairs, you name it. eventstreams and observables are both functors. Others can be for encapsulation or even just type modelling. Functors are all around us and we'll use them extensively throughout the book.
748 |
749 | What about calling a function with multiple functor arguments? How about working with an order sequence of impure or async actions? We haven't yet acquired the full tool set for working in this boxed up world. Next, we'll cut right to the chase and look at monads.
750 |
751 | [Chapter 9: Monadic Onions](ch9.md)
752 |
753 | ## Exercises
754 |
755 | ```js
756 | require('../../support');
757 | var Task = require('data.task');
758 | var _ = require('ramda');
759 |
760 | // Exercise 1
761 | // ==========
762 | // Use _.add(x,y) and _.map(f,x) to make a function that increments a value
763 | // inside a functor.
764 |
765 | var ex1 = undefined;
766 |
767 |
768 |
769 | // Exercise 2
770 | // ==========
771 | // Use _.head to get the first element of the list.
772 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
773 |
774 | var ex2 = undefined;
775 |
776 |
777 |
778 | // Exercise 3
779 | // ==========
780 | // Use safeProp and _.head to find the first initial of the user.
781 | var safeProp = _.curry(function(x, o) {
782 | return Maybe.of(o[x]);
783 | });
784 |
785 | var user = {
786 | id: 2,
787 | name: 'Albert',
788 | };
789 |
790 | var ex3 = undefined;
791 |
792 |
793 | // Exercise 4
794 | // ==========
795 | // Use Maybe to rewrite ex4 without an if statement.
796 |
797 | var ex4 = function(n) {
798 | if (n) {
799 | return parseInt(n);
800 | }
801 | };
802 |
803 | var ex4 = undefined;
804 |
805 |
806 |
807 | // Exercise 5
808 | // ==========
809 | // Write a function that will getPost then toUpperCase the post's title.
810 |
811 | // getPost :: Int -> Future({id: Int, title: String})
812 | var getPost = function(i) {
813 | return new Task(function(rej, res) {
814 | setTimeout(function() {
815 | res({
816 | id: i,
817 | title: 'Love them futures',
818 | });
819 | }, 300);
820 | });
821 | };
822 |
823 | var ex5 = undefined;
824 |
825 |
826 |
827 | // Exercise 6
828 | // ==========
829 | // Write a function that uses checkActive() and showWelcome() to grant access
830 | // or return the error.
831 |
832 | var showWelcome = _.compose(_.add('Welcome '), _.prop('name'));
833 |
834 | var checkActive = function(user) {
835 | return user.active ? Right.of(user) : Left.of('Your account is not active');
836 | };
837 |
838 | var ex6 = undefined;
839 |
840 |
841 |
842 | // Exercise 7
843 | // ==========
844 | // Write a validation function that checks for a length > 3. It should return
845 | // Right(x) if it is greater than 3 and Left("You need > 3") otherwise.
846 |
847 | var ex7 = function(x) {
848 | return undefined; // <--- write me. (don't be pointfree)
849 | };
850 |
851 |
852 |
853 | // Exercise 8
854 | // ==========
855 | // Use ex7 above and Either as a functor to save the user if they are valid or
856 | // return the error message string. Remember either's two arguments must return
857 | // the same type.
858 |
859 | var save = function(x) {
860 | return new IO(function() {
861 | console.log('SAVED USER!');
862 | return x + '-saved';
863 | });
864 | };
865 |
866 | var ex8 = undefined;
867 | ```
868 |
--------------------------------------------------------------------------------
/ch9.md:
--------------------------------------------------------------------------------
1 | # Chapter 9: Monadic Onions
2 |
3 | ## Pointy Functor Factory
4 |
5 | Before we go any further, I have a confession to make: I haven't been fully honest about that `of` method we've placed on each of our types. Turns out, it is not there to avoid the `new` keyword, but rather to place values in what's called a *default minimal context*. Yes, `of` does not actually take the place of a constructor - it is part of an important interface we call *Pointed*.
6 |
7 | > A *pointed functor* is a functor with an `of` method
8 |
9 | What's important here is the ability to drop any value in our type and start mapping away.
10 |
11 | ```js
12 | IO.of('tetris').map(concat(' master'));
13 | // IO('tetris master')
14 |
15 | Maybe.of(1336).map(add(1));
16 | // Maybe(1337)
17 |
18 | Task.of({
19 | id: 2,
20 | }).map(_.prop('id'));
21 | // Task(2)
22 |
23 | Either.of('The past, present and future walk into a bar...').map(
24 | concat('it was tense.')
25 | );
26 | // Right('The past, present and future walk into a bar...it was tense.')
27 | ```
28 |
29 | If you recall, `IO` and `Task`'s constructors expect a function as their argument, but `Maybe` and `Either` do not. The motivation for this interface is a common, consistent way to place a value into our functor without the complexities and specific demands of constructors. The term "default minimal context" lacks precision, yet captures the idea well: we'd like to lift any value in our type and `map` away per usual with the expected behaviour of whichever functor.
30 |
31 | One important correction I must make at this point, pun intended, is that `Left.of` doesn't make any sense. Each functor must have one way to place a value inside it and with `Either`, that's `new Right(x)`. We define `of` using `Right` because if our type *can* `map`, it *should* `map`. Looking at the examples above, we should have an intuition about how `of` will usually work and `Left` breaks that mold.
32 |
33 | You may have heard of functions such as `pure`, `point`, `unit`, and `return`. These are various monikers for our `of` method, international function of mystery. `of` will become important when we start using monads because, as we will see, it's our responsibility to place values back into the type manually.
34 |
35 | To avoid the `new` keyword, there are several standard JavaScript tricks or libraries so let's use them and use `of` like a responsible adult from here on out. I recommend using functor instances from `folktale`, `ramda` or `fantasy-land` as they provide the correct `of` method as well as nice constructors that don't rely on `new`.
36 |
37 |
38 | ## Mixing Metaphors
39 |
40 |
41 |
42 | You see, in addition to space burritos (if you've heard the rumors), monads are like onions. Allow me to demonstrate with a common situation:
43 |
44 | ```js
45 | // Support
46 | // ===========================
47 | var fs = require('fs');
48 |
49 | // readFile :: String -> IO String
50 | var readFile = function(filename) {
51 | return new IO(function() {
52 | return fs.readFileSync(filename, 'utf-8');
53 | });
54 | };
55 |
56 | // print :: String -> IO String
57 | var print = function(x) {
58 | return new IO(function() {
59 | console.log(x);
60 | return x;
61 | });
62 | };
63 |
64 | // Example
65 | // ===========================
66 | // cat :: String -> IO (IO String)
67 | var cat = compose(map(print), readFile);
68 |
69 | cat('.git/config');
70 | // IO(IO('[core]\nrepositoryformatversion = 0\n'))
71 | ```
72 |
73 | What we've got here is an `IO` trapped inside another `IO` because `print` introduced a second `IO` during our `map`. To continue working with our string, we must `map(map(f))` and to observe the effect, we must `unsafePerformIO().unsafePerformIO()`.
74 |
75 | ```js
76 | // cat :: String -> IO (IO String)
77 | var cat = compose(map(print), readFile);
78 |
79 | // catFirstChar :: String -> IO (IO String)
80 | var catFirstChar = compose(map(map(head)), cat);
81 |
82 | catFirstChar(".git/config");
83 | // IO(IO("["))
84 | ```
85 |
86 | While it is nice to see that we have two effects packaged up and ready to go in our application, it feels a bit like working in two hazmat suits and we end up with an uncomfortably awkward API. Let's look at another situation:
87 |
88 | ```js
89 | // safeProp :: Key -> {Key: a} -> Maybe a
90 | var safeProp = curry(function(x, obj) {
91 | return new Maybe(obj[x]);
92 | });
93 |
94 | // safeHead :: [a] -> Maybe a
95 | var safeHead = safeProp(0);
96 |
97 | // firstAddressStreet :: User -> Maybe (Maybe (Maybe Street) )
98 | var firstAddressStreet = compose(
99 | map(map(safeProp('street'))), map(safeHead), safeProp('addresses')
100 | );
101 |
102 | firstAddressStreet({
103 | addresses: [{
104 | street: {
105 | name: 'Mulburry',
106 | number: 8402,
107 | },
108 | postcode: 'WC2N',
109 | }],
110 | });
111 | // Maybe(Maybe(Maybe({name: 'Mulburry', number: 8402})))
112 | ```
113 |
114 | Again, we see this nested functor situation where it's neat to see there are three possible failures in our function, but it's a little presumptuous to expect a caller to `map` three times to get at the value - we'd only just met. This pattern will arise time and time again and it is the primary situation where we'll need to shine the mighty monad symbol into the night sky.
115 |
116 | I said monads are like onions because tears well up as we peel back layer of the nested functor with `map` to get at the inner value. We can dry our eyes, take a deep breath, and use a method called `join`.
117 |
118 | ```js
119 | var mmo = Maybe.of(Maybe.of('nunchucks'));
120 | // Maybe(Maybe('nunchucks'))
121 |
122 | mmo.join();
123 | // Maybe('nunchucks')
124 |
125 | var ioio = IO.of(IO.of('pizza'));
126 | // IO(IO('pizza'))
127 |
128 | ioio.join();
129 | // IO('pizza')
130 |
131 | var ttt = Task.of(Task.of(Task.of('sewers')));
132 | // Task(Task(Task('sewers')));
133 |
134 | ttt.join();
135 | // Task(Task('sewers'))
136 | ```
137 |
138 | If we have two layers of the same type, we can smash them together with `join`. This ability to join together, this functor matrimony, is what makes a monad a monad. Let's inch toward the full definition with something a little more accurate:
139 |
140 | > Monads are pointed functors that can flatten
141 |
142 | Any functor which defines a `join` method, has an `of` method, and obeys a few laws is a monad. Defining `join` is not too difficult so let's do so for `Maybe`:
143 |
144 | ```js
145 | Maybe.prototype.join = function() {
146 | return this.isNothing() ? Maybe.of(null) : this.__value;
147 | }
148 | ```
149 |
150 | There, simple as consuming one's twin in the womb. If we have a `Maybe(Maybe(x))` then `.__value` will just remove the unnecessary extra layer and we can safely `map` from there. Otherwise, we'll just have the one `Maybe` as nothing would have been mapped in the first place.
151 |
152 | Now that we have a `join` method, let's sprinkle some magic monad dust over the `firstAddressStreet` example and see it in action:
153 |
154 | ```js
155 | // join :: Monad m => m (m a) -> m a
156 | var join = function(mma) {
157 | return mma.join();
158 | };
159 |
160 | // firstAddressStreet :: User -> Maybe Street
161 | var firstAddressStreet = compose(
162 | join, map(safeProp('street')), join, map(safeHead), safeProp('addresses')
163 | );
164 |
165 | firstAddressStreet({
166 | addresses: [{
167 | street: {
168 | name: 'Mulburry',
169 | number: 8402,
170 | },
171 | postcode: 'WC2N',
172 | }],
173 | });
174 | // Maybe({name: 'Mulburry', number: 8402})
175 | ```
176 |
177 | We added `join` wherever we encountered the nested `Maybe`'s to keep them from getting out of hand. Let's do the same with `IO` to give us a feel for that.
178 |
179 | ```js
180 | IO.prototype.join = function() {
181 | var thiz = this;
182 | return new IO(function() {
183 | return thiz.unsafePerformIO().unsafePerformIO();
184 | });
185 | };
186 | ```
187 |
188 | We simply bundle running the two layers of IO sequentially: outer then inner. Mind you, we have not thrown out purity, but merely repackaged the excessive two layers of shrink wrap into one easier-to-open package.
189 |
190 | ```js
191 | // log :: a -> IO a
192 | var log = function(x) {
193 | return new IO(function() {
194 | console.log(x);
195 | return x;
196 | });
197 | };
198 |
199 | // setStyle :: Selector -> CSSProps -> IO DOM
200 | var setStyle = curry(function(sel, props) {
201 | return new IO(function() {
202 | return jQuery(sel).css(props);
203 | });
204 | });
205 |
206 | // getItem :: String -> IO String
207 | var getItem = function(key) {
208 | return new IO(function() {
209 | return localStorage.getItem(key);
210 | });
211 | };
212 |
213 | // applyPreferences :: String -> IO DOM
214 | var applyPreferences = compose(
215 | join, map(setStyle('#main')), join, map(log), map(JSON.parse), getItem
216 | );
217 |
218 |
219 | applyPreferences('preferences').unsafePerformIO();
220 | // Object {backgroundColor: "green"}
221 | //
222 | ```
223 |
224 | `getItem` returns an `IO String` so we `map` to parse it. Both `log` and `setStyle` return `IO`'s themselves so we must `join` to keep our nesting under control.
225 |
226 | ## My chain hits my chest
227 |
228 |
229 |
230 | You might have noticed a pattern. We often end up calling `join` right after a `map`. Let's abstract this into a function called `chain`.
231 |
232 | ```js
233 | // chain :: Monad m => (a -> m b) -> m a -> m b
234 | var chain = curry(function(f, m){
235 | return m.map(f).join(); // or compose(join, map(f))(m)
236 | });
237 | ```
238 |
239 | We'll just bundle up this map/join combo into a single function. If you've read about monads previously, you might have seen `chain` called `>>=` (pronounced bind) or `flatMap` which are all aliases for the same concept. I personally think `flatMap` is the most accurate name, but we'll stick with `chain` as it's the widely accepted name in JS. Let's refactor the two examples above with `chain`:
240 |
241 | ```js
242 | // map/join
243 | var firstAddressStreet = compose(
244 | join, map(safeProp('street')), join, map(safeHead), safeProp('addresses')
245 | );
246 |
247 | // chain
248 | var firstAddressStreet = compose(
249 | chain(safeProp('street')), chain(safeHead), safeProp('addresses')
250 | );
251 |
252 |
253 |
254 | // map/join
255 | var applyPreferences = compose(
256 | join, map(setStyle('#main')), join, map(log), map(JSON.parse), getItem
257 | );
258 |
259 | // chain
260 | var applyPreferences = compose(
261 | chain(setStyle('#main')), chain(log), map(JSON.parse), getItem
262 | );
263 | ```
264 |
265 | I swapped out any `map/join` with our new `chain` function to tidy things up a bit. Cleanliness is nice and all, but there's more to `chain` than meets the eye - it's more of tornado than a vacuum. Because `chain` effortlessly nests effects, we can capture both *sequence* and *variable assignment* in a purely functional way.
266 |
267 | ```js
268 | // getJSON :: Url -> Params -> Task JSON
269 | // querySelector :: Selector -> IO DOM
270 |
271 |
272 | getJSON('/authenticate', {
273 | username: 'stale',
274 | password: 'crackers',
275 | })
276 | .chain(function(user) {
277 | return getJSON('/friends', {
278 | user_id: user.id,
279 | });
280 | });
281 | // Task([{name: 'Seimith', id: 14}, {name: 'Ric', id: 39}]);
282 |
283 |
284 | querySelector('input.username').chain(function(uname) {
285 | return querySelector('input.email').chain(function(email) {
286 | return IO.of(
287 | 'Welcome ' + uname.value + ' ' + 'prepare for spam at ' + email.value
288 | );
289 | });
290 | });
291 | // IO('Welcome Olivia prepare for spam at olivia@tremorcontrol.net');
292 |
293 |
294 | Maybe.of(3).chain(function(three) {
295 | return Maybe.of(2).map(add(three));
296 | });
297 | // Maybe(5);
298 |
299 |
300 | Maybe.of(null).chain(safeProp('address')).chain(safeProp('street'));
301 | // Maybe(null);
302 | ```
303 |
304 | We could have written these examples with `compose`, but we'd need a few helper functions and this style lends itself to explicit variable assignment via closure anyhow. Instead we're using the infix version of `chain` which, incidentally, can be derived from `map` and `join` for any type automatically: `t.prototype.chain = function(f) { return this.map(f).join(); }`. We can also define `chain` manually if we'd like a false sense of performance, though we must take care to maintain the correct functionality - that is, it must equal `map` followed by `join`. An interesting fact is that we can derive `map` for free if we've created `chain` simply by bottling the value back up when we're finished with `of`. With `chain`, we can also define `join` as `chain(id)`. It may feel like playing Texas Hold em' with a rhinestone magician in that I'm just pulling things out of my behind, but, as with most mathematics, all of these principled constructs are interrelated. Lots of these derivations are mentioned in the [fantasyland](https://github.com/fantasyland/fantasy-land) repo, which is the official specification for algebraic data types in JavaScript.
305 |
306 | Anyways, let's get to the examples above. In the first example, we see two `Task`'s chained in a sequence of asynchronous actions - first it retrieves the `user`, then it finds the friends with that user's id. We use `chain` to avoid a `Task(Task([Friend]))` situation.
307 |
308 | Next, we use `querySelector` to find a few different inputs and create a welcoming message. Notice how we have access to both `uname` and `email` at the innermost function - this is functional variable assignment at its finest. Since `IO` is graciously lending us its value, we are in charge of putting it back how we found it - we wouldn't want to break its trust (and our program). `IO.of` is the perfect tool for the job and it's why Pointed is an important prerequisite to the Monad interface. However, we could choose to `map` as that would also return the correct type:
309 |
310 | ```js
311 | querySelector('input.username').chain(function(uname) {
312 | return querySelector('input.email').map(function(email) {
313 | return 'Welcome ' + uname.value + ' prepare for spam at ' + email.value;
314 | });
315 | });
316 | // IO('Welcome Olivia prepare for spam at olivia@tremorcontrol.net');
317 | ```
318 |
319 | Finally, we have two examples using `Maybe`. Since `chain` is mapping under the hood, if any value is `null`, we stop the computation dead in its tracks.
320 |
321 | Don't worry if these examples are hard to grasp at first. Play with them. Poke them with a stick. Smash them to bits and reassemble. Remember to `map` when returning a "normal" value and `chain` when we're returning another functor.
322 |
323 | As a reminder, this does not work with two different nested types. Functor composition and later, monad transformers, can help us in that situation.
324 |
325 | ## Power trip
326 |
327 | Container style programming can be confusing at times. We sometimes find ourselves struggling to understand how many containers deep a value is or if we need `map` or `chain` (soon we'll see more container methods). We can greatly improve debugging with tricks like implementing `inspect` and we'll learn how to create a "stack" that can handle whatever effects we throw at it, but there are times when we question if it's worth the hassle.
328 |
329 | I'd like to swing the fiery monadic sword for a moment to exhibit the power of programming this way.
330 |
331 | Let's read a file, then upload it directly afterward:
332 |
333 | ```js
334 | // readFile :: Filename -> Either String (Task Error String)
335 | // httpPost :: String -> String -> Task Error JSON
336 |
337 | // upload :: String -> Either String (Task Error JSON)
338 | var upload = compose(map(chain(httpPost('/uploads'))), readFile);
339 | ```
340 |
341 | Here, we are branching our code several times. Looking at the type signatures I can see that we protect against 3 errors - `readFile` uses `Either` to validate the input (perhaps ensuring the filename is present), `readFile` may error when accessing the file as expressed in the first type parameter of `Task`, and the upload may fail for whatever reason which is expressed by the `Error` in `httpPost`. We casually pull off two nested, sequential asynchronous actions with `chain`.
342 |
343 | All of this is achieved in one linear left to right flow. This is all pure and declarative. It holds equational reasoning and reliable properties. We aren't forced to add needless and confusing variable names. Our `upload` function is written against generic interfaces and not specific one-off APIs. It's one bloody line for goodness sake.
344 |
345 | For contrast, let's look at the standard imperative way to pull this off:
346 |
347 | ```js
348 | // upload :: String -> (String -> a) -> Void
349 | var upload = function(filename, callback) {
350 | if (!filename) {
351 | throw "You need a filename!";
352 | } else {
353 | readFile(filename, function(err, contents) {
354 | if (err) throw err;
355 | httpPost('/uploads', contents, function(err, json) {
356 | if (err) throw err;
357 | callback(json);
358 | });
359 | });
360 | }
361 | };
362 | ```
363 |
364 | Well isn't that the devil's arithmetic. We're pinballed through a volatile maze of madness. Imagine if it were a typical app that also mutated variables along the way! We'd be in the tar pit indeed.
365 |
366 | #Theory
367 |
368 | The first law we'll look at is associativity, but perhaps not in the way you're used to it.
369 |
370 | ```js
371 | // associativity
372 | compose(join, map(join)) == compose(join, join);
373 | ```
374 |
375 | These laws get at the nested nature of monads so associativity focuses on joining the inner or outer types first to achieve the same result. A picture might be more instructive:
376 |
377 |
378 |
379 | Starting with the top left moving downward, we can `join` the outer two `M`'s of `M(M(M a))` first then cruise over to our desired `M a` with another `join`. Alternatively, we can pop the hood and flatten the inner two `M`'s with `map(join)`. We end up with the same `M a` regardless of if we join the inner or outer `M`'s first and that's what associativity is all about. It's worth noting that `map(join) != join`. The intermediate steps can vary in value, but the end result of the last `join` will be the same.
380 |
381 | The second law is similar:
382 |
383 | ```js
384 | // identity for all (M a)
385 | compose(join, of) === compose(join, map(of)) === id
386 | ```
387 |
388 | It states that, for any monad `M`, `of` and `join` amounts to `id`. We can also `map(of)` and attack it from the inside out. We call this "triangle identity" because it makes such a shape when visualized:
389 |
390 |
391 |
392 | If we start at the top left heading right, we can see that `of` does indeed drop our `M a` in another `M` container. Then if we move downward and `join` it, we get the same as if we just called `id` in the first place. Moving right to left, we see that if we sneak under the covers with `map` and call `of` of the plain `a`, we'll still end up with `M (M a)` and `join`ing will bring us back to square one.
393 |
394 | I should mention that I've just written `of`, however, it must be the specific `M.of` for whatever monad we're using.
395 |
396 | Now, I've seen these laws, identity and associativity, somewhere before... Hold on, I'm thinking...Yes of course! They are the laws for a category. But that would mean we need a composition function to complete the definition. Behold:
397 |
398 | ```js
399 | var mcompose = function(f, g) {
400 | return compose(chain(f), chain(g));
401 | };
402 |
403 | // left identity
404 | mcompose(M, f) == f;
405 |
406 | // right identity
407 | mcompose(f, M) == f;
408 |
409 | // associativity
410 | mcompose(mcompose(f, g), h) === mcompose(f, mcompose(g, h));
411 | ```
412 |
413 | They are the category laws after all. Monads form a category called the "Kleisli category" where all objects are monads and morphisms are chained functions. I don't mean to taunt you with bits and bobs of category theory without much explanation of how the jigsaw fits together. The intention is to scratch the surface enough to show the relevance and spark some interest while focusing on the practical properties we can use each day.
414 |
415 |
416 | ## In Summary
417 |
418 | Monads let us drill downward into nested computations. We can assign variables, run sequential effects, perform asynchronous tasks, all without laying one brick in a pyramid of doom. They come to the rescue when a value finds itself jailed in multiple layers of the same type. With the help of the trusty sidekick "pointed", monads are able to lend us an unboxed value and know we'll be able to place it back in when we're done.
419 |
420 | Yes, monads are very powerful, yet we still find ourselves needing some extra container functions. For instance, what if we wanted to run a list of api calls at once, then gather the results? We can accomplish this task with monads, but we'd have to wait for each one to finish before calling the next. What about combining several validations? We'd like to continue validating to gather the list of errors, but monads would stop the show after the first `Left` entered the picture.
421 |
422 | In the next chapter, we'll see how applicative functors fit into the container world and why we prefer them to monads in many cases.
423 |
424 | [Chapter 10: Applicative Functors](ch10.md)
425 |
426 |
427 | ## Exercises
428 |
429 | ```js
430 | // Exercise 1
431 | // ==========
432 | // Use safeProp and map/join or chain to safely get the street name when given
433 | // a user.
434 |
435 | var safeProp = _.curry(function(x, o) {
436 | return Maybe.of(o[x]);
437 | });
438 | var user = {
439 | id: 2,
440 | name: 'albert',
441 | address: {
442 | street: {
443 | number: 22,
444 | name: 'Walnut St',
445 | },
446 | },
447 | };
448 |
449 | var ex1 = undefined;
450 |
451 |
452 | // Exercise 2
453 | // ==========
454 | // Use getFile to get the filename, remove the directory so it's just the file,
455 | // then purely log it.
456 |
457 | var getFile = function() {
458 | return new IO(function() {
459 | return __filename;
460 | });
461 | };
462 |
463 | var pureLog = function(x) {
464 | return new IO(function() {
465 | console.log(x);
466 | return 'logged ' + x;
467 | });
468 | };
469 |
470 | var ex2 = undefined;
471 |
472 |
473 |
474 | // Exercise 3
475 | // ==========
476 | // Use getPost() then pass the post's id to getComments().
477 | //
478 | var getPost = function(i) {
479 | return new Task(function(rej, res) {
480 | setTimeout(function() {
481 | res({
482 | id: i,
483 | title: 'Love them tasks',
484 | });
485 | }, 300);
486 | });
487 | };
488 |
489 | var getComments = function(i) {
490 | return new Task(function(rej, res) {
491 | setTimeout(function() {
492 | res([{
493 | post_id: i,
494 | body: 'This book should be illegal',
495 | }, {
496 | post_id: i,
497 | body: 'Monads are like smelly shallots',
498 | }]);
499 | }, 300);
500 | });
501 | };
502 |
503 |
504 | var ex3 = undefined;
505 |
506 |
507 | // Exercise 4
508 | // ==========
509 | // Use validateEmail, addToMailingList, and emailBlast to implement ex4's type
510 | // signature.
511 |
512 | // addToMailingList :: Email -> IO([Email])
513 | var addToMailingList = (function(list) {
514 | return function(email) {
515 | return new IO(function() {
516 | list.push(email);
517 | return list;
518 | });
519 | };
520 | })([]);
521 |
522 | function emailBlast(list) {
523 | return new IO(function() {
524 | return 'emailed: ' + list.join(',');
525 | });
526 | }
527 |
528 | var validateEmail = function(x) {
529 | return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
530 | };
531 |
532 | // ex4 :: Email -> Either String (IO String)
533 | var ex4 = undefined;
534 | ```
535 |
--------------------------------------------------------------------------------
/code/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MAG_lib",
3 | "version": "0.0.1",
4 | "description": "Support libs for the book",
5 | "main": "index.js",
6 | "dependencies": {
7 | "ramda": "^0.13.0"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/DrBoolean/mostly-adequate-guide"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "bugs": {
16 | "url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
17 | },
18 | "homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
19 | }
20 |
--------------------------------------------------------------------------------
/code/part1_demo/flickr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/code/part1_demo/flickr.js:
--------------------------------------------------------------------------------
1 | requirejs.config({
2 | paths: {
3 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min',
4 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min'
5 | }
6 | });
7 |
8 | require([
9 | 'ramda',
10 | 'jquery'
11 | ],
12 | function (_, $) {
13 | ////////////////////////////////////////////
14 | // Utils
15 | //
16 | var img = function (url) {
17 | return $('
', { src: url });
18 | };
19 |
20 | var Impure = {
21 | getJSON: _.curry(function(callback, url) {
22 | $.getJSON(url, callback)
23 | }),
24 |
25 | setHtml: _.curry(function(sel, html) {
26 | $(sel).html(html)
27 | })
28 | }
29 |
30 | var trace = _.curry(function(tag, x) {
31 | console.log(tag, x);
32 | return x;
33 | })
34 |
35 | ////////////////////////////////////////////
36 | // Pure
37 |
38 | // url :: String -> URL
39 | var url = function (t) {
40 | return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?';
41 | };
42 |
43 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
44 |
45 | var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
46 |
47 | var images = _.compose(_.map(img), srcs);
48 |
49 |
50 | ////////////////////////////////////////////
51 | // Impure
52 | //
53 | var renderImages = _.compose(Impure.setHtml("body"), images)
54 | var app = _.compose(Impure.getJSON(renderImages), url)
55 |
56 | app("cats")
57 | });
58 |
--------------------------------------------------------------------------------
/code/part1_exercises/README.md:
--------------------------------------------------------------------------------
1 | Part 1 Exercises
2 | ==================
3 |
4 | **Installation**:
5 | `npm install`
6 |
7 | **Running tests**:
8 | Tests are located in their corresponding folders. To run:
9 |
10 | ```
11 | cd exercises/curry
12 | mocha *spec.js
13 | ```
14 |
15 | Some will fail and some will pass. You'll need to edit the exercises until the tests pass.
16 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/compose/compose_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 | var accounting = require('accounting');
4 |
5 | // Example Data
6 | var CARS = [
7 | {name: "Ferrari FF", horsepower: 660, dollar_value: 700000, in_stock: true},
8 | {name: "Spyker C12 Zagato", horsepower: 650, dollar_value: 648000, in_stock: false},
9 | {name: "Jaguar XKR-S", horsepower: 550, dollar_value: 132000, in_stock: false},
10 | {name: "Audi R8", horsepower: 525, dollar_value: 114200, in_stock: false},
11 | {name: "Aston Martin One-77", horsepower: 750, dollar_value: 1850000, in_stock: true},
12 | {name: "Pagani Huayra", horsepower: 700, dollar_value: 1300000, in_stock: false}
13 | ];
14 |
15 | // Exercise 1:
16 | // ============
17 | var isLastInStock = _.compose(_.prop('in_stock'), _.last);
18 |
19 | // Exercise 2:
20 | // ============
21 | var nameOfFirstCar = _.compose(_.prop('name'), _.head);
22 |
23 |
24 | // Exercise 3:
25 | // ============
26 | // Use the helper function _average to refactor averageDollarValue as a composition
27 |
28 | var _average = function(xs) { return reduce(add, 0, xs) / xs.length; }; // <- leave be
29 |
30 | var averageDollarValue = _.compose(_average, _.map(_.prop('dollar_value')));
31 |
32 |
33 | // Exercise 4:
34 | // ============
35 | // Write a function: sanitizeNames() using compose that returns a list of lowercase and underscored names: e.g: sanitizeNames(["Hello World"]) //=> ["hello_world"].
36 |
37 | var _underscore = replace(/\W+/g, '_'); //<-- leave this alone and use to sanitize
38 |
39 | var sanitizeNames = _.map(_.compose(_underscore, toLowerCase, _.prop('name')));
40 |
41 |
42 | // Bonus 1:
43 | // ============
44 | // Refactor availablePrices with compose.
45 |
46 | var formatPrice = _.compose(accounting.formatMoney, _.prop('dollar_value'));
47 | var availablePrices = _.compose(join(', '), _.map(formatPrice), _.filter(_.prop('in_stock')));
48 |
49 | // Bonus 2:
50 | // ============
51 | // Refactor to pointfree. Hint: you can use _.flip()
52 |
53 | //+ fastestCar :: [Car] -> String
54 | var append = _.flip(_.concat);
55 | var fastestCar = _.compose(append(' is the fastest'),
56 | _.prop('name'),
57 | _.last,
58 | _.sortBy(_.prop('horsepower')));
59 |
60 | module.exports = { CARS: CARS,
61 | isLastInStock: isLastInStock,
62 | nameOfFirstCar: nameOfFirstCar,
63 | fastestCar: fastestCar,
64 | averageDollarValue: averageDollarValue,
65 | availablePrices: availablePrices,
66 | sanitizeNames: sanitizeNames
67 | };
68 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/compose/compose_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./compose_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Compose Exercises", function(){
5 | var CARS = E.CARS;
6 |
7 | it('Exercise 1', function(){
8 | assert.equal(E.isLastInStock(CARS), false);
9 | });
10 |
11 | it('Exercise 2', function(){
12 | assert.equal(E.nameOfFirstCar(CARS), "Ferrari FF");
13 | });
14 |
15 | it('Exercise 3', function(){
16 | assert.equal(E.averageDollarValue(CARS), 790700);
17 | });
18 |
19 | it('Exercise 4', function(){
20 | assert.deepEqual(E.sanitizeNames(CARS), ['ferrari_ff', 'spyker_c12_zagato', 'jaguar_xkr_s', 'audi_r8', 'aston_martin_one_77', 'pagani_huayra']);
21 | });
22 |
23 | it('Bonus 1', function(){
24 | assert.equal(E.availablePrices(CARS), '$700,000.00, $1,850,000.00');
25 | });
26 |
27 | it('Bonus 2', function(){
28 | assert.equal(E.fastestCar(CARS), 'Aston Martin One-77 is the fastest');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/curry/curry_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 |
4 | console.log('add', map(add(2)));
5 |
6 |
7 | // Exercise 1
8 | //==============
9 |
10 | var words = split(' ');
11 |
12 | // Exercise 1a
13 | //==============
14 |
15 | var sentences = map(words);
16 |
17 |
18 | // Exercise 2
19 | //==============
20 |
21 | var filterQs = filter(match(/q/i));
22 |
23 |
24 | // Exercise 3
25 | //==============
26 | // Use the helper function _keepHighest to refactor max
27 |
28 | var _keepHighest = function(x,y){ return x >= y ? x : y; }; // <- leave be
29 |
30 | var max = reduce(_keepHighest, -Infinity);
31 |
32 |
33 | // Bonus 1:
34 | // ============
35 | // wrap array's slice to be functional and curried.
36 | // //[1,2,3].slice(0, 2)
37 | var slice = _.curry(function(start, end, xs){ return xs.slice(start, end); });
38 |
39 |
40 | // Bonus 2:
41 | // ============
42 | // use slice to define a function "take" that takes n elements. make it curried
43 | var take = slice(0);
44 |
45 |
46 | module.exports = { words: words,
47 | sentences: sentences,
48 | filterQs: filterQs,
49 | max: max,
50 | slice: slice,
51 | take: take
52 | };
53 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/curry/curry_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./curry_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Curry Exercises", function(){
5 |
6 | it('Exercise 1', function(){
7 | assert.deepEqual(E.words("Jingle bells Batman smells"), ['Jingle', 'bells', 'Batman', 'smells']);
8 | });
9 |
10 | it('Exercise 1a', function(){
11 | assert.deepEqual(E.sentences(["Jingle bells Batman smells", "Robin laid an egg"]), [['Jingle', 'bells', 'Batman', 'smells'], ['Robin', 'laid', 'an', 'egg']]);
12 | });
13 |
14 | it('Exercise 2', function(){
15 | assert.deepEqual(E.filterQs(['quick', 'camels', 'quarry', 'over', 'quails']), ['quick', 'quarry', 'quails']);
16 | });
17 |
18 | it('Exercise 3', function(){
19 | assert.equal(E.max([323,523,554,123,5234]), 5234);
20 | });
21 |
22 | if (E.slice != undefined) {
23 | it('Curry Bonus 1', function(){
24 | assert.deepEqual(E.slice(1)(3)(['a', 'b', 'c']), ['b', 'c']);
25 | });
26 | }
27 |
28 | if (E.take != undefined) {
29 | it('Curry Bonus 2', function(){
30 | assert.deepEqual(E.take(2)(['a', 'b', 'c']), ['a', 'b']);
31 | });
32 | }
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/compose/compose_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 | var accounting = require('accounting');
4 |
5 | // Example Data
6 | var CARS = [
7 | {name: "Ferrari FF", horsepower: 660, dollar_value: 700000, in_stock: true},
8 | {name: "Spyker C12 Zagato", horsepower: 650, dollar_value: 648000, in_stock: false},
9 | {name: "Jaguar XKR-S", horsepower: 550, dollar_value: 132000, in_stock: false},
10 | {name: "Audi R8", horsepower: 525, dollar_value: 114200, in_stock: false},
11 | {name: "Aston Martin One-77", horsepower: 750, dollar_value: 1850000, in_stock: true},
12 | {name: "Pagani Huayra", horsepower: 700, dollar_value: 1300000, in_stock: false}
13 | ];
14 |
15 | // Exercise 1:
16 | // ============
17 | // use _.compose() to rewrite the function below. Hint: _.prop() is curried.
18 | var isLastInStock = function(cars) {
19 | var reversed_cars = _.last(cars);
20 | return _.prop('in_stock', reversed_cars)
21 | };
22 |
23 | // Exercise 2:
24 | // ============
25 | // use _.compose(), _.prop() and _.head() to retrieve the name of the first car
26 | var nameOfFirstCar = undefined;
27 |
28 |
29 | // Exercise 3:
30 | // ============
31 | // Use the helper function _average to refactor averageDollarValue as a composition
32 | var _average = function(xs) { return reduce(add, 0, xs) / xs.length; }; // <- leave be
33 |
34 | var averageDollarValue = function(cars) {
35 | var dollar_values = map(function(c) { return c.dollar_value; }, cars);
36 | return _average(dollar_values);
37 | };
38 |
39 |
40 | // Exercise 4:
41 | // ============
42 | // Write a function: sanitizeNames() using compose that takes an array of cars and returns a list of lowercase and underscored names: e.g: sanitizeNames([{name: "Ferrari FF"}]) //=> ["ferrari_ff"].
43 |
44 | var _underscore = replace(/\W+/g, '_'); //<-- leave this alone and use to sanitize
45 |
46 | var sanitizeNames = undefined;
47 |
48 |
49 | // Bonus 1:
50 | // ============
51 | // Refactor availablePrices with compose.
52 |
53 | var availablePrices = function(cars) {
54 | var available_cars = _.filter(_.prop('in_stock'), cars);
55 | return available_cars.map(function(x){
56 | return accounting.formatMoney(x.dollar_value)
57 | }).join(', ');
58 | };
59 |
60 |
61 | // Bonus 2:
62 | // ============
63 | // Refactor to pointfree. Hint: you can use _.flip()
64 |
65 | var fastestCar = function(cars) {
66 | var sorted = _.sortBy(function(car){ return car.horsepower }, cars);
67 | var fastest = _.last(sorted);
68 | return fastest.name + ' is the fastest';
69 | };
70 |
71 |
72 | module.exports = { CARS: CARS,
73 | isLastInStock: isLastInStock,
74 | nameOfFirstCar: nameOfFirstCar,
75 | fastestCar: fastestCar,
76 | averageDollarValue: averageDollarValue,
77 | availablePrices: availablePrices,
78 | sanitizeNames: sanitizeNames
79 | };
80 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/compose/compose_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./compose_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Compose Exercises", function(){
5 | var CARS = E.CARS
6 |
7 | it('Exercise 1', function(){
8 | assert.equal(E.isLastInStock(CARS), false);
9 | });
10 |
11 | it('Exercise 2', function(){
12 | assert.equal(E.nameOfFirstCar(CARS), "Ferrari FF");
13 | });
14 |
15 | it('Exercise 3', function(){
16 | assert.equal(E.averageDollarValue(CARS), 790700);
17 | });
18 |
19 | it('Exercise 4', function(){
20 | assert.deepEqual(E.sanitizeNames(CARS), ['ferrari_ff', 'spyker_c12_zagato', 'jaguar_xkr_s', 'audi_r8', 'aston_martin_one_77', 'pagani_huayra']);
21 | });
22 |
23 | it('Bonus 1', function(){
24 | assert.equal(E.availablePrices(CARS), '$700,000.00, $1,850,000.00');
25 | });
26 |
27 | it('Bonus 2', function(){
28 | assert.equal(E.fastestCar(CARS), 'Aston Martin One-77 is the fastest');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/curry/curry_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 |
4 |
5 | // Exercise 1
6 | //==============
7 | // Refactor to remove all arguments by partially applying the function
8 |
9 | var words = function(str) {
10 | return split(' ', str);
11 | };
12 |
13 | // Exercise 1a
14 | //==============
15 | // Use map to make a new words fn that works on an array of strings.
16 |
17 | var sentences = undefined;
18 |
19 |
20 | // Exercise 2
21 | //==============
22 | // Refactor to remove all arguments by partially applying the functions
23 |
24 | var filterQs = function(xs) {
25 | return filter(function(x){ return match(/q/i, x); }, xs);
26 | };
27 |
28 |
29 | // Exercise 3
30 | //==============
31 | // Use the helper function _keepHighest to refactor max to not reference any arguments
32 |
33 | // LEAVE BE:
34 | var _keepHighest = function(x,y){ return x >= y ? x : y; };
35 |
36 | // REFACTOR THIS ONE:
37 | var max = function(xs) {
38 | return reduce(function(acc, x){
39 | return _keepHighest(acc, x);
40 | }, 0, xs);
41 | };
42 |
43 |
44 | // Bonus 1:
45 | // ============
46 | // wrap array's slice to be functional and curried.
47 | // //[1,2,3].slice(0, 2)
48 | var slice = undefined;
49 |
50 |
51 | // Bonus 2:
52 | // ============
53 | // use slice to define a function "take" that takes n elements. Make it curried
54 | var take = undefined;
55 |
56 |
57 | module.exports = { words: words,
58 | sentences: sentences,
59 | filterQs: filterQs,
60 | max: max,
61 | slice: slice,
62 | take: take
63 | };
64 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/curry/curry_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./curry_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Curry Exercises", function(){
5 |
6 | it('Exercise 1', function(){
7 | assert.deepEqual(E.words("Jingle bells Batman smells"), ['Jingle', 'bells', 'Batman', 'smells']);
8 | });
9 |
10 | it('Exercise 1a', function(){
11 | assert.deepEqual(E.sentences(["Jingle bells Batman smells", "Robin laid an egg"]), [['Jingle', 'bells', 'Batman', 'smells'], ['Robin', 'laid', 'an', 'egg']]);
12 | });
13 |
14 | it('Exercise 2', function(){
15 | assert.deepEqual(E.filterQs(['quick', 'camels', 'quarry', 'over', 'quails']), ['quick', 'quarry', 'quails']);
16 | });
17 |
18 | it('Exercise 3', function(){
19 | assert.equal(E.max([323,523,554,123,5234]), 5234);
20 | });
21 |
22 | if (E.slice != undefined) {
23 | it('Curry Bonus 1', function(){
24 | assert.deepEqual(E.slice(1)(3)(['a', 'b', 'c']), ['b', 'c']);
25 | });
26 | }
27 |
28 | if (E.take != undefined) {
29 | it('Curry Bonus 2', function(){
30 | assert.deepEqual(E.take(2)(['a', 'b', 'c']), ['a', 'b']);
31 | });
32 | }
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/code/part1_exercises/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MAG_Part1_Exercises",
3 | "version": "0.0.1",
4 | "description": "Exercises for Part 1 of the Book",
5 | "main": "index.js",
6 | "dependencies": {
7 | "accounting": "^0.4.1",
8 | "chai": "^1.9.1",
9 | "ramda": "^0.13.0"
10 | },
11 | "devDependencies": {
12 | "mocha": "^1.17.1"
13 | },
14 | "scripts": {
15 | "test": "mocha exercises/**/*_spec.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/DrBoolean/mostly-adequate-guide"
20 | },
21 | "author": "",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
25 | },
26 | "homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
27 | }
28 |
--------------------------------------------------------------------------------
/code/part1_exercises/support.js:
--------------------------------------------------------------------------------
1 | //var curry = require('ramda').curry;
2 | //
3 | //
4 | //
5 | //
6 | function inspect(x) {
7 | return (typeof x === 'function') ? inspectFn(x) : inspectArgs(x);
8 | }
9 |
10 | function inspectFn(f) {
11 | return (f.name) ? f.name : f.toString();
12 | }
13 |
14 | function inspectArgs(args) {
15 | return args.reduce(function(acc, x){
16 | return acc += inspect(x);
17 | }, '(') + ')';
18 | }
19 |
20 | function curry(fx) {
21 | var arity = fx.length;
22 |
23 | return function f1() {
24 | var args = Array.prototype.slice.call(arguments, 0);
25 | if (args.length >= arity) {
26 | return fx.apply(null, args);
27 | }
28 | else {
29 | var f2 = function f2() {
30 | var args2 = Array.prototype.slice.call(arguments, 0);
31 | return f1.apply(null, args.concat(args2));
32 | }
33 | f2.toString = function() {
34 | return inspectFn(fx) + inspectArgs(args);
35 | }
36 | return f2;
37 | }
38 | };
39 | }
40 |
41 | compose = function() {
42 | var fns = toArray(arguments),
43 | arglen = fns.length;
44 |
45 | return function(){
46 | for(var i=arglen;--i>=0;) {
47 | var fn = fns[i]
48 | , args = fn.length ? Array.prototype.slice.call(arguments, 0, fn.length) : arguments
49 | , next_args = Array.prototype.slice.call(arguments, (fn.length || 1)); //not right with *args
50 | next_args.unshift(fn.apply(this,args));
51 | arguments = next_args;
52 | }
53 | return arguments[0];
54 | }
55 | }
56 |
57 | add = curry(function(x, y) {
58 | return x + y;
59 | });
60 |
61 | match = curry(function(what, x) {
62 | return x.match(what);
63 | });
64 |
65 | replace = curry(function(what, replacement, x) {
66 | return x.replace(what, replacement);
67 | });
68 |
69 | filter = curry(function(f, xs) {
70 | return xs.filter(f);
71 | });
72 |
73 | map = curry(function map(f, xs) {
74 | return xs.map(f);
75 | });
76 |
77 | reduce = curry(function(f, a, xs) {
78 | return xs.reduce(f, a);
79 | });
80 |
81 | split = curry(function(what, x) {
82 | return x.split(what);
83 | });
84 |
85 | join = curry(function(what, x) {
86 | return x.join(what);
87 | });
88 |
89 | toUpperCase = function(x) {
90 | return x.toUpperCase()
91 | };
92 |
93 | toLowerCase = function(x) {
94 | return x.toLowerCase()
95 | };
96 |
--------------------------------------------------------------------------------
/code/part2_exercises/README.md:
--------------------------------------------------------------------------------
1 | Part 2 Exercises
2 | ==================
3 |
4 | **Installation**:
5 | `npm install`
6 |
7 | **Running tests**:
8 | Tests are located in their corresponding folders. To run:
9 |
10 | ```
11 | cd exercises/curry
12 | mocha *spec.js
13 | ```
14 |
15 | Some will fail and some will pass. You'll need to edit the exercises until the tests pass.
16 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/applicative/applicative_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // fib browser for test
6 | var localStorage = {};
7 |
8 |
9 |
10 | // Exercise 1
11 | // ==========
12 | // Write a function that add's two possibly null numbers together using Maybe and ap()
13 |
14 | var ex1 = function(x, y) {
15 | return Maybe.of(_.add).ap(Maybe.of(x)).ap(Maybe.of(y));
16 | };
17 |
18 |
19 | // Exercise 2
20 | // ==========
21 | // Rewrite 1 to use liftA2 instead of ap()
22 |
23 | var ex2 = liftA2(_.add);
24 |
25 |
26 |
27 | // Exercise 3
28 | // ==========
29 | // Run both getPost(n) and getComments(n) then render the page with both. (the n arg is arbitrary)
30 | var makeComments = _.reduce(function(acc, c){ return acc+""+c+"" }, "");
31 | var render = _.curry(function(p, cs) { return ""+p.title+"
"+makeComments(cs); });
32 |
33 |
34 | var ex3 = Task.of(render).ap(getPost(2)).ap(getComments(2));
35 | // or
36 | // var ex3 = liftA2(render, getPost(2), getComments(2))
37 |
38 |
39 |
40 |
41 | // Exercise 4
42 | // ==========
43 | // Write an IO that gets both player1 and player2 from the cache and starts the game
44 | localStorage.player1 = "toby";
45 | localStorage.player2 = "sally";
46 |
47 | var getCache = function(x) {
48 | return new IO(function() { return localStorage[x]; });
49 | }
50 | var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; });
51 |
52 | var ex4 = liftA2(game, getCache('player1'), getCache('player2'));
53 |
54 |
55 |
56 |
57 |
58 | // TEST HELPERS
59 | // =====================
60 |
61 | function getPost(i) {
62 | return new Task(function (rej, res) {
63 | setTimeout(function () { res({ id: i, title: 'Love them tasks' }); }, 300);
64 | });
65 | }
66 |
67 | function getComments(i) {
68 | return new Task(function (rej, res) {
69 | setTimeout(function () {
70 | res(["This book should be illegal", "Monads are like space burritos"]);
71 | }, 300);
72 | });
73 | }
74 |
75 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4}
76 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/applicative/applicative_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./applicative_exercises');
3 | var _ = require('ramda');
4 | var assert = require("chai").assert
5 |
6 | describe("Applicative Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(Maybe.of(5), E.ex1(2, 3));
10 | assert.deepEqual(Maybe.of(null), E.ex1(null, 3));
11 | });
12 |
13 | it('Exercise 2', function(){
14 | assert.deepEqual(Maybe.of(5), E.ex2(Maybe.of(2), Maybe.of(3)));
15 | assert.deepEqual(Maybe.of(null), E.ex2(Maybe.of(null), Maybe.of(3)));
16 | });
17 |
18 | it('Exercise 3', function(done){
19 | E.ex3.fork(console.log, function (html) {
20 | assert.equal("Love them tasks
This book should be illegalMonads are like space burritos", html);
21 | done();
22 | });
23 | });
24 |
25 | it('Exercise 4', function(){
26 | assert.equal("toby vs sally", E.ex4.unsafePerformIO());
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/functors/functor_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // Exercise 1
6 | // ==========
7 | // Use _.add(x,y) and map(f,x) to make a function that increments a value inside a functor
8 |
9 | var ex1 = _.map(_.add(1));
10 |
11 |
12 |
13 | // Exercise 2
14 | // ==========
15 | // Use _.head to get the first element of the list
16 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
17 |
18 | var ex2 = _.map(_.head);
19 |
20 |
21 |
22 | // Exercise 3
23 | // ==========
24 | // Use safeProp and _.head to find the first initial of the user
25 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
26 |
27 | var user = { id: 2, name: "Albert" };
28 |
29 | var ex3 = _.compose(_.map(_.head), safeProp('name'));
30 |
31 |
32 | // Exercise 4
33 | // ==========
34 | // Use Maybe to rewrite ex4 without an if statement
35 |
36 | var ex4 = function (n) {
37 | if (n) { return parseInt(n); }
38 | };
39 |
40 | var ex4 = _.compose(_.map(parseInt), Maybe.of);
41 |
42 |
43 | // Exercise 5
44 | // ==========
45 | // Write a function that will getPost then toUpperCase the post's title
46 |
47 | // getPost :: Int -> Task({id: Int, title: String})
48 | var getPost = function (i) {
49 | return new Task(function(rej, res) {
50 | setTimeout(function(){
51 | res({id: i, title: 'Love them futures'})
52 | }, 300)
53 | });
54 | };
55 |
56 | var upperTitle = _.compose(toUpperCase, _.prop('title'));
57 | var ex5 = _.compose(_.map(upperTitle), getPost);
58 |
59 |
60 |
61 | // Exercise 6
62 | // ==========
63 | // Write a function that uses checkActive() and showWelcome() to grant access or return the error
64 |
65 | var showWelcome = _.compose(_.concat( "Welcome "), _.prop('name'))
66 |
67 | var checkActive = function(user) {
68 | return user.active ? Right.of(user) : Left.of('Your account is not active')
69 | }
70 |
71 | var ex6 = _.compose(_.map(showWelcome), checkActive)
72 |
73 |
74 |
75 | // Exercise 7
76 | // ==========
77 | // Write a validation function that checks for a length > 3. It should return Right(x) if it is greater than 3 and Left("You need > 3") otherwise
78 |
79 | var ex7 = function(x) {
80 | return x.length > 3 ? Right.of(x) : Left.of("You need > 3");
81 | }
82 |
83 |
84 |
85 | // Exercise 8
86 | // ==========
87 | // Use ex7 above and Either as a functor to save the user if they are valid or return the error message string. Remember either's two arguments must return the same type.
88 |
89 | var save = function(x){
90 | return new IO(function(){
91 | console.log("SAVED USER!");
92 | return x + '-saved';
93 | });
94 | }
95 |
96 | var ex8 = _.compose(either(IO.of, save), ex7)
97 |
98 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7, ex8: ex8}
99 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/functors/functor_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./functor_exercises');
3 | var assert = require("chai").assert;
4 |
5 | describe("Functor Exercises", function(){
6 |
7 | it('Exercise 1', function(){
8 | assert.deepEqual(Identity.of(3), E.ex1(Identity.of(2)));
9 | });
10 |
11 | it('Exercise 2', function(){
12 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
13 | assert.deepEqual(Identity.of('do'), E.ex2(xs));
14 | });
15 |
16 | it('Exercise 3', function(){
17 | var user = { id: 2, name: "Albert" };
18 | assert.deepEqual(Maybe.of('A'), E.ex3(user));
19 | });
20 |
21 | it('Exercise 4', function(){
22 | assert.deepEqual(Maybe.of(4), E.ex4("4"));
23 | });
24 |
25 | it('Exercise 5', function(done){
26 | E.ex5(13).fork(console.log, function(res){
27 | assert.deepEqual('LOVE THEM FUTURES', res);
28 | done();
29 | })
30 | });
31 |
32 | it('Exercise 6', function(){
33 | assert.deepEqual(Left.of('Your account is not active'), E.ex6({active: false, name: 'Gary'}));
34 | assert.deepEqual(Right.of('Welcome Theresa'), E.ex6({active: true, name: 'Theresa'}));
35 | });
36 |
37 | it('Exercise 7', function(){
38 | assert.deepEqual(Right.of("fpguy99"), E.ex7("fpguy99"));
39 | assert.deepEqual(Left.of("You need > 3"), E.ex7("..."));
40 | });
41 |
42 | it('Exercise 8', function(){
43 | assert.deepEqual("fpguy99-saved", E.ex8("fpguy99").unsafePerformIO());
44 | assert.deepEqual("You need > 3", E.ex8("...").unsafePerformIO());
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/monads/monad_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var path = require('path');
3 | var Task = require('data.task');
4 | var _ = require('ramda');
5 |
6 | // Exercise 1
7 | // ==========
8 | // Use safeProp and map/join or chain to safely get the street name when given a user
9 |
10 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
11 | var user = {
12 | id: 2,
13 | name: "albert",
14 | address: {
15 | street: {
16 | number: 22,
17 | name: 'Walnut St'
18 | }
19 | }
20 | };
21 |
22 | var ex1 = _.compose(chain(safeProp('name')), chain(safeProp('street')), safeProp('address'));
23 |
24 |
25 | // Exercise 2
26 | // ==========
27 | // Use getFile to get the filename, remove the directory so it's just the file, then purely log it.
28 |
29 | var getFile = function() {
30 | return new IO(function(){ return __filename; });
31 | };
32 |
33 | var pureLog = function(x) {
34 | return new IO(function(){
35 | console.log(x);
36 | return 'logged ' + x; // for testing w/o mocks
37 | });
38 | };
39 |
40 | var ex2 = _.compose(chain(_.compose(pureLog, _.last, split(path.sep))), getFile);
41 |
42 |
43 |
44 | // Exercise 3
45 | // ==========
46 | // Use getPost() then pass the post's id to getComments().
47 |
48 | var getPost = function(i) {
49 | return new Task(function (rej, res) {
50 | setTimeout(function () {
51 | res({ id: i, title: 'Love them tasks' }); // THE POST
52 | }, 300);
53 | });
54 | };
55 |
56 | var getComments = function(i) {
57 | return new Task(function (rej, res) {
58 | setTimeout(function () {
59 | res([{post_id: i, body: "This book should be illegal"}, {post_id: i, body:"Monads are like smelly shallots"}]);
60 | }, 300);
61 | });
62 | };
63 |
64 | var ex3 = _.compose(chain(_.compose(getComments, _.prop('id'))), getPost);
65 |
66 |
67 | // Exercise 4
68 | // ==========
69 | // Use validateEmail and addToMailingList to implement ex4's type signature. It should
70 |
71 | // addToMailingList :: Email -> IO([Email])
72 | var addToMailingList = (function(list){
73 | return function(email) {
74 | return new IO(function(){
75 | list.push(email);
76 | return list;
77 | });
78 | }
79 | })([]);
80 |
81 | function emailBlast(list) {
82 | return new IO(function(){
83 | return 'emailed: ' + list.join(','); // for testing w/o mocks
84 | });
85 | }
86 |
87 | var validateEmail = function(x){
88 | return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
89 | };
90 |
91 | // ex4 :: Email -> Either String (IO String)
92 | var ex4 = _.compose(_.map(_.compose(chain(emailBlast), addToMailingList)), validateEmail);
93 |
94 |
95 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, user: user}
96 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/monads/monad_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./monad_exercises');
3 | var assert = require("chai").assert
4 | var _ = require('ramda');
5 |
6 | describe("Monad Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(E.ex1(E.user), Maybe.of('Walnut St'));
10 | });
11 |
12 | it('Exercise 2', function(){
13 | assert.equal(E.ex2(undefined).unsafePerformIO(), 'logged monad_exercises.js');
14 | });
15 |
16 | it('Exercise 3', function(done){
17 | E.ex3(13).fork(console.log, function (res) {
18 | assert.deepEqual(res.map(_.prop('post_id')), [13, 13]);
19 | done();
20 | });
21 | });
22 |
23 | it('Exercise 4', function(){
24 | var getResult = either(_.identity, unsafePerformIO);
25 | assert.equal(getResult(E.ex4('notanemail')), 'invalid email');
26 | assert.equal(getResult(E.ex4('sleepy@grandpa.net')), 'emailed: sleepy@grandpa.net');
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/applicative/applicative_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // fib browser for test
6 | var localStorage = {};
7 |
8 |
9 |
10 | // Exercise 1
11 | // ==========
12 | // Write a function that add's two possibly null numbers together using Maybe and ap()
13 |
14 | // ex1 :: Number -> Number -> Maybe Number
15 | var ex1 = function(x, y) {
16 | // write me
17 | };
18 |
19 |
20 | // Exercise 2
21 | // ==========
22 | // Now write a function that takes 2 Maybe's and adds them. Use liftA2 instead of ap().
23 |
24 | // ex2 :: Maybe Number -> Maybe Number -> Maybe Number
25 | var ex2 = undefined;
26 |
27 |
28 |
29 | // Exercise 3
30 | // ==========
31 | // Run both getPost(n) and getComments(n) then render the page with both. (the n arg is arbitrary)
32 | var makeComments = _.reduce(function(acc, c){ return acc+""+c+"" }, "");
33 | var render = _.curry(function(p, cs) { return ""+p.title+"
"+makeComments(cs); });
34 |
35 | // ex3 :: Task Error HTML
36 | var ex3 = undefined;
37 |
38 |
39 |
40 | // Exercise 4
41 | // ==========
42 | // Write an IO that gets both player1 and player2 from the cache and starts the game
43 | localStorage.player1 = "toby";
44 | localStorage.player2 = "sally";
45 |
46 | var getCache = function(x) {
47 | return new IO(function() { return localStorage[x]; });
48 | }
49 | var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; });
50 |
51 | // ex4 :: IO String
52 | var ex4 = undefined;
53 |
54 |
55 |
56 |
57 |
58 | // TEST HELPERS
59 | // =====================
60 |
61 | function getPost(i) {
62 | return new Task(function (rej, res) {
63 | setTimeout(function () { res({ id: i, title: 'Love them futures' }); }, 300);
64 | });
65 | }
66 |
67 | function getComments(i) {
68 | return new Task(function (rej, res) {
69 | setTimeout(function () {
70 | res(["This book should be illegal", "Monads are like space burritos"]);
71 | }, 300);
72 | });
73 | }
74 |
75 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4}
76 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/applicative/applicative_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./applicative_exercises');
3 | var _ = require('ramda');
4 | var assert = require("chai").assert
5 |
6 | describe("Applicative Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(Maybe.of(5), E.ex1(2, 3));
10 | assert.deepEqual(Maybe.of(null), E.ex1(null, 3));
11 | });
12 |
13 | it('Exercise 2', function(){
14 | assert.deepEqual(Maybe.of(5), E.ex2(Maybe.of(2), Maybe.of(3)));
15 | assert.deepEqual(Maybe.of(null), E.ex2(Maybe.of(null), Maybe.of(3)));
16 | });
17 |
18 | it('Exercise 3', function(done){
19 | E.ex3.fork(console.log, function (html) {
20 | assert.equal("Love them futures
This book should be illegalMonads are like space burritos", html);
21 | done();
22 | });
23 | });
24 |
25 | it('Exercise 4', function(){
26 | assert.equal("toby vs sally", E.ex4.unsafePerformIO());
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/functors/functor_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // Exercise 1
6 | // ==========
7 | // Use _.add(x,y) and _.map(f,x) to make a function that increments a value inside a functor
8 |
9 | var ex1 = undefined;
10 |
11 |
12 |
13 | // Exercise 2
14 | // ==========
15 | // Use _.head to get the first element of the list
16 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
17 |
18 | var ex2 = undefined;
19 |
20 |
21 |
22 | // Exercise 3
23 | // ==========
24 | // Use safeProp and _.head to find the first initial of the user
25 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
26 |
27 | var user = { id: 2, name: "Albert" };
28 |
29 | var ex3 = undefined;
30 |
31 |
32 |
33 | // Exercise 4
34 | // ==========
35 | // Use Maybe to rewrite ex4 without an if statement
36 |
37 | var ex4 = function (n) {
38 | if (n) { return parseInt(n); }
39 | };
40 |
41 | var ex4 = undefined;
42 |
43 |
44 |
45 | // Exercise 5
46 | // ==========
47 | // Write a function that will getPost then _.toUpper the post's title
48 |
49 | // getPost :: Int -> Future({id: Int, title: String})
50 | var getPost = function (i) {
51 | return new Task(function(rej, res) {
52 | setTimeout(function(){
53 | res({id: i, title: 'Love them futures'})
54 | }, 300)
55 | });
56 | };
57 |
58 | var ex5 = undefined;
59 |
60 |
61 |
62 | // Exercise 6
63 | // ==========
64 | // Write a function that uses checkActive() and showWelcome() to grant access or return the error
65 |
66 | var showWelcome = _.compose(_.concat( "Welcome "), _.prop('name'));
67 |
68 | var checkActive = function(user) {
69 | return user.active ? Right.of(user) : Left.of('Your account is not active')
70 | };
71 |
72 | var ex6 = undefined;
73 |
74 |
75 |
76 | // Exercise 7
77 | // ==========
78 | // Write a validation function that checks for a length > 3. It should return Right(x) if it is greater than 3 and Left("You need > 3") otherwise
79 |
80 | var ex7 = function(x) {
81 | return undefined; // <--- write me. (don't be pointfree)
82 | };
83 |
84 |
85 |
86 | // Exercise 8
87 | // ==========
88 | // Use ex7 above and either as a functor to save the user if they are valid or return the error message string. Remember either's two arguments must return the same type.
89 |
90 | var save = function(x) {
91 | return new IO(function() {
92 | console.log("SAVED USER!");
93 | return x + '-saved';
94 | });
95 | };
96 |
97 | var ex8 = undefined;
98 |
99 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7, ex8: ex8};
100 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/functors/functor_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./functor_exercises');
3 | var assert = require("chai").assert;
4 |
5 | describe("Functor Exercises", function(){
6 |
7 | it('Exercise 1', function(){
8 | assert.deepEqual(E.ex1(Identity.of(2)), Identity.of(3));
9 | });
10 |
11 | it('Exercise 2', function(){
12 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
13 | assert.deepEqual(E.ex2(xs), Identity.of('do'));
14 | });
15 |
16 | it('Exercise 3', function(){
17 | var user = { id: 2, name: "Albert" };
18 | assert.deepEqual(E.ex3(user), Maybe.of('A'));
19 | });
20 |
21 | it('Exercise 4', function(){
22 | assert.deepEqual(E.ex4("4"), Maybe.of(4));
23 | });
24 |
25 | it('Exercise 5', function(done){
26 | E.ex5(13).fork(console.log, function(res){
27 | assert.deepEqual(res, 'LOVE THEM FUTURES');
28 | done();
29 | })
30 | });
31 |
32 | it('Exercise 6', function(){
33 | assert.deepEqual(E.ex6({active: false, name: 'Gary'}), Left.of('Your account is not active'));
34 | assert.deepEqual(E.ex6({active: true, name: 'Theresa'}), Right.of('Welcome Theresa'));
35 | });
36 |
37 | it('Exercise 7', function(){
38 | assert.deepEqual(E.ex7("fpguy99"), Right.of("fpguy99"));
39 | assert.deepEqual(E.ex7("..."), Left.of("You need > 3"));
40 | });
41 |
42 | it('Exercise 8', function(){
43 | assert.deepEqual(E.ex8("fpguy99").unsafePerformIO(), "fpguy99-saved");
44 | assert.deepEqual(E.ex8("...").unsafePerformIO(), "You need > 3");
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/monads/monad_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // Exercise 1
6 | // ==========
7 | // Use safeProp and map/join or chain to safely get the street name when given a user
8 |
9 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
10 | var user = {
11 | id: 2,
12 | name: "albert",
13 | address: {
14 | street: {
15 | number: 22,
16 | name: 'Walnut St'
17 | }
18 | }
19 | };
20 |
21 | var ex1 = undefined;
22 |
23 |
24 | // Exercise 2
25 | // ==========
26 | // Use getFile to get the filename, remove the directory so it's just the file, then purely log it.
27 |
28 | var getFile = function() {
29 | return new IO(function(){ return __filename; });
30 | }
31 |
32 | var pureLog = function(x) {
33 | return new IO(function(){
34 | console.log(x);
35 | return 'logged ' + x; // for testing w/o mocks
36 | });
37 | }
38 |
39 | var ex2 = undefined;
40 |
41 |
42 |
43 | // Exercise 3
44 | // ==========
45 | // Use getPost() then pass the post's id to getComments().
46 |
47 | var getPost = function(i) {
48 | return new Task(function (rej, res) {
49 | setTimeout(function () {
50 | res({ id: i, title: 'Love them tasks' }); // THE POST
51 | }, 300);
52 | });
53 | }
54 |
55 | var getComments = function(i) {
56 | return new Task(function (rej, res) {
57 | setTimeout(function () {
58 | res([{post_id: i, body: "This book should be illegal"}, {post_id: i, body:"Monads are like smelly shallots"}]);
59 | }, 300);
60 | });
61 | }
62 |
63 | var ex3 = undefined;
64 |
65 |
66 | // Exercise 4
67 | // ==========
68 | // Use validateEmail, addToMailingList and emailBlast to implement ex4's type signature.
69 | // It should safely add a new subscriber to the list, then email everyone with this happy news.
70 |
71 | // addToMailingList :: Email -> IO [Email]
72 | var addToMailingList = (function(list){
73 | return function(email) {
74 | return new IO(function(){
75 | list.push(email);
76 | return list;
77 | });
78 | }
79 | })([]);
80 |
81 | // emailBlast :: [Email] -> IO String
82 | function emailBlast(list) {
83 | return new IO(function(){
84 | return 'emailed: ' + list.join(','); // for testing w/o mocks
85 | });
86 | }
87 |
88 | // validateEmail :: Email -> Either String Email
89 | var validateEmail = function(x){
90 | return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
91 | }
92 |
93 | // ex4 :: Email -> Either String (IO String)
94 | var ex4 = undefined;
95 |
96 |
97 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, user: user}
98 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/monads/monad_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./monad_exercises');
3 | var assert = require("chai").assert
4 | var _ = require('ramda');
5 |
6 | describe("Monad Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(E.ex1(E.user), Maybe.of('Walnut St'));
10 | });
11 |
12 | it('Exercise 2', function(){
13 | assert.equal(E.ex2(undefined).unsafePerformIO(), 'logged monad_exercises.js');
14 | });
15 |
16 | it('Exercise 3', function(done){
17 | E.ex3(13).fork(console.log, function (res) {
18 | assert.deepEqual(res.map(_.prop('post_id')), [13, 13]);
19 | done();
20 | });
21 | });
22 |
23 | it('Exercise 4', function(){
24 | var getResult = either(_.identity, unsafePerformIO);
25 | assert.equal(getResult(E.ex4('notanemail')), 'invalid email');
26 | assert.equal(getResult(E.ex4('sleepy@grandpa.net')), 'emailed: sleepy@grandpa.net');
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part2_exercises/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MAG_Part2_Exercises",
3 | "version": "0.0.1",
4 | "description": "Exercises for Part 2 of the Book",
5 | "main": "index.js",
6 | "dependencies": {
7 | "chai": "^1.9.1",
8 | "data.task": "^3.0.0",
9 | "ramda": "^0.13.0"
10 | },
11 | "devDependencies": {
12 | "mocha": "^1.17.1"
13 | },
14 | "scripts": {
15 | "test": "mocha exercises/**/*_spec.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/DrBoolean/mostly-adequate-guide"
20 | },
21 | "author": "",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
25 | },
26 | "homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
27 | }
28 |
--------------------------------------------------------------------------------
/code/part2_exercises/support.js:
--------------------------------------------------------------------------------
1 | require('../part1_exercises/support');
2 | var _ = require('ramda');
3 | var Task = require('data.task');
4 | var curry = _.curry;
5 |
6 | inspect = function(x) {
7 | return (x && x.inspect) ? x.inspect() : x;
8 | };
9 |
10 | toUpperCase = function(x) {
11 | return x.toUpperCase();
12 | };
13 |
14 | // Identity
15 | Identity = function(x) {
16 | this.__value = x;
17 | };
18 |
19 | Identity.of = function(x) { return new Identity(x); };
20 |
21 | Identity.prototype.map = function(f) {
22 | return Identity.of(f(this.__value));
23 | };
24 |
25 | Identity.prototype.inspect = function() {
26 | return 'Identity('+inspect(this.__value)+')';
27 | };
28 |
29 | // Maybe
30 | Maybe = function(x) {
31 | this.__value = x;
32 | };
33 |
34 | Maybe.of = function(x) {
35 | return new Maybe(x);
36 | };
37 |
38 | Maybe.prototype.isNothing = function(f) {
39 | return (this.__value === null || this.__value === undefined);
40 | };
41 |
42 | Maybe.prototype.map = function(f) {
43 | return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
44 | };
45 |
46 | Maybe.prototype.chain = function(f) {
47 | return this.map(f).join();
48 | };
49 |
50 | Maybe.prototype.ap = function(other) {
51 | return this.isNothing() ? Maybe.of(null) : other.map(this.__value);
52 | };
53 |
54 | Maybe.prototype.join = function() {
55 | return this.isNothing() ? Maybe.of(null) : this.__value;
56 | }
57 |
58 | Maybe.prototype.inspect = function() {
59 | return 'Maybe('+inspect(this.__value)+')';
60 | }
61 |
62 |
63 | // Either
64 | Either = function() {};
65 | Either.of = function(x) {
66 | return new Right(x);
67 | }
68 |
69 | Left = function(x) {
70 | this.__value = x;
71 | }
72 |
73 | // TODO: remove this nonsense
74 | Left.of = function(x) {
75 | return new Left(x);
76 | }
77 |
78 | Left.prototype.map = function(f) { return this; }
79 | Left.prototype.ap = function(other) { return this; }
80 | Left.prototype.join = function() { return this; }
81 | Left.prototype.chain = function() { return this; }
82 | Left.prototype.inspect = function() {
83 | return 'Left('+inspect(this.__value)+')';
84 | }
85 |
86 |
87 | Right = function(x) {
88 | this.__value = x;
89 | }
90 |
91 | // TODO: remove in favor of Either.of
92 | Right.of = function(x) {
93 | return new Right(x);
94 | }
95 |
96 | Right.prototype.map = function(f) {
97 | return Right.of(f(this.__value));
98 | }
99 |
100 | Right.prototype.join = function() {
101 | return this.__value;
102 | }
103 |
104 | Right.prototype.chain = function(f) {
105 | return f(this.__value);
106 | }
107 |
108 | Right.prototype.ap = function(other) {
109 | return this.chain(function(f) {
110 | return other.map(f);
111 | });
112 | }
113 |
114 | Right.prototype.join = function() {
115 | return this.__value;
116 | }
117 |
118 | Right.prototype.chain = function(f) {
119 | return f(this.__value);
120 | }
121 |
122 | Right.prototype.inspect = function() {
123 | return 'Right('+inspect(this.__value)+')';
124 | }
125 |
126 | // IO
127 | IO = function(f) {
128 | this.unsafePerformIO = f;
129 | }
130 |
131 | IO.of = function(x) {
132 | return new IO(function() {
133 | return x;
134 | });
135 | }
136 |
137 | IO.prototype.map = function(f) {
138 | return new IO(_.compose(f, this.unsafePerformIO));
139 | }
140 |
141 | IO.prototype.join = function() {
142 | return this.unsafePerformIO();
143 | }
144 |
145 | IO.prototype.chain = function(f) {
146 | return this.map(f).join();
147 | }
148 |
149 | IO.prototype.ap = function(a) {
150 | return this.chain(function(f) {
151 | return a.map(f);
152 | });
153 | }
154 |
155 | IO.prototype.inspect = function() {
156 | return 'IO('+inspect(this.unsafePerformIO)+')';
157 | }
158 |
159 | unsafePerformIO = function(x) { return x.unsafePerformIO(); }
160 |
161 | either = curry(function(f, g, e) {
162 | switch(e.constructor) {
163 | case Left: return f(e.__value);
164 | case Right: return g(e.__value);
165 | }
166 | });
167 |
168 | // overwriting join from pt 1
169 | join = function(m){ return m.join(); };
170 |
171 | chain = curry(function(f, m){
172 | return m.map(f).join(); // or compose(join, map(f))(m)
173 | });
174 |
175 | liftA2 = curry(function(f, a1, a2){
176 | return a1.map(f).ap(a2);
177 | });
178 |
179 | liftA3 = curry(function(f, a1, a2, a3){
180 | return a1.map(f).ap(a2).ap(a3);
181 | });
182 |
183 |
184 | Task.prototype.join = function(){ return this.chain(_.identity); }
185 |
--------------------------------------------------------------------------------
/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/cover.jpg
--------------------------------------------------------------------------------
/feedback_loop.md:
--------------------------------------------------------------------------------
1 | # Seting up an immediate feedback loop
2 |
3 | Clone the repo to a `local_folder`
4 |
5 | ```shell
6 | git clone https://github.com/DrBoolean/mostly-adequate-guide.git
7 | ```
8 |
9 | change directory to the code folder in repository root
10 | ```shell
11 | cd /path/to/local_folder/mostly-adequate-guide/code/part1_exercises/
12 | ```
13 |
14 | install dependencies
15 | ```shell
16 | npm install
17 | # this picks up the dependencies from package.json and installs them
18 | ```
19 |
20 | run the test runner with the spec file for that exercise
21 |
22 | ```shell
23 | # for example
24 | mocha exercises/curry/curry_exercises_spec.js -w
25 | ```
26 |
27 | this brings up the mocha test runner which shows the error immediately when you save the file with changed code. Achieve the requested refactor while keeping all the arrows green.
28 |
--------------------------------------------------------------------------------
/images/canopener.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/canopener.jpg
--------------------------------------------------------------------------------
/images/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/cat.png
--------------------------------------------------------------------------------
/images/cat_comp1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/cat_comp1.png
--------------------------------------------------------------------------------
/images/cat_comp2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/cat_comp2.png
--------------------------------------------------------------------------------
/images/cat_theory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/cat_theory.png
--------------------------------------------------------------------------------
/images/catmap copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/catmap copy.png
--------------------------------------------------------------------------------
/images/catmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/catmap.png
--------------------------------------------------------------------------------
/images/cats_ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/cats_ss.png
--------------------------------------------------------------------------------
/images/chain.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/chain.jpg
--------------------------------------------------------------------------------
/images/console_ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/console_ss.png
--------------------------------------------------------------------------------
/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/cover.png
--------------------------------------------------------------------------------
/images/dominoes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/dominoes.jpg
--------------------------------------------------------------------------------
/images/fists.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/fists.jpg
--------------------------------------------------------------------------------
/images/fn_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/fn_graph.png
--------------------------------------------------------------------------------
/images/function-sets.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/function-sets.gif
--------------------------------------------------------------------------------
/images/functormap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/functormap.png
--------------------------------------------------------------------------------
/images/functormapmaybe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/functormapmaybe.png
--------------------------------------------------------------------------------
/images/jar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/jar.jpg
--------------------------------------------------------------------------------
/images/monad_associativity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/monad_associativity.png
--------------------------------------------------------------------------------
/images/onion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/onion.png
--------------------------------------------------------------------------------
/images/plugs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/plugs.jpg
--------------------------------------------------------------------------------
/images/relation-not-function.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/relation-not-function.gif
--------------------------------------------------------------------------------
/images/ship_in_a_bottle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/ship_in_a_bottle.jpg
--------------------------------------------------------------------------------
/images/triangle_identity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigsawye/mostly-adequate-guide/69775fe342090ed14740422b00613193c0d59ec3/images/triangle_identity.png
--------------------------------------------------------------------------------