├── .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 illegal
  • Monads 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 illegal
  • Monads 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 | --------------------------------------------------------------------------------