《函数式编程指北》练习题速通指南
孙泽辉

最近很闲,把这本书读完了,练习题做题经验分享一下。

curry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
require("../../support");
var _ = require("ramda");

// 练习 1
//==============
// 通过局部调用(partial apply)移除所有参数

var _words = function (str) {
return split(" ", str);
};
// 使用 curry 延迟调用
const words = _.curry(split(" "));

// 练习 1a
//==============
// 使用 `map` 创建一个新的 `words` 函数,使之能够操作字符串数组

// 直接包裹map提前传入处理函数
var sentences = _.map(words);

// 练习 2
//==============
// 通过局部调用(partial apply)移除所有参数

var _filterQs = function (xs) {
return filter(function (x) {
return match(/q/i, x);
}, xs);
};

// 自带 curry 直接用
const filterQs = _.filter(_.match(/q/i));

// 练习 3
//==============
// 使用帮助函数 `_keepHighest` 重构 `max` 使之成为 curry 函数

// 无须改动:
var _keepHighest = function (x, y) {
return x >= y ? x : y;
};

// 重构这段代码:
var _max = function (xs) {
return reduce(
function (acc, x) {
return _keepHighest(acc, x);
},
-Infinity,
xs
);
};

// 把参数拎出来即可
const max = _.reduce(_keepHighest, -Infinity);

// 彩蛋 1:
// ============
// 包裹数组的 `slice` 函数使之成为 curry 函数
// //[1,2,3].slice(0, 2)

// slice(0)(2)([1,2,3])
// 感觉翻译有误:使用curry包裹数组的slice函数
var slice = _.curry(function (x, y, arr) {
return arr.slice(x, y);
});

// 彩蛋 2:
// ============
// 借助 `slice` 定义一个 `take` curry 函数,该函数调用后可以取出字符串的前 n 个字符。
// E.take(2)(['a', 'b', 'c']), ['a', 'b']
var take = _.curry(function (x, y) {
return y.slice(0, x);
});

module.exports = {
words: words,
sentences: sentences,
filterQs: filterQs,
max: max,
slice: slice,
take: take,
};

compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require("../../support");
var _ = require("ramda");
var accounting = require("accounting");

// 示例数据
var CARS = [
{ name: "Ferrari FF", horsepower: 660, dollar_value: 700000, in_stock: true },
{
name: "Spyker C12 Zagato",
horsepower: 650,
dollar_value: 648000,
in_stock: false,
},
{
name: "Jaguar XKR-S",
horsepower: 550,
dollar_value: 132000,
in_stock: false,
},
{ name: "Audi R8", horsepower: 525, dollar_value: 114200, in_stock: false },
{
name: "Aston Martin One-77",
horsepower: 750,
dollar_value: 1850000,
in_stock: true,
},
{
name: "Pagani Huayra",
horsepower: 700,
dollar_value: 1300000,
in_stock: false,
},
];

// 练习 1:
// ============
// 使用 _.compose() 重写下面这个函数。提示:_.prop() 是 curry 函数
var _isLastInStock = function (cars) {
var last_car = _.last(cars);
return _.prop("in_stock", last_car);
};
// 两个函数有递进关系,所以compose
const isLastInStock = _.compose(_.prop("in_stock"), _.last);

// 练习 2:
// ============
// 使用 _.compose()、_.prop() 和 _.head() 获取第一个 car 的 name

// 有交换律随便顺序
var nameOfFirstCar = _.compose(_.prop("name"), _.head);

// 练习 3:
// ============
// 使用帮助函数 _average 重构 averageDollarValue 使之成为一个组合
var _average = function (xs) {
return reduce(add, 0, xs) / xs.length;
}; // <- 无须改动

var _averageDollarValue = function (cars) {
var dollar_values = map(function (c) {
return c.dollar_value;
}, cars);
return _average(dollar_values);
};
// 对map的调用者延迟执行,取property可以用prop,最后把返回值赋给_average即可
const averageDollarValue = _.compose(_average, map(_.prop("dollar_value")));

