├── .gitignore
├── LICENSE
├── README.md
├── lib
└── monoids.js
├── package.json
├── 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
└── monoids
│ ├── monoid_exercises.js
│ └── monoid_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
└── monoids
│ ├── monoid_exercises.js
│ └── monoid_exercises_spec.js
├── package.json
└── support.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | grunt
4 | less
5 | Gruntfile.js
6 | npm-debug.log
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 loop/recur
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | FP-JS Class
2 | ==================
3 |
4 | Functional Programming learned through JS
5 |
6 | Presentation here: http://goo.gl/zovcux
7 |
8 | **Installation**:
9 | `npm install`
10 |
11 | **Example of running tests**:
12 | Tests are all located in their cooresponding folder so you must cd in to run.
13 |
14 | ```
15 | cd exercises/curry
16 | mocha .
17 | ```
18 |
--------------------------------------------------------------------------------
/lib/monoids.js:
--------------------------------------------------------------------------------
1 | var getResult = function(x) { return x.val; };
2 |
3 | var Max = function(val) { return new _Max(val) }
4 |
5 | var _Max = function(val) {
6 | this.val = val;
7 | };
8 |
9 | _Max.prototype.empty = function() { return Max(Number.MIN_VALUE); };
10 |
11 | _Max.prototype.concat = function(s2) { return Max(this.val > s2.val ? this.val : s2.val); };
12 |
13 | _Max.prototype.inspect = function(f) {
14 | return 'Max('+inspect(this.val)+')';
15 | };
16 |
17 | var Min = function(val) { return new _Min(val) }
18 | var _Min = function(val) {
19 | this.val = val;
20 | };
21 |
22 |
23 | _Min.prototype.empty = function() { return Min(Number.MAX_VALUE); };
24 |
25 | _Min.prototype.concat = function(s2) { return Min(this.val < s2.val ? this.val : s2.val); };
26 |
27 | _Min.prototype.inspect = function(f) {
28 | return 'Min('+inspect(this.val)+')';
29 | };
30 |
31 | var Sum = function(val) { return new _Sum(val) }
32 | var _Sum = function(val) {
33 | this.val = val;
34 | };
35 |
36 | _Sum.prototype.empty = function() { return Sum(0); };
37 |
38 | _Sum.prototype.concat = function(s2) { return Sum(this.val + s2.val); };
39 |
40 | _Sum.prototype.inspect = function(f) {
41 | return 'Sum('+inspect(this.val)+')';
42 | };
43 |
44 |
45 | var Product = function(val) { return new _Product(val) }
46 | var _Product = function(val) {
47 | this.val = val;
48 | };
49 |
50 | _Product.prototype.empty = function() { return Product(1); };
51 |
52 | _Product.prototype.concat = function(s2) { return Product(this.val * s2.val); };
53 |
54 | _Product.prototype.inspect = function(f) {
55 | return 'Product('+inspect(this.val)+')';
56 | };
57 |
58 |
59 | var Any = function(val) { return new _Any(val) }
60 | var _Any = function(val) {
61 | this.val = val;
62 | };
63 |
64 | _Any.prototype.empty = function() { return Any(false); };
65 |
66 | _Any.prototype.concat = function(s2) { return Any(this.val || s2.val); };
67 |
68 | _Any.prototype.inspect = function(f) {
69 | return 'Any('+inspect(this.val)+')';
70 | };
71 |
72 | var All = function(val) { return new _All(val) }
73 | var _All = function(val) {
74 | this.val = val;
75 | };
76 |
77 | _All.prototype.empty = function() { return All(true); };
78 |
79 | _All.prototype.concat = function(s2) { return All(this.val && s2.val); };
80 |
81 | _All.prototype.inspect = function(f) {
82 | return 'All('+inspect(this.val)+')';
83 | };
84 |
85 |
86 | module.exports = {Max: Max, Min: Min, Sum: Sum, Product: Product, Any: Any, All: All, getResult: getResult}
87 |
--------------------------------------------------------------------------------
/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 | "accounting": "^0.4.1",
8 | "chai": "^1.9.1",
9 | "data.task": "^3.0.0",
10 | "ramda": "^0.13.0"
11 | },
12 | "devDependencies": {
13 | "mocha": "^1.17.1"
14 | },
15 | "scripts": {
16 | "test": "mocha *exercises/exercises/**/*_spec.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/DrBoolean/mostly-adequate-guide"
21 | },
22 | "author": "",
23 | "license": "ISC",
24 | "bugs": {
25 | "url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
26 | },
27 | "homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
28 | }
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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, _.toLower, _.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/part1_exercises/answers/curry/curry_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 |
4 |
5 | // Exercise 1
6 | //==============
7 |
8 | var words = _.split(' ');
9 |
10 | // Exercise 1a
11 | //==============
12 |
13 | var sentences = _.map(words);
14 |
15 |
16 | // Exercise 2
17 | //==============
18 |
19 | var filterQs = _.filter(_.match(/q/i));
20 |
21 |
22 | // Exercise 3
23 | //==============
24 | // Use the helper function _keepHighest to refactor max
25 |
26 | var _keepHighest = function(x,y){ return x >= y ? x : y; }; // <- leave be
27 |
28 | var max = _.reduce(_keepHighest, 0);
29 |
30 |
31 | // Bonus 1:
32 | // ============
33 | // wrap array's slice to be functional and curried.
34 | // //[1,2,3].slice(0, 2)
35 | var slice = _.curry(function(start, end, xs){ return xs.slice(start, end); });
36 |
37 |
38 | // Bonus 2:
39 | // ============
40 | // use slice to define a function "take" that takes n elements. make it's curried
41 | var take = slice(0);
42 |
43 |
44 | module.exports = { words: words,
45 | sentences: sentences,
46 | filterQs: filterQs,
47 | max: max,
48 | slice: slice,
49 | take: take
50 | };
51 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 built in slice to be functional and curried like fn's in ramda
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/part1_exercises/support.js:
--------------------------------------------------------------------------------
1 | var curry = require('ramda').curry;
2 |
3 | add = curry(function(x, y) {
4 | return x + y;
5 | });
6 |
7 | match = curry(function(what, x) {
8 | return x.match(what);
9 | });
10 |
11 | replace = curry(function(what, replacement, x) {
12 | return x.replace(what, replacement);
13 | });
14 |
15 | filter = curry(function(f, xs) {
16 | return xs.filter(f);
17 | });
18 |
19 | map = curry(function(f, xs) {
20 | return xs.map(f);
21 | });
22 |
23 | reduce = curry(function(f, a, xs) {
24 | return xs.reduce(f, a);
25 | });
26 |
27 | split = curry(function(what, x) {
28 | return x.split(what);
29 | });
30 |
31 | join = curry(function(what, x) {
32 | return x.join(what);
33 | });
34 |
35 | toUpperCase = function(x) {
36 | return x.toUpperCase()
37 | };
38 |
39 | toLowerCase = function(x) {
40 | return x.toLowerCase()
41 | };
42 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | // Make a future by running getPost() and getComments() using applicatives, then renders the page with both
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 | // setup...
44 | localStorage.player1 = "toby";
45 | localStorage.player2 = "sally";
46 |
47 | // Write a function that gets both player1 and player2 from the cache.
48 | var getCache = function(x) {
49 | return new IO(function () { return localStorage[x]; });
50 | }
51 | var game = _.curry(function (p1, p2) { return p1 + ' vs ' + p2; });
52 |
53 | var ex4 = liftA2(game, getCache('player1'), getCache('player2'));
54 |
55 |
56 |
57 |
58 |
59 | // TEST HELPERS
60 | // =====================
61 |
62 | function getPost(i) {
63 | return new Task(function (rej, res) {
64 | setTimeout(function () { res({ id: i, title: 'Love them futures' }); }, 300);
65 | });
66 | }
67 |
68 | function getComments(i) {
69 | return new Task(function (rej, res) {
70 | setTimeout(function () {
71 | res(["This class should be illegal", "Monads are like space burritos"]);
72 | }, 300);
73 | });
74 | }
75 |
76 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4}
77 |
--------------------------------------------------------------------------------
/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 futures
This class 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 |
--------------------------------------------------------------------------------
/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(_.add( "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 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7}
84 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/part2_exercises/answers/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 safetly 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 = _.compose(chain(safeProp('name')), chain(safeProp('street')), safeProp('address'));
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 = _.compose(chain(_.compose(pureLog, _.last, split('/'))), getFile);
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 = _.compose(chain(_.compose(getComments, _.prop('id'))), getPost);
64 |
65 |
66 | // Exercise 4
67 | // ==========
68 | // Use validateEmail and addToMailingList to implmeent ex4's type signature. It should
69 |
70 | // addToMailingList :: Email -> IO([Email])
71 | var addToMailingList = (function(list){
72 | return function(email) {
73 | return new IO(function(){
74 | list.push(email);
75 | return list;
76 | });
77 | }
78 | })([]);
79 |
80 | function emailBlast(list) {
81 | return new IO(function(){
82 | return 'emailed: ' + list.join(','); // for testing w/o mocks
83 | });
84 | }
85 |
86 | var validateEmail = function(x){
87 | return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
88 | };
89 |
90 | // ex4 :: Email -> Either String (IO String)
91 | var ex4 = _.compose(_.map(_.compose(chain(emailBlast), addToMailingList)), validateEmail);
92 |
93 |
94 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, user: user}
95 |
--------------------------------------------------------------------------------
/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(Maybe.of('Walnut St'), E.ex1(E.user));
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([13, 13], res.map(_.prop('post_id')));
19 | done();
20 | });
21 | });
22 |
23 | it('Exercise 4', function(){
24 | var getResult = either(_.identity, unsafePerformIO);
25 | assert.equal('invalid email', getResult(E.ex4('notanemail')));
26 | assert.equal('emailed: sleepy@grandpa.net', getResult(E.ex4('sleepy@grandpa.net')));
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/part2_exercises/answers/monoids/monoid_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Monoids = require('../../../lib/monoids');
3 | var _ = require('ramda');
4 |
5 | var Sum = Monoids.Sum;
6 | var Product = Monoids.Product;
7 | var Max = Monoids.Max;
8 | var Min = Monoids.Min;
9 | var Any = Monoids.Any;
10 | var All = Monoids.All;
11 | var getResult = Monoids.getResult;
12 |
13 |
14 |
15 |
16 | // Exercise 1
17 | // ==========
18 | // rewrite the ex1 function to use getResult() mconcat() and Sum() instead of sum()
19 |
20 | var sum = _.reduce(_.add, 0);
21 |
22 | var ex1 = _.compose(getResult, mconcat, _.map(Sum));
23 |
24 |
25 |
26 |
27 | // Exercise 2
28 | // ==========
29 | // Similar to the above, get the Product of the list.
30 |
31 | var ex2 = _.compose(getResult, mconcat, _.map(Product));
32 |
33 |
34 |
35 |
36 | // Exercise 3
37 | // ==========
38 | // Similar to the above, get the Max of the list.
39 | var ex3 = _.compose(getResult, mconcat, _.map(Max));
40 |
41 |
42 |
43 | // Exercise 4
44 | // ==========
45 | // use the function monoid instance to mconcat the functions below to create a full name string.
46 | var firstName = _.prop('first');
47 | var middleName = _.prop('middle');
48 | var lastName = _.prop('last');
49 | var space = function(){ return ' ' }
50 | var user = { first: "Bill", middle: "Jefferson", last: "Clinton" };
51 |
52 | var ex4 = mconcat([firstName, space, middleName, space, lastName]);
53 |
54 |
55 |
56 |
57 | // Bonus
58 | // ==========
59 | // For Tuple to be a monoid, its x,y must also be monoids. Monoids beget monoids.
60 | // Use this information to complete the definition of Tuple's concat fn.
61 |
62 | var _Tuple = function (x, y) {
63 | this.x = x;
64 | this.y = y;
65 | };
66 | var Tuple = _.curry(function (x, y) { return new _Tuple(x, y); });
67 |
68 | _Tuple.prototype.inspect = function () {
69 | return 'Tuple(' + inspect(this.x) + ' ' + inspect(this.y) + ')';
70 | };
71 |
72 | _Tuple.prototype.empty = function () {
73 | return Tuple(this.x.empty(), this.y.empty());
74 | };
75 |
76 | _Tuple.prototype.concat = function (t2) {
77 | return Tuple(this.x.concat(t2.x), this.y.concat(t2.y));
78 | };
79 |
80 | var bonus = function() {
81 | return mconcat([Tuple("abc", [1, 2, 3]), Tuple("def", [4, 5, 6])]);
82 | }
83 |
84 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, bonus: bonus, user: user, Tuple: Tuple}
85 |
--------------------------------------------------------------------------------
/part2_exercises/answers/monoids/monoid_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./monoid_exercises');
3 | var _ = require('ramda');
4 | var assert = require("chai").assert
5 |
6 | describe("Monoid Answers", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.equal(6, E.ex1([1, 2, 3]));
10 | });
11 |
12 | it('Exercise 2', function(){
13 | assert.equal(12, E.ex2([2, 2, 3]));
14 | });
15 |
16 | it('Exercise 3', function(){
17 | assert.equal(32, E.ex3([12, 32, 3]));
18 | });
19 |
20 | it('Exercise 4', function(){
21 | assert.equal("Bill Jefferson Clinton", E.ex4(E.user));
22 | });
23 |
24 | it('Bonus', function(){
25 | assert.deepEqual(E.Tuple("abcdef", [1, 2, 3, 4, 5, 6]), E.bonus());
26 | });
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/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 | var ex1 = function(x, y) {
15 | // write me
16 | };
17 |
18 |
19 | // Exercise 2
20 | // ==========
21 | // Rewrite 1 to use liftA2 instead of ap()
22 |
23 | var ex2 = undefined;
24 |
25 |
26 |
27 | // Exercise 3
28 | // ==========
29 | // Make a future by running getPost() and getComments() using applicatives, then renders the page with both
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 = undefined;
35 |
36 |
37 |
38 | // Exercise 4
39 | // ==========
40 | // setup...
41 | localStorage.player1 = "toby";
42 | localStorage.player2 = "sally";
43 |
44 | // Write a function that gets both player1 and player2 from the cache.
45 | var getCache = function(x) {
46 | return new IO(function () { return localStorage[x]; });
47 | }
48 | var game = _.curry(function (p1, p2) { return p1 + ' vs ' + p2; });
49 |
50 | var ex4 = undefined;
51 |
52 |
53 |
54 |
55 |
56 | // TEST HELPERS
57 | // =====================
58 |
59 | function getPost(i) {
60 | return new Task(function (rej, res) {
61 | setTimeout(function () { res({ id: i, title: 'Love them futures' }); }, 300);
62 | });
63 | }
64 |
65 | function getComments(i) {
66 | return new Task(function (rej, res) {
67 | setTimeout(function () {
68 | res(["This class should be illegal", "Monads are like space burritos"]);
69 | }, 300);
70 | });
71 | }
72 |
73 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4}
74 |
--------------------------------------------------------------------------------
/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 class 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 |
--------------------------------------------------------------------------------
/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 | // 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 = undefined;
41 |
42 |
43 |
44 | // Exercise 5
45 | // ==========
46 | // Write a function that will getPost then toUpperCase the post's title
47 |
48 | // getPost :: Int -> Future({id: Int, title: String})
49 | var getPost = function (i) {
50 | return new Task(function(rej, res) {
51 | setTimeout(function(){
52 | res({id: i, title: 'Love them futures'})
53 | }, 300)
54 | });
55 | };
56 |
57 | var ex5 = undefined;
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(_.add( "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 = undefined;
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 undefined; // <--- write me. (don't be pointfree)
81 | };
82 |
83 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7};
84 |
--------------------------------------------------------------------------------
/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(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 |
--------------------------------------------------------------------------------
/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 safetly 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 and addToMailingList to implmeent ex4's type signature. It should
69 |
70 | // addToMailingList :: Email -> IO([Email])
71 | var addToMailingList = (function(list){
72 | return function(email) {
73 | return new IO(function(){
74 | list.push(email);
75 | return list;
76 | });
77 | }
78 | })([]);
79 |
80 | function emailBlast(list) {
81 | return new IO(function(){
82 | return 'emailed: ' + list.join(','); // for testing w/o mocks
83 | });
84 | }
85 |
86 | var validateEmail = function(x){
87 | return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
88 | }
89 |
90 | // ex4 :: Email -> Either String (IO String)
91 | var ex4 = undefined;
92 |
93 |
94 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, user: user}
95 |
--------------------------------------------------------------------------------
/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(Maybe.of('Walnut St'), E.ex1(E.user));
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([13, 13], res.map(_.prop('post_id')));
19 | done();
20 | });
21 | });
22 |
23 | it('Exercise 4', function(){
24 | var getResult = either(_.identity, unsafePerformIO);
25 | assert.equal('invalid email', getResult(E.ex4('notanemail')));
26 | assert.equal('emailed: sleepy@grandpa.net', getResult(E.ex4('sleepy@grandpa.net')));
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/part2_exercises/exercises/monoids/monoid_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Monoids = require('../../../lib/monoids');
3 | var _ = require('ramda');
4 |
5 | var Sum = Monoids.Sum;
6 | var Product = Monoids.Product;
7 | var Max = Monoids.Max;
8 | var Min = Monoids.Min;
9 | var Any = Monoids.Any;
10 | var All = Monoids.All;
11 | var getResult = Monoids.getResult;
12 |
13 |
14 |
15 |
16 | // Exercise 1
17 | // ==========
18 | // rewrite the ex1 function to use getResult() mconcat() and Sum() instead of sum()
19 |
20 | var sum = _.reduce(_.add, 0);
21 |
22 | var ex1 = undefined;
23 |
24 |
25 |
26 |
27 | // Exercise 2
28 | // ==========
29 | // Similar to the above, get the Product of the list.
30 |
31 | var ex2 = undefined;
32 |
33 |
34 |
35 |
36 | // Exercise 3
37 | // ==========
38 | // Similar to the above, get the Max of the list.
39 | var ex3 = undefined;
40 |
41 |
42 |
43 | // Exercise 4
44 | // ==========
45 | // use the function monoid instance to mconcat the functions below to create a full name string.
46 | var firstName = _.prop('first');
47 | var middleName = _.prop('middle');
48 | var lastName = _.prop('last');
49 | var space = function(){ return ' ' }
50 | var user = { first: "Bill", middle: "Jefferson", last: "Clinton" };
51 |
52 | var ex4 = undefined;
53 |
54 |
55 |
56 |
57 | // Bonus
58 | // ==========
59 | // For Tuple to be a monoid, its x,y must also be monoids. Monoids beget monoids.
60 | // Use this information to complete the definition of Tuple's concat fn.
61 |
62 | var _Tuple = function (x, y) {
63 | this.x = x;
64 | this.y = y;
65 | };
66 | var Tuple = _.curry(function (x, y) { return new _Tuple(x, y); });
67 |
68 | _Tuple.prototype.inspect = function () {
69 | return 'Tuple(' + inspect(this.x) + ' ' + inspect(this.y) + ')';
70 | };
71 |
72 | _Tuple.prototype.empty = function () {
73 | //write me
74 | };
75 |
76 | _Tuple.prototype.concat = function (t2) {
77 | //write me
78 | };
79 |
80 | var bonus = function() {
81 | return mconcat([Tuple("abc", [1, 2, 3]), Tuple("def", [4, 5, 6])]);
82 | }
83 |
84 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, bonus: bonus, user: user, Tuple: Tuple}
85 |
--------------------------------------------------------------------------------
/part2_exercises/exercises/monoids/monoid_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./monoid_exercises');
3 | var _ = require('ramda');
4 | var assert = require("chai").assert
5 |
6 | describe("Monoid Answers", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.equal(6, E.ex1([1, 2, 3]));
10 | });
11 |
12 | it('Exercise 2', function(){
13 | assert.equal(12, E.ex2([2, 2, 3]));
14 | });
15 |
16 | it('Exercise 3', function(){
17 | assert.equal(32, E.ex3([12, 32, 3]));
18 | });
19 |
20 | it('Exercise 4', function(){
21 | assert.equal("Bill Jefferson Clinton", E.ex4(E.user));
22 | });
23 |
24 | it('Bonus', function(){
25 | assert.deepEqual(E.Tuple("abcdef", [1, 2, 3, 4, 5, 6]), E.bonus());
26 | });
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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() {
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.__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
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.chain = function(f) {
101 | return this.map(f).join();
102 | };
103 |
104 | Right.prototype.ap = function(other) {
105 | return this.chain(function(f) {
106 | return other.map(f);
107 | });
108 | };
109 |
110 | Right.prototype.join = function() {
111 | return this.__value;
112 | }
113 |
114 | Right.prototype.chain = function(f) {
115 | return f(this.__value);
116 | }
117 |
118 | Right.prototype.inspect = function() {
119 | return 'Right('+inspect(this.__value)+')';
120 | }
121 |
122 | // IO
123 | IO = function(f) {
124 | this.unsafePerformIO = f;
125 | }
126 |
127 | IO.of = function(x) {
128 | return new IO(function() {
129 | return x;
130 | });
131 | }
132 |
133 | IO.prototype.map = function(f) {
134 | return new IO(_.compose(f, this.unsafePerformIO));
135 | }
136 |
137 | IO.prototype.chain = function(f) {
138 | return this.map(f).join();
139 | };
140 |
141 | IO.prototype.ap = function(a) {
142 | return this.chain(function(f) {
143 | return a.map(f);
144 | });
145 | };
146 |
147 | IO.prototype.join = function() {
148 | return this.unsafePerformIO();
149 | }
150 |
151 | IO.prototype.inspect = function() {
152 | return 'IO('+inspect(this.__value)+')';
153 | }
154 |
155 | unsafePerformIO = function(x) { return x.unsafePerformIO(); }
156 |
157 | Task.prototype.join = function(){ return this.chain(_.identity); }
158 |
159 |
160 | either = curry(function(f, g, e) {
161 | switch(e.constructor) {
162 | case Left: return f(e.__value);
163 | case Right: return g(e.__value);
164 | }
165 | });
166 |
167 | // overwriting join from pt 1
168 | join = function(m){ return m.join(); };
169 |
170 | chain = curry(function(f, m){
171 | return m.map(f).join(); // or compose(join, map(f))(m)
172 | });
173 |
174 | liftA2 = curry(function(f, a1, a2){
175 | return a1.map(f).ap(a2);
176 | });
177 |
178 | liftA3 = curry(function(f, a1, a2, a3){
179 | return a1.map(f).ap(a2).ap(a3);
180 | });
181 |
182 | concat = curry(function(x, y) {
183 | return x.concat(y);
184 | });
185 |
186 | mconcat = function(xs) {
187 | if(!xs[0]) return xs;
188 | return xs.reduce(concat, xs[0].empty());
189 | };
190 |
191 |
192 | // Enhance, enhance.
193 | (function() {
194 | var _K = function(x) { return function(y) { return x; } };
195 |
196 | var _fmap = function(g) {
197 | var f = this;
198 | return function(x) { return g(f(x)) };
199 | };
200 |
201 | Object.defineProperty(Function.prototype, 'map',{
202 | value: _fmap,
203 | writable: true,
204 | configurable: true,
205 | enumerable: false
206 | });
207 |
208 | var _concat = function(g) {
209 | var f = this;
210 | return function() {
211 | return f.apply(this, arguments).concat(g.apply(this, arguments))
212 | }
213 | };
214 |
215 | Object.defineProperty(Function.prototype, 'concat',{
216 | value: _concat,
217 | writable: true,
218 | configurable: true,
219 | enumerable: false
220 | });
221 |
222 | var _empty = function() {
223 | return _K({ concat: function(g) { return g.empty().concat(g); } });
224 | };
225 |
226 | Object.defineProperty(Function.prototype, 'empty',{
227 | value: _empty,
228 | writable: true,
229 | configurable: true,
230 | enumerable: false
231 | });
232 |
233 | Object.defineProperty(Function.prototype, 'of',{
234 | value: _K,
235 | writable: true,
236 | configurable: true,
237 | enumerable: false
238 | });
239 |
240 | var _ap = function(g) {
241 | var f = this;
242 | return function(x) {
243 | return f(x)(g(x));
244 | }
245 | };
246 |
247 | Object.defineProperty(Function.prototype, 'ap',{
248 | value: _ap,
249 | writable: true,
250 | configurable: true,
251 | enumerable: false
252 | });
253 |
254 |
255 | //empty String
256 | Object.defineProperty(String.prototype, 'empty',{
257 | value: function(){ return '' },
258 | writable: true,
259 | configurable: true,
260 | enumerable: false
261 | });
262 |
263 | //empty Array
264 | Object.defineProperty(Array.prototype, 'empty',{
265 | value: function(){ return [] },
266 | writable: true,
267 | configurable: true,
268 | enumerable: false
269 | });
270 | })();
271 |
272 |
--------------------------------------------------------------------------------