├── .gitignore
├── examples
├── news
│ ├── webpack.config.js
│ ├── index.html
│ ├── src
│ │ ├── utils.js
│ │ └── index.js
│ ├── package.json
│ ├── fp-example-news.xml
│ └── db
│ │ └── news.json
└── todoList
│ ├── index.html
│ ├── src
│ ├── index.css
│ └── index.jsx
│ ├── webpack.config.js
│ └── package.json
├── README.md
├── fp-composition-law.xml
├── beginer.md
└── advance.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/examples/news/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devtool: "source-map",
3 | mode: "development"
4 | };
5 |
--------------------------------------------------------------------------------
/examples/news/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | news list
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/todoList/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TodoList with react
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/news/src/utils.js:
--------------------------------------------------------------------------------
1 | function getUrlParam(name, search) {
2 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
3 | var r = search.substr(1).match(reg); //匹配目标参数
4 | if (r != null) return unescape(r[2]);
5 | return null; //返回参数值
6 | }
7 | module.exports = { getUrlParam };
8 |
--------------------------------------------------------------------------------
/examples/todoList/src/index.css:
--------------------------------------------------------------------------------
1 | .demo-wrapper {
2 | width: 800px;
3 | list-style: none;
4 | }
5 | .demo-todo-item {
6 | width: 300px;
7 |
8 | white-space: pre-line;
9 | word-break: break-all;
10 | padding: 15px;
11 | margin: 20px 0 0 45px;
12 | display: block;
13 |
14 | box-shadow: 0 0 1em gold;
15 | }
16 |
17 | .delete {
18 | float: right;
19 | color: red;
20 | }
21 |
--------------------------------------------------------------------------------
/examples/todoList/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devtool: "source-map",
3 | mode: "development",
4 | resolve: {
5 | extensions: [".js", ".jsx"]
6 | },
7 | module: {
8 | rules: [
9 | { test: /\.js|jsx$/, use: ["babel-loader?presets[]=react"] },
10 | {
11 | test: /\.css$/,
12 | use: ["style-loader", "css-loader"]
13 | }
14 | ]
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 函数式编程系列教程
2 |
3 | > javascript函数式编程指南。
4 |
5 | 写这个文章的原因在于函数式编程的思想非常先进,其天生的可预测性(也可以说是可测试), 更细粒度的代码(逻辑)重用,以及天生支持并行等特点, 已经被也业内越来越多的人认可。由于其很高的学习门槛导致大多人并不了解它,或者只是知道概念,并不能够理解并运用函数式编程,因此写下了这篇文章
6 | 。当然本人也不是函数式编程大师,如有错误,请斧正。
7 |
8 | ## 目录
9 |
10 | [函数式编程-入门](https://github.com/azl397985856/functional-programming/blob/master/beginer.md)
11 |
12 |
13 | [函数式编程-进阶](https://github.com/azl397985856/functional-programming/blob/master/advance.md)
14 |
15 |
16 | [函数式编程-精通]()
17 |
--------------------------------------------------------------------------------
/examples/news/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "news",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "main.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "webpack-dev-server --hot",
9 | "build": "webpack"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "webpack": "^4.12.0",
15 | "webpack-cli": "^3.0.8",
16 | "webpack-dev-server": "^3.1.4"
17 | },
18 | "dependencies": {
19 | "jquery": "^3.3.1",
20 | "ramda": "^0.25.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/todoList/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todoList",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "webpack-dev-server --hot",
9 | "build": "webpack"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "ramda": "^0.25.0",
16 | "react": "^16.4.1",
17 | "react-dom": "^16.4.1"
18 | },
19 | "devDependencies": {
20 | "babel-core": "^6.26.3",
21 | "babel-loader": "^7.1.4",
22 | "babel-preset-react": "^6.24.1",
23 | "css-loader": "^0.28.11",
24 | "style-loader": "^0.21.0",
25 | "webpack": "^4.12.0",
26 | "webpack-cli": "^3.0.8",
27 | "webpack-dev-server": "^3.1.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/fp-composition-law.xml:
--------------------------------------------------------------------------------
1 | 7VlNb6MwEP01SO1lBZiQ5Nikze5lpZV62LMDBrw1GDlO8/Hrd8CGACZVViWNuoVDhJ8/sN+bGY8dCy3T/XeB8+QnDwmzXDvcW+jRct35xIPfAjgowPcdBcSChgpqAM/0SDRoa3RLQ7JpNZScM0nzNhjwLCOBbGFYCL5rN4s4a381xzExgOcAMxP9TUOZKHTmTk/4D0LjpPqy489VzRoHL7Hg20x/z3JRVD6qOsXVWHqhmwSHfNeA0JOFloJzqd7S/ZKwgtqKNtVvdaa2nrcgmbykg6s6vGK2JdWMy3nJQ8UFdADaobDYJVSS5xwHRc0OhAcskSmDkgOvEWVsyRkXZT80L58C55nUEsNsVLnRzi4fwPVUiJBkf3Y5Tk0S2B7hKZHiAE2qDhPNa2V3urg7iYgqG0uaAlb9sDacuB76RB68aP76uZwYXEYmmSHYmS4Stua7pxOw+LNNc02U40O5bACFghIKxtmmm2ThQ2HqUOQ5yRSyosX0Hgs6N1LwF9Ig2g9mZB3VNZVhT0zpQkxmUfDP0hWLe1s44IJvRaBbIe3ZWMREt/L65RWEYUlf26O/RytkaLW3XB+nhUln601er6mhHZilbEvQpjjjGelwqSHMaAwCPQbABgF8USn6oCtSGobsnIuV4YSEWtaP9KZp25sc13Qnr8eb3AGcyZkZCsWjN73lTZ7pTc70g9zJM8Q6jO7jzm7oPvZ/trH7bS5hcHNnt/t2dm8INh2DzdVddD9MPEq4oEdg7hNGpGb4cdye+IMu1nnwkFTNp6XZ/n7c5Q3X6oQp1JM0Xy1MmXnY6u5gOtaXE8Wd31AU78sHtksOLu+IYbrrL07hIyfN7Y7m3UOpCq26V0fPehqXSTwZJb4gmx5c4vry4foS14saJT6XsUzNjKVxCXfl9GRq7HzHcdvz7BsemeY9uUg8JvlDJu9noiLqJjvz9hDKa42oaAzkdcNrd6ABw6t5wF7dHcfMFTRA3yY3y12ra46GLA+fXJOynZ68M4BAN7zU7bl2/+wH8Curg6ZXUweKp/8ZVQQ8/ZeLnv4C
--------------------------------------------------------------------------------
/examples/news/src/index.js:
--------------------------------------------------------------------------------
1 | const $ = require("jquery");
2 | const { curry, compose, map } = require("ramda");
3 |
4 | const { getUrlParam } = require("./utils");
5 |
6 | import news from "../db/news.json";
7 |
8 | const queryNews = curry((url, news) => {
9 | const title = getUrlParam("title", url.slice(url.indexOf("?")));
10 |
11 | return {
12 | items: news.filter(item => item.title.indexOf(title) !== -1)
13 | };
14 | });
15 | const ajax = (url, cb) =>
16 | setTimeout(() => cb(queryNews(url, news.items)), Math.random() * 1000);
17 |
18 | const getJSON = curry((cb, url) => ajax(url, cb));
19 | const setHtml = curry((selector, html) => $(selector).html(html));
20 | const trace = curry((tag, x) => {
21 | console.log(tag, x);
22 | return x;
23 | });
24 |
25 | const getNewsByTitle = title =>
26 | `http://news.test.com/api/v2/news?title=${title}`;
27 | const prop = curry((property, object) => object[property]);
28 | const coverUrls = compose(
29 | map(prop("cover")),
30 | trace("items: "),
31 | prop("items")
32 | );
33 |
34 | const img = src => $("
", { src });
35 |
36 | const images = compose(
37 | map(img),
38 | trace("coverUrls: "),
39 | coverUrls
40 | );
41 |
42 | const render = compose(
43 | setHtml("#main"),
44 | trace("iamges: "),
45 | images
46 | );
47 | const app = compose(
48 | getJSON(render),
49 | trace("news url: "),
50 | getNewsByTitle
51 | );
52 |
53 | app("hello world");
54 |
--------------------------------------------------------------------------------
/examples/todoList/src/index.jsx:
--------------------------------------------------------------------------------
1 | const React = require("react");
2 | import ReactDOM from "react-dom";
3 | const { compose, map } = require("ramda");
4 |
5 | const getItem = item => {item};
6 |
7 | const getWrapper = className => items => ;
8 |
9 | const render = compose(
10 | getWrapper("demo-wrapper"),
11 | map(getItem)
12 | );
13 |
14 | ReactDOM.render(
15 | render(["todo1", "todo2", "todo3", "todo4"]),
16 | document.getElementById("main")
17 | );
18 |
19 | // const store = {
20 | // todos: ["todo1", "todo2", "todo3", "todo4"]
21 | // };
22 | // // style
23 | // import "./index.css";
24 |
25 | // function forceRender() {
26 | // ReactDOM.render(render(store.todos), document.getElementById("main"));
27 | // }
28 | // function deleteTodoByItem(item) {
29 | // store.todos = store.todos.filter(todo => todo !== item);
30 | // forceRender();
31 | // }
32 |
33 | // const getItem = onDelete => classname => item => (
34 | //
35 | // {item}
36 | //
37 | // X
38 | //
39 | //
40 | // );
41 |
42 | // const getWrapper = className => items => ;
43 |
44 | // const render = compose(
45 | // getWrapper("demo-wrapper"),
46 | // map(getItem(deleteTodoByItem)("demo-todo-item"))
47 | // );
48 |
49 | // ReactDOM.render(render(store.todos), document.getElementById("main"));
50 |
--------------------------------------------------------------------------------
/examples/news/fp-example-news.xml:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/beginer.md:
--------------------------------------------------------------------------------
1 | # 函数式编程-入门
2 | 本篇文章是我的函数式编程教程中的第一篇文章,目的是让
3 | 大家对函数式编程有一个概念,为后面学习函数式编程提供基础。
4 |
5 | ## 介绍
6 | 函数式编程不过是相比于传统的面向过程和面向对象的一种新的编程方法而已。
7 |
8 | 他的出现并不是为了取代谁,也不会取代。他的出现只是为了改善传统的
9 | 编程方法中存在的问题。典型的一个作用就是就是限制系统的副作用。然而函数式编程
10 | 并不是用来消灭副作用,这是不切实际的。他的存在只是将副作用限制到某一个很小的范围。
11 | 使我们的程序的大部分逻辑都是纯粹的。
12 |
13 | 然而函数式编程离不开数学,你可以从函数式编程中看到数学之美。
14 | 你在初中高中大学用到的很多公式理论在这里都适用,很神奇,不是嘛?
15 | 让我们开始学习吧~
16 | ## 函数
17 | 一个常见的误解就是函数式编程中的函数指的就是用函数写代码。
18 | 比如JavaScript就是function。 这种理解是完全错误的。
19 |
20 | 然而函数式编程中的函数其实指的是数学中的函数。
21 | 让我们来回忆一下初中数学。
22 | ### 数学中的函数
23 | 我们来看下数学中的函数的定义:
24 |
25 | > 给定一个数集A,假设其中的元素为x。现对A中的元素x施加对应法则f,记作f(x),得到另一数集B。
26 | 假设B中的元素为y。则y与x之间的等量关系可以用y=f(x)表示。我们把这个关系式就叫函数关系式,简称函数。
27 |
28 | 函数概念含有三个要素:定义域A、值域C和对应法则f。其中核心是对应法则f,它是函数关系的本质特征。
29 |
30 |
31 | 函数式编程中的函数正是数学中的函数,而JavaScript中的函数是其超集。
32 | 这也就是为什么很多数学公式理论在函数式编程中都适用的原因。
33 | ### 纯函数
34 | 上面介绍了数学中的函数,我们已经知道了函数式编程中的函数指的正是数据中的函数。
35 | 那么数学中的函数在JavaScript中又是什么呢?
36 |
37 | 数学中的函数指的Javascript中的纯函数。
38 |
39 | > 这种说法不准确,因此纯函数并不是Javascript中的术语,这里只是方便解释。
40 |
41 | 那么什么是纯函数?
42 |
43 | > 纯函数就是给定输入,输出总是相同的函数。
44 |
45 | 我们在学习初中数学中的函数的时候,学过`定义域中的一个元素在值域有且仅有一个对应的值`。
46 | 这个其实和纯函数的定义是一致的。
47 |
48 | 纯函数的好处就是无副作用,不管我是执行一次,还是一百次,结果总是一样的。
49 | 那么这有什么用呢?
50 |
51 | - 可预测(也可以叫可测试)
52 |
53 | 一个很明显的好处就是可预测,即根据输入就可以知道输出。
54 | 利用这个特性,我们就可以很容易的去断言输出,也就更容易测试。
55 |
56 | redux声称是可预测的状态管理容器, 其可预测正是归功于纯函数的特性。
57 | redux中的reducer被要求是一个纯函数,所有的状态变化都经过reducer这个
58 | 纯函数去完成。 这样应用的状态(准确地讲是redux的store)就变得可预测,
59 | 也就方便测试。
60 |
61 | - 可缓存
62 |
63 | 由于给定输入,输出总是一定的。那么我们就可以将函数结果缓存起来,当
64 | `后续`调用的时候就可以直接从缓存中拿,避免了重新执行的开销。
65 | 这在大运算中是非常重要的。
66 |
67 | 很多函数式编程库都实现了memorize方法,我们拿ramda为例,
68 | 如下是ramda的官方文档对memorize的代码演示:
69 |
70 | ```js
71 | let count = 0;
72 | const factorial = R.memoize(n => {
73 | count += 1;
74 | return R.product(R.range(1, n + 1));
75 | });
76 | factorial(5); //=> 120
77 | factorial(5); //=> 120
78 | factorial(5); //=> 120
79 | count; //=> 1
80 | ```
81 |
82 | - 可并行
83 |
84 | 函数式编程由于其纯函数的特性,是天然支持并行的。
85 | 因为其不会因为时间的改变而导致函数的执行出现改变。
86 | 换句话说,在函数式编程中,时间并不是自变量。
87 |
88 | ## 一等公民
89 | 函数式编程的本质是将函数当作一等公民。
90 |
91 | 在理解这个概念以前,我们先来看下高阶函数。
92 | ### 高阶函数
93 | 高阶函数要么是以函数作为参数,要么以函数作为返回值,要么兼而有之。
94 |
95 | 一个简单的例子:
96 |
97 | ```js
98 | // 常见写法
99 | function add(x) {
100 | return y => x + y;
101 | }
102 |
103 | // 箭头函数写法
104 | const add = x => y => x + y;
105 |
106 | const addOne = add(1);
107 | addOne(2); // 3
108 | ```
109 | > 为了保持代码的简洁性,后面都采用箭头函数的形式书写。
110 |
111 | 然后js里面的map,sort,reduce等都是地地道道的高阶函数。
112 | 可以说我们一直在使用高阶函数,只是我们并不知情。就好像我们一直在使用闭包,
113 | 却可能并不知道闭包的概念一样。
114 |
115 | 事实上,js中的事件也是高阶函数。
116 |
117 | ```js
118 | // 将函数作为参数传入
119 | document.addEventListener("click", e => e);
120 | ```
121 | 高阶函数大大提高了代码的抽象能力,进而提高了代码的复用率。
122 | 通过传入不同的函数进而实现不同的效果,毫不夸张地讲,这种
123 | 抽象非常强大。理解高阶函数对于理解函数式编程至关重要,他是函数式编程中的基石。
124 | ## 柯里化
125 | 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,
126 | 并且返回接受余下的参数且返回结果的新函数的技术。
127 |
128 | 听起来比较拗口,让我们通过一个例子来看下。
129 | ```js
130 | // 取出对象里面的属性, 简单起见,省略了校验逻辑
131 | const get = key => obj => obj[key]
132 | const getId = get("id")
133 | const getName = get("name")
134 |
135 | const data = {
136 | id: 1,
137 | name: 'lucifer'
138 | }
139 | getId(data); // 1
140 | getName(data); // lucifer
141 | ```
142 |
143 | 上面的例子非常简洁,我们称之为pointfree风格。
144 | 让我们再看下非柯里化版本
145 |
146 | ```js
147 | const getByKey = (key, obj) => obj[key]
148 |
149 | // 繁琐
150 | const getId = data => getByKey("id", data);
151 | const getName = data => getByKey("name", data);
152 |
153 | const data = {
154 | id: 1,
155 | name: 'lucifer'
156 | }
157 |
158 | getId(data); // 1
159 | getName(data); // lucifer
160 | ```
161 |
162 | 由于非柯里化版本必须提供所有参数才能执行。
163 | 因此构造出的getId和getName必须提供data。
164 | 可能这个例子还不太明显,我们会在后面将pointfree部分举一个更复杂的例子。
165 |
166 | 其实我们前面将高阶函数部分举的add的例子已经是柯里化了。
167 | add本身接受两个参数,然后通过柯里化的方式将其编程接受一个参数的函数,执行它,
168 | 你得到了一个接受一个参数的值,再次执行,才会返回相加的结果。
169 |
170 | 纯正的函数式编程中,所有的函数都只有一个参数。
171 | 这样再js中就会写成这样`getId('id')(data)`。我们必须写下两个括号,
172 | 这是由于js本身的语法决定的,对于scala,haskell等函数式语言,不存在这样的事情。
173 | 因此我们通常使用一些函数式编程库帮我们简化成`getId('id', data)`的写法。
174 | 当然这并不以为着getId就不柯里化了。这只是一个语法糖而已。
175 |
176 | 一个简单的柯里化的js实现如下:
177 |
178 | ```js
179 | function curry(fn) {
180 | return function inner(...args) {
181 | if(args.length < fn.length) {
182 | return function(...innerArgs) {
183 | return inner.apply(this, args.concat(innerArgs))
184 | }
185 | }
186 | return fn.apply(this, args)
187 | }
188 | }
189 |
190 | ```
191 |
192 | ## 组合
193 | 通过柯里化我们将多个参数的函数变成只接受一个参数的高阶函数。
194 | 本节我们通过函数组合,将不同的函数组合形成各种不同的新的函数。
195 |
196 | 函数组合就是将函数串联起来执行,将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。
197 |
198 | 举个例子:
199 | ```js
200 | const add = x => y => x + y;
201 | const addOne = add(1);
202 | const plus = x => y => x * y;
203 | const plusFive = plus(5);
204 |
205 | const addOneAndPlusFive = compose(plusFive, addOne)
206 |
207 | addOneAndPlusFive(3); // 20
208 | ```
209 |
210 | 上面的例子很简单,就是将addOne 和 plusFive 这两个函数给组合起来,形成了一个新的函数而已。
211 | 类似的我们可以组合创造出无数的方法来。
212 |
213 | 如下是redux的compose的实现,可以帮助大家理解它是怎么运作的。
214 |
215 | ```js
216 | function compose(...funcs) {
217 | if (funcs.length === 0) {
218 | return arg => arg
219 | }
220 |
221 | if (funcs.length === 1) {
222 | return funcs[0]
223 | }
224 |
225 | return funcs.reduce((a, b) => (...args) => a(b(...args)))
226 | }
227 | ```
228 | 如上使用reduce方法将函数累计到一起,形成一个新的函数。
229 | ## Pointfree
230 | 终于到了文章的重点了。
231 |
232 | 其实前面已经提到了pointfree了。我们通过curry已经实现了pointfree风格。
233 | 但是这并不是全部。事实上要实现pointfree除了借助curry,还需要将可变化的数据放在最后(data goes last)。
234 |
235 | 前面的例子不足以解释这一点,我们举一个稍微复杂且贴近实际的例子:
236 |
237 | 如下是后端返回的数据格式
238 | ```json
239 | [{
240 | "user": "lucifer",
241 | "posts": [
242 | { "title": "fun fun function", "contents": "..." },
243 | { "title": "time slice", "contents": "..." }
244 | ]
245 | }, {
246 | "user": "lucifer",
247 | "posts": [
248 | { "title": "babel", "contents": "..." },
249 | { "title": "webpack", "contents": "..." }
250 | ]
251 | }, {
252 | "user": "karl",
253 | "posts": [
254 | { "title": "ramda", "contents": "..." },
255 | { "title": "lodash", "contents": "..." }
256 | ]
257 | }]
258 |
259 | ```
260 | 我们需要做的就是找到所有lucifer的文章,并将其title打印出来。
261 |
262 | 非pointfree的写法:
263 |
264 | ```js
265 | fetch(url)
266 | .then(JSON.parse)
267 | .then(datas => datas.filter(data => data.user === 'lucifer'))
268 | .then(datas => datas.map(data => data.posts).reduce((pre, next) => pre.concat(next)))
269 | .then(posts => posts.map(post => post.title))
270 |
271 | ```
272 | pointfree的写法:
273 |
274 | ```js
275 | // 这里为了演示,并没有将fetch这样的副作用函数进行包装
276 | fetch(url)
277 | .then(JSON.parse)
278 | .then(filter(compose(equals('lucifer'), get('user'))))
279 | .then(chain(get('posts')))
280 | .then(map(get('title')))
281 | ```
282 |
283 | 看到了嘛,整个过程我们没有提到data。 我们不需要提到data。
284 | 代码精简了很多,逻辑纯粹了很多。
285 |
286 | > 我们再也不必为变量命名而苦恼了
287 |
288 | 上面用到了一个api`chain`, 可能不太好理解,我会在函数式编程-进阶部分讲解。
289 | 现在你可以把它理解为将数组拍平,就好像我在非函数式写法中的那样。
290 |
291 | 另一个比较难以理解的地方在于`filter(compose(equals('lucifer'), get('user')))`
292 |
293 | 我们来看一下:
294 |
295 | ```js
296 | // 这样的写法更容易理解,但是它不pointfree
297 | filter(data => equals(get('user')(data), 'lucifer'))
298 |
299 | // 等价于下面的写法(交换律)
300 |
301 | filter(data => equals('lucifer', get('user')(data)))
302 |
303 | // 等价于下面的写法(curry)
304 | filter(data => equals('lucifer')(get('user')(data)))
305 |
306 | // 把equal('lucifer') 看成f, get('user') 看成g
307 | // 上面的代码本质上是f(g(x))
308 | // 因此等价于下面的写法(compose) f(g(x)) = compose(f, g)(X)
309 | filter(data => compose(equals('lucifer'), get('user'))(data))
310 |
311 | // 所有的形如 x => fn(x) 的代码都等价于 fn
312 | // 因此上面的代码等价于
313 | filter(compose(equals('lucifer'), get('user')))
314 | ```
315 | 通过curry, compose,data goes last一系列技巧,我们写出了pointfree的
316 | 代码。 pointfree风格是函数式编程中特别重要的概念。pointfree使得开发者写出的
317 | 代码更加容易重用,仅仅面向逻辑,而将具体的数据抽离出来。而且直观上来讲,代码
318 | 更加简洁。这还仅仅是一个小小的例子,现实中情况会复杂地多,其重要性不言而喻。
319 |
320 | ## 总结
321 | 本文从数学中的函数入手,讲述了函数式编程中的函数其实就是数学中的函数。
322 | 接着我们讲述了纯函数以及其优点。然后我们讲述了函数式编程中的两个基础概念,curry和compose。
323 | 最后阐述了pointfree的概念,并通过curry,compose,以及data goes last原则写出了一个pointfree风格的代码。
324 |
325 |
--------------------------------------------------------------------------------
/advance.md:
--------------------------------------------------------------------------------
1 | # 函数式编程-进阶
2 | 本文是函数式编程系列教程的第二篇。如果你对函数式编程不了解,可以看下之前的[函数式编程-基础篇](https://github.com/azl397985856/functional-programming/blob/master/beginer.md)。
3 | ## Hindley-Milner
4 | 开始讲接下面的内容之前,我们引入一个类型签名系统HM。它的功能非常强大,我们可以通过它知道函数做了什么,而不需要将代码读一遍。
5 | Hindley-Milner是一个经典的类型系统,他在函数式编程中的功能非常强大。
6 | 尽管你也可以在非函数式编程中使用,但是总感觉少了点什么。
7 | 类型签名不但可以用于编译时检测,而且还可以当作文档。
8 | HM是一个非常复杂的系统,如果大家想要学习的话,可以自己去查阅相关资料。
9 | 这里只是简单介绍下。如下是一个简单的例子:
10 |
11 | ```js
12 | // head :: [a] -> (a Null)
13 | function head(arr) {
14 | return arr && arr.length ? arr[0] : null;
15 | }
16 |
17 | head(["gone"]); // "gone"
18 |
19 | ```
20 |
21 | 可以看出HM由两部分组成, part one :: part two ,前面是函数的名字,后面是函数的签名。
22 | 那么head的签名意思就是接受一个任意类型的Array,返回一个该类型的元素或者Null.
23 |
24 |
25 | 我们来看一个更加复杂的例子:
26 | ```js
27 | // map :: (a -> b) -> [a] -> [b]
28 | var map = curry(function(f, xs){
29 | return xs.map(f);
30 | });
31 |
32 | ```
33 |
34 | 它表达的是map这个函数接受一个a到b的一个函数, 然后接受一个任意类型数组,最后返回另外一个任意类型的数组。
35 |
36 | 我们再来看下reduce的:
37 | ```js
38 | // reduce :: (b -> a -> b) -> b -> [a] -> b
39 | var reduce = curry(function(f, x, xs){
40 | return xs.reduce(f, x);
41 | });
42 |
43 | ```
44 |
45 | 上面表达的是reduce,接受一个b -> a -> b 这样的函数作为参数,然后成为了另外一个函数,
46 | 这个函数接受类型b为参数,然后返回一个函数,这个函数又接受[a]作为参数,最终返回b.
47 |
48 | > a b 虽然任意的类型,但是同一个类型签名中a类型和a类型一定是相同的,这点需要注意。
49 |
50 | 更多关于类型签名记号的信息,可以查看[类型签名记号](https://github.com/fantasyland/fantasy-land#type-signature-notation)
51 |
52 | 大家好好消化一下,再往后看哦~
53 |
54 | ## 再谈组合
55 |
56 | 前面我们介绍了函数组合。那么这部分我们进一步来讨论一下函数组合。
57 |
58 | 下面举一个原生JS的例子:
59 | ```js
60 | // 假设addOne和mutiFive已经在别的地方定义
61 | // 事实上,我们已经在上一节中写过这两个函数了。
62 | [1,2,3,4]
63 | .map(addOne) // [2,3,4,5]
64 | .map(multiFive)// [10, 15, 20, 25]
65 | ```
66 | 很好,我们似乎也实现了函数组合的效果。只不过我们的风格变成了链式调用。
67 |
68 | 上面的代码使用我们上一节介绍的知识,写出来应该是`compose(multiFive, addOne)`(正如我们上一节介绍的那样)。
69 |
70 | 但如果是这样的呢?
71 |
72 | ```js
73 | [1,2,3,4]
74 | .reduce(add) // 15
75 | .map(multiFive) // TypeError: xxx map is not a function
76 | ```
77 | 实际项目中,我们不只会用到数组,我们会用到很多其他类型。这个时候我们是否可以以这种map链式调用的方式实现函数组合呢?
78 |
79 | 我们是否可以使用一种通用的方式操作所有的数据类型呢?如果有了这种通用的操作方式,是否就可以实现上面的效果了呢?
80 |
81 | ### map
82 | 我们先来看下数组是如何实现链式调用的组合效果的。
83 |
84 | 数组能够通过map实现链式调用,本质上是因为其作为一个包装对象,可以通过map这样的一个高阶函数操作其内部的数据。
85 |
86 | 那么我们是否可以封装一个包装对象,使得所有数据类型都可以通过某种方式操作呢?
87 | 而这种操作,我们习惯称之为map。
88 |
89 |
90 | ### 容器
91 | 我们来实现一个Box
92 |
93 | ```js
94 | function Box(value) {
95 | this.__value = value;
96 | }
97 |
98 | Box.prototype.of = (value) => new Box(value);
99 |
100 | Box.prototype.map = function(fn) { return Box.of(fn(this.__value)) } // 箭头函数会丢失this到Box的指向
101 | ```
102 |
103 | JS中的Array也是一种BOX。 它同样提供了一种操作对象的方式map。
104 | 在函数式编程中,我们称这样的BOX为functor(函子)。
105 |
106 |
107 | 函数式编程中有很多functor, 比如option,future等,
108 | 这样我们操作的所有的数据就可以链式调用了(因为它们实现了相同的契约-map)。
109 | 实际上functor并不仅仅是盒子这么简单,它提供了一致的操作数据的方式,对外提供了简洁的api,对调用者隐藏了具体细节。
110 |
111 | 如果我们需要处理异步呢?如果我们需要处理错误(其中又会涉及到分支处理)呢?
112 |
113 | 答案就是借助刚才讲到的容器。
114 |
115 | 可以说函数式编程对异步操作是很简单的。通过前面讲到的compose,我们可以将多个函数像多米诺骨牌一样组合起来,然后按照一定的顺序执行,即使他们是异步的。
116 |
117 | > 我们需要将之前的compose做一些小改变以适应异步操作
118 |
119 | ### compose Promsie
120 | 回忆一下compose的写法:
121 |
122 | ```js
123 |
124 | function compose(...funcs) {
125 | if (funcs.length === 0) {
126 | return arg => arg
127 | }
128 |
129 | if (funcs.length === 1) {
130 | return funcs[0]
131 | }
132 |
133 | return funcs.reduce((a, b) => (...args) => a(b(...args)))
134 | }
135 | ```
136 |
137 | 以compose promise为例:
138 | ```js
139 | function composePromise(...funcs) {
140 | if (funcs.length === 0) {
141 | return arg => arg
142 | }
143 |
144 | if (funcs.length === 1) {
145 | return funcs[0]
146 | }
147 |
148 | return funcs.reduce((a, b) => (...args) => Promise.resolve(...args).then(b).then(a))
149 | }
150 |
151 | function setTimeoutPromise(number, delay) {
152 | return new Promise((resolve, reject) => {
153 | setTimeout(() => {
154 | console.log(number);
155 | resolve()
156 | }, delay)
157 | })
158 | }
159 |
160 | const setTimeoutPromiseWithOne = curry(setTimeoutPromise)(1)
161 | const setTimeoutPromiseWithTwo = curry(setTimeoutPromise)(2)
162 | composePromise(setTimeoutPromiseWithTwo, setTimeoutPromiseWithOne)(2000) // 2s先输出1,4s之后输出2
163 |
164 | ```
165 | 可以看到我们可以将异步操作给组合起来。
166 | 我们可以将函数柯里化,实现Promise.all的效果。
167 |
168 | 可以说函数式编程对异步操作是很友好的,只是需要做一些小小的”把戏“。
169 |
170 | 另外我们常常碰到的问题就是异常处理,异常处理怎么看都很难是纯函数,那么如何
171 | 以纯的方式处理异常呢? 异常处理必然要伴随着分支处理,函数式编程又是如何处理分支处理(像if/else)呢?毕竟这些在我们的日常开发中非常常见,我们接下来解释。
172 | 我们如何处理呢?
173 | ## 分支处理-Either容器
174 | 答案还是借助容器,先来看下函数式 如何优雅实现if/else,我们需要学习一个新的容器,
175 | 我们称之为Either.
176 |
177 |
178 | ```js
179 |
180 | // either做的事情就是在有value的时候将构造一个Right容器,
181 | // 否则构造一个Left容器,Left调用map操作数据的时候,不会发生任何事情
182 | // 我们可以通过定制化msg,定制一些诸如错误信息之类的
183 | var Either = function(msg, value) {
184 | if (value) return Right.of(value);
185 | return Left.of(msg)
186 | }
187 | var Left = function(x) {
188 | this.__value = x;
189 | }
190 |
191 | Left.of = function(x) {
192 | return new Left(x);
193 | }
194 |
195 | Left.prototype.map = function(f) {
196 | return this; // 什么都不做
197 | }
198 |
199 | var Right = function(x) {
200 | this.__value = x;
201 | }
202 |
203 | Right.of = function(x) {
204 | return new Right(x);
205 | }
206 |
207 | Right.prototype.map = function(f) {
208 | return Right.of(f(this.__value));
209 | }
210 |
211 | ```
212 |
213 | 我们可以用Either来实现一下上面的异常处理。
214 |
215 | ```js
216 | function readFile(filename) {
217 | return Either('filename shouldn't be empty!', filename)
218 | }
219 |
220 | readFile() // Left {__value: "filename shouldn't be empty!"}
221 | readFile('./test.txt') // Right {__value: "./test.txt"}
222 | ```
223 | 我们没有使用throw error,而是以一种更加温和更加纯粹的方式处理。
224 |
225 |
226 | ## 异步处理-Task容器
227 | 那么如何处理异步呢? 没错,我们再来引入一个容器,叫Task。
228 | Task的实现稍微复杂,在这里不再实现,大家可以自行查阅详细信息,如果你不想查,
229 | 那么你可以暂时把它看成Promise(两者相似,但也有不一样的地方)。
230 |
231 | 异步处理是不纯的,是有副作用的,我们可以直接通过HM看到哪些函数是不纯粹的。
232 |
233 | 正如Martin Odersky 在 [Google Groups Scala debate](https://groups.google.com/forum/#!topic/scala-debate/xYlUlQAnkmE%5B251-275%5D)
234 | 阐述的一样。
235 |
236 | > The IO monad does not make a function pure. It just makes it obvious that it’s impure
237 |
238 | 拿http获取用户信息为例:
239 |
240 | ```js
241 | // fetchUserInfo :: Number -> Task(Error, JSON)
242 | function fetchUserInfo(id) {
243 | return new Task(function(reject, result) {
244 | httpGet(`/user/${id}`, function(err, data) {
245 | err ? reject(err) : result(data);
246 | });
247 | });
248 | };
249 | // 假设data是{ name: 'lucifer', id: 1001, sex: 'male'}
250 | fetchUserInfo(1001).map(getName).map(toUpperCase); // LUCIFER
251 | ```
252 | 是不是觉得task和Promise很像,map就像then。
253 |
254 |
255 | 我们再来一个更加复杂的例子,
256 | 我们需要读取本地文件,然后根据文件读取的内容去请求另外一个服务的数据。以下例子摘自[mostly-adequate-guide](https://github.com/MostlyAdequate/mostly-adequate-guide)
257 |
258 | 我稍加了修改。
259 |
260 | ```js
261 | // upload :: String -> (String -> a) -> Void
262 | var upload = function(filename, callback) {
263 | if(!filename) {
264 | throw new Error("filename should't be empty!");
265 | } else {
266 | readFile(filename, function(err, data) {
267 | if(err) throw err;
268 | httpPost(data, function(err, result) {
269 | if(err) throw err;
270 | callback(result);
271 | });
272 | });
273 | }
274 | }
275 |
276 | ```
277 |
278 | 上面的代码如何用函数式编程重构一下,前提当然是readFile, httpPost也是函数式的写法喽。
279 |
280 |
281 | ```js
282 | // readFile :: Filename -> Either String (Future Error String)
283 | // httpPost :: String -> Future Error JSON
284 |
285 | // upload :: String -> Either String (Future Error JSON)
286 | var upload = compose(map(map(httpPost('/uploads'))), readFile);
287 |
288 | ```
289 |
290 | 从类型签名可以看出,我们预防了三个错误——readFile 使用 Either 来验证输入(或许还有确保文件名存在);readFile 在读取文件的时候可能会出错,错误通过 readFile 的 Future 表示;文件上传可能会因为各种各样的原因出错,错误通过 httpPost 的 Future 表示。我们就这么随意地使用 chain 实现了两个嵌套的、有序的异步执行动作。
291 | 所有这些操作都是在一个线性流中完成的,是完完全全纯的、声明式的代码,是可以等式推导(equational reasoning)并拥有可靠特性(reliable properties)的代码。我们没有被迫使用不必要甚至令人困惑的变量名,我们的 upload 函数符合通用接口而不是特定的一次性接口。
292 |
293 | 为了帮助大家理解,这里附一下函数式的readFile和httpPost的实现。
294 |
295 | 函数式实现:
296 | ```js
297 | // readFile :: Filename -> Either String (Future Error String)
298 | function readFile(filename) {
299 | return Either('filename shouldn\'t be empty!', new Task((resolve, reject) => {
300 | fs.readFile(filename, (err, data) => {
301 | if (err) return reject(err);
302 | resolve(data);
303 | }
304 | )}
305 | ))
306 | }
307 |
308 | function http(method, url, data) {
309 | return new Task((resolve, reject) => {
310 | http[method](url, {data}, (err, data) => {
311 | if (err) return reject(err);
312 | resolve(data);
313 | })
314 | })
315 | }
316 | // httpPost :: String -> Future Error JSON
317 | var httpPost = curry(http('post', '/path');
318 |
319 | ```
320 |
321 | 可以看出我们的代码重用性非常强。
322 | 类似的函数式编程中还有很多容器,
323 | 但是本质都是一样的,思想也是相通的,
324 | 大家可以自行查阅相关资料。
325 |
326 | ## 总结
327 | 这一节我们介绍了如何在函数式中使用容器处理分支逻辑,如何处理异步情况,以及如何加二者的嵌套结合起来。
328 | 对于不同的地方我们用到了不同的容器,函数式世界中有很多很多容器。
329 |
330 | 最后通过一个例子,让大家更直观地感受到函数式编程对逻辑的抽象,使得代码天生复用性,可读性都很强。
331 | 不过理解和熟练使用会比较困难, 还是那句话,重要的是改变思路,用函数式思维去编程。
332 |
333 | 下一节我们来介绍monad,applicative,以及一些类型推导,集合方面的知识。
334 | ## 参考
335 | [from-callback-to-future-functor-monad](https://hackernoon.com/from-callback-to-future-functor-monad-6c86d9c16cb5)
336 |
--------------------------------------------------------------------------------
/examples/news/db/news.json:
--------------------------------------------------------------------------------
1 | {
2 | "items": [
3 | {
4 | "title": "hello world1",
5 | "author": "lucifer",
6 | "cover":
7 | "http://www.ruanyifeng.com/blogimg/asset/201204/bg2012040601.png",
8 | "content": "新消息",
9 | "publishDate": "lucifer"
10 | },
11 | {
12 | "title": "hello world2",
13 | "author": "lucifer",
14 | "cover":
15 | "data:image/jpeg;base64,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",
16 | "content": "新消息",
17 | "publishDate": "lucifer"
18 | },
19 | {
20 | "title": "hello world3",
21 | "author": "lucifer",
22 | "cover":
23 | "data:image/jpeg;base64,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",
24 | "content": "新消息",
25 | "publishDate": "lucifer"
26 | },
27 | {
28 | "title": "hello world4",
29 | "author": "lucifer",
30 | "cover":
31 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQnJcMQAjjH--kvx_iPKxuNCwu0kA11xhJwDVY4eCMvxhEjqmOI",
32 | "content": "新消息",
33 | "publishDate": "lucifer"
34 | },
35 | {
36 | "title": "hello world5",
37 | "author": "lucifer",
38 | "cover":
39 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTBLMiFaF6bhidw9cKB8ujJU7d8ZnenAITFNuayci9pChqndsPbqg",
40 | "content": "新消息",
41 | "publishDate": "lucifer"
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------