// 练习 4:
// ============
// 使用 compose 写一个 sanitizeNames() 函数,返回一个下划线连接的小写字符串:例如:sanitizeNames(["Hello World"]) //=> ["hello_world"]。

var _underscore = replace(/\W+/g, "_"); //<-- 无须改动,并在 sanitizeNames 中使用它
// 坑!上面注释和单测传参不一样,参数是CARS!
// 因为传入的是数组,所以取出所有name然后对所有字符串tolower,replace
var sanitizeNames = _.compose(
_.map(_underscore),
_.map(_.toLower),
_.map(_.prop("name"))
);
// 根据交换律,可以写成
var sanitizeNames2 = _.map(_.compose(_.toLower, _underscore, _.prop("name")));

// 彩蛋 1:
// ============
// 使用 compose 重构 availablePrices

var _availablePrices = function (cars) {
var available_cars = _.filter(_.prop("in_stock"), cars);
return available_cars
.map(function (x) {
return accounting.formatMoney(x.dollar_value);
})
.join(", ");
};
// compose 从后往前写,里面map要两步操作,用compose取出prop喂给accounting
var availablePrices = _.compose(
_.join(", "),
_.map(_.compose(accounting.formatMoney, _.prop("dollar_value"))),
_.filter(_.prop("in_stock"))
);
// 彩蛋 2:
// ============
// 重构使之成为 pointfree 函数。提示:可以使用 _.flip()

var _fastestCar = function (cars) {
var sorted = _.sortBy(function (car) {
return car.horsepower;
}, cars);
var fastest = _.last(sorted);
return fastest.name + " is the fastest";
};
// 按顺序写到字符串拼接时
// flip交换函数参数位置,使name先提取出来再拼接
var fastestCar = _.compose(
_.flip(_.concat)(" is the fastest"),
_.prop("name"),
_.last,
_.sortBy(_.prop("horsepower"))
);

module.exports = {
CARS: CARS,
isLastInStock: isLastInStock,
nameOfFirstCar: nameOfFirstCar,
fastestCar: fastestCar,
averageDollarValue: averageDollarValue,
availablePrices: availablePrices,
sanitizeNames: sanitizeNames,
};

functors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
require("../../support");
var Task = require("data.task");
var _ = require("ramda");

// 练习 1
// ==========
// 使用 _.add(x,y) 和 _.map(f,x) 创建一个能让 functor 里的值增加的函数

// 这里需要看一下 Identity 的 map,
// 代码说的清楚
// Identity.prototype.map = function(f) {
// return Identity.of(f(this.__value));
// };
// add = function (x) {
// return function (y) {
// return x + y;
// };
// };
// 单测:
// assert.deepEqual(E.ex1(Identity.of(2)), Identity.of(3));
// 执行顺序 :
// f = map(function(1,y){ return 1+y })
// return Indentity.of( f( 2 ) )
var ex1 = _.map(_.add(1));
//练习 2
// ==========
// 使用 _.head 获取列表的第一个元素
var xs = Identity.of(["do", "ray", "me", "fa", "so", "la", "ti", "do"]);
// 让Identity执行head
var ex2 = _.map(_.head);

// 练习 3
// ==========
// 使用 safeProp 和 _.head 找到 user 的名字的首字母
var safeProp = _.curry(function (x, o) {
return Maybe.of(o[x]);
});

var user = { id: 2, name: "Albert" };
// 取到name之后执行head
var ex3 = _.compose(_.map(_.head), safeProp("name"));
// 练习 4
// ==========
// 使用 Maybe 重写 ex4,不要有 if 语句

var _ex4 = function (n) {
if (n) {
return parseInt(n);
}
};
// 直接写
var ex4 = function (n) {
return Maybe.of(parseInt(n));
};
// 也可以freepoint
var _ex4 = _.compose(Maybe.of, parseInt);

// 练习 5
// ==========
// 写一个函数,先 getPost 获取一篇文章,然后 toUpperCase 让这片文章标题变为大写

// getPost :: Int -> Future({id: Int, title: String})
var getPost = function (i) {
return new Task(function (rej, res) {
setTimeout(function () {
res({ id: i, title: "Love them futures" });
}, 300);
});
};
// task可以直接写处理函数,无副作用
var ex5 = _.compose(_.map(_.compose(_.toUpper, _.prop("title"))), getPost);

// 练习 6
// ==========
// 写一个函数,使用 checkActive() 和 showWelcome() 分别允许访问或返回错误

var showWelcome = _.compose(_.add("Welcome "), _.prop("name"));

var checkActive = function (user) {
return user.active ? Right.of(user) : Left.of("Your account is not active");
};

var ex6 = _.compose(_.map(showWelcome), checkActive);

// 练习 7
// ==========
// 写一个验证函数,检查参数是否 length > 3。如果是就返回 Right(x),否则就返回
// Left("You need > 3")

var ex7 = function (x) {
return x.length > 3 ? Right.of(x) : Left.of("You need > 3"); // <--- write me. (don't be pointfree)
};

// 练习 8
// ==========
// 使用练习 7 的 ex7 和 Either 构造一个 functor,如果一个 user 合法就保存它,否则
// 返回错误消息。别忘了 either 的两个参数必须返回同一类型的数据。

var save = function (x) {
return new IO(function () {
console.log("SAVED USER!");
return x + "-saved";
});
};
// either 接受三个参数,
// 判断第三个参数是left/right,执行左右(第一,二)两个函数
// (第一个函数要用IO的构造包裹一下)
var ex8 = _.compose(either(IO.of, save), ex7);

module.exports = {
ex1: ex1,
ex2: ex2,
ex3: ex3,
ex4: ex4,
ex5: ex5,
ex6: ex6,
ex7: ex7,
ex8: ex8,
};

monad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
require("../../support");
var Task = require("data.task");
var _ = require("ramda");
// 练习 1
// ==========
// 给定一个 user,使用 safeProp 和 map/join 或 chain 安全地获取 sreet 的 name

var safeProp = _.curry(function (x, o) {
return Maybe.of(o[x]);
});
var user = {
id: 2,
name: "albert",
address: {
street: {
number: 22,
name: "Walnut St",
},
},
};
// 每次对prop操作都会生成一层Maybe,用chain取出
var ex1 = _.compose(
_.chain(safeProp("name")),
_.chain(safeProp("street")),
safeProp("address")
);
// 或
var ex1 = _.compose(
join,
_.map(safeProp("name")),
join,
_.map(safeProp("street")),
safeProp("address")
);

// 练习 2
// ==========
// 使用 getFile 获取文件名并删除目录,所以返回值仅仅是文件,然后以纯的方式打印文件

var getFile = function () {
return new IO(function () {
return __filename;
});
};

var pureLog = function (x) {
return new IO(function () {
console.log(x);
return "logged " + x;
});
};
// getFile 会包一层IO,使用chain取出后喂给prueLog
var ex2 = _.compose(_.chain(pureLog), getFile);

// 练习 3
// ==========
// 使用 getPost() 然后以 post 的 id 调用 getComments()
var getPost = function (i) {
return new Task(function (rej, res) {
setTimeout(function () {
res({ id: i, title: "Love them tasks" });
}, 300);
});
};

var getComments = function (i) {
return new Task(function (rej, res) {
setTimeout(function () {
res([
{ post_id: i, body: "This book should be illegal" },
{ post_id: i, body: "Monads are like smelly shallots" },
]);
}, 300);
});
};
// getPost 返回task 使用chain取出(至于为什么可以取出,看下面源码)
const getCommentsById = _.compose(getComments, _.prop("id"));
var ex3 = _.compose(_.chain(getCommentsById), getPost);

// -- Chain ------------------------------------------------------------

/**
* Transforms the succesful value of the `Task[α, β]` using a function to a
* monad.
*
* @summary @Task[α, β] => (β → Task[α, γ]) → Task[α, γ]
*/
Task.prototype.chain = function _chain(f) {
var fork = this.fork;
var cleanup = this.cleanup;

return new Task(function (reject, resolve) {
return fork(
function (a) {
return reject(a);
},
function (b) {
// resolve之前把传入的函数包裹一下再fork出去
// f必须是实现了fork方法
return f(b).fork(reject, resolve);
}
);
}, cleanup);
};

// 练习 4
// ==========
// 用 validateEmail、addToMailingList 和 emailBlast 实现 ex4 的类型签名

// addToMailingList :: Email -> IO([Email])
var addToMailingList = (function (list) {
return function (email) {
return new IO(function () {
list.push(email);
return list;
});
};
})([]);

function emailBlast(list) {
return new IO(function () {
return "emailed: " + list.join(",");
});
}

var validateEmail = function (x) {
return x.match(/\S+@\S+\.\S+/) ? new Right(x) : new Left("invalid email");
};

// ex4 :: Email -> Either String (IO String)
// emailBlast,addToMailingList 两个有递进关系 compose组合一下
// chain 取出IO.value,map将validateEmail返回值执行f
// Left不会被处理
var ex4 = _.compose(
_.map(_.compose(_.chain(emailBlast), addToMailingList)),
validateEmail
);
module.exports = { ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, user: user };

applicative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
require("../../support");
var Task = require("data.task");
var _ = require("ramda");

// 模拟浏览器的 localStorage 对象
var localStorage = {};

// 练习 1
// ==========
// 写一个函数,使用 Maybe 和 ap() 实现让两个可能是 null 的数值相加。
// 瓶子放add函数,然后两个值相加
// ex1 :: Number -> Number -> Maybe Number
var ex1 = function (x, y) {
return Maybe.of(add).ap(Maybe.of(x)).ap(Maybe.of(y));
};

// 练习 2
// ==========
// 写一个函数,接收两个 Maybe 为参数,让它们相加。使用 liftA2 代替 ap()。
// 自带curry
// ex2 :: Maybe Number -> Maybe Number -> Maybe Number
var ex2 = liftA2(add);

// 练习 3
// ==========
// 运行 getPost(n) 和 getComments(n),两者都运行完毕后执行渲染页面的操作。(参数 n 可以是任意值)。

var makeComments = _.reduce(function (acc, c) {
return acc + "<li>" + c + "</li>";
}, "");
var render = _.curry(function (p, cs) {
return "<div>" + p.title + "</div>" + makeComments(cs);
});
// liftA2:
// var liftA2 = curry(function(f, functor1, functor2) {
// return functor1.map(f).ap(functor2);
// });
// ap:
// Container.prototype.ap = function(other_container) {
// return other_container.map(this.__value);
// }
// ex3 :: Task Error HTML
var ex3 = liftA2(render, getPost(2), getComments(2));

// 练习 4
// ==========
// 写一个 IO,从缓存中读取 player1 和 player2,然后开始游戏。

localStorage.player1 = "toby";
localStorage.player2 = "sally";

var getCache = function (x) {
return new IO(function () {
return localStorage[x];
});
};
var game = _.curry(function (p1, p2) {
return p1 + " vs " + p2;
});

// ex4 :: IO String
var ex4 = liftA2(game, getCache("player1"), getCache("player2"));

// 帮助函数
// =====================

function getPost(i) {
return new Task(function (rej, res) {
setTimeout(function () {
res({ id: i, title: "Love them futures" });
}, 300);
});
}

function getComments(i) {
return new Task(function (rej, res) {
setTimeout(function () {
res(["This book should be illegal", "Monads are like space burritos"]);
}, 300);
});
}
module.exports = { ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4 };

练习 3 有必要说一下,liftA2是为了以一种 pointfree 的方式调用 applicative functor,

1
2
3
4
5
6
var liftA2 = curry(function(f, functor1, functor2) {
return functor1.map(f).ap(functor2);
});

liftA2(add, Maybe.of(2), Maybe.of(3));
// Maybe(5)

上面Maybe(2)先map(add),此时functor1.map(f)是一个Maybe(add(2))的functor科里函数,继续ap,把一个 functor 的函数值应用到另一个 functor 的值上

1
2
3
Container.prototype.ap = function(other_container) {
return other_container.map(this.__value);
}

记住,this.__value 是一个函数,将会接收另一个 functor 作为参数,所以我们只需 map 它。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
This site is deployed on
Total words 89.4k