├── exercises
├── ch04
│ ├── solution_a.js
│ ├── solution_b.js
│ ├── solution_c.js
│ ├── exercise_a.js
│ ├── exercise_b.js
│ ├── validation_a.js
│ ├── exercise_c.js
│ ├── validation_c.js
│ └── validation_b.js
├── ch08
│ ├── solution_a.js
│ ├── solution_b.js
│ ├── solution_c.js
│ ├── exercise_a.js
│ ├── exercise_b.js
│ ├── solution_d.js
│ ├── validation_a.js
│ ├── validation_b.js
│ ├── exercise_c.js
│ ├── validation_c.js
│ ├── exercise_d.js
│ └── validation_d.js
├── ch10
│ ├── solution_b.js
│ ├── solution_a.js
│ ├── solution_c.js
│ ├── exercise_b.js
│ ├── exercise_a.js
│ ├── validation_c.js
│ ├── exercise_c.js
│ ├── validation_a.js
│ └── validation_b.js
├── ch05
│ ├── solution_a.js
│ ├── solution_b.js
│ ├── solution_c.js
│ ├── exercise_c.js
│ ├── exercise_b.js
│ ├── exercise_a.js
│ ├── validation_c.js
│ ├── validation_b.js
│ └── validation_a.js
├── ch11
│ ├── solution_a.js
│ ├── solution_c.js
│ ├── solution_b.js
│ ├── exercise_a.js
│ ├── validation_c.js
│ ├── exercise_b.js
│ ├── exercise_c.js
│ ├── validation_a.js
│ └── validation_b.js
├── ch12
│ ├── solution_a.js
│ ├── solution_b.js
│ ├── solution_c.js
│ ├── exercise_c.js
│ ├── exercise_b.js
│ ├── exercise_a.js
│ ├── validation_c.js
│ ├── validation_a.js
│ └── validation_b.js
├── ch09
│ ├── solution_c.js
│ ├── solution_b.js
│ ├── solution_a.js
│ ├── exercise_a.js
│ ├── exercise_c.js
│ ├── validation_b.js
│ ├── exercise_b.js
│ ├── validation_c.js
│ └── validation_a.js
├── test
│ ├── ch04.js
│ ├── ch05.js
│ ├── ch08.js
│ ├── ch09.js
│ ├── ch10.js
│ ├── ch11.js
│ ├── ch12.js
│ ├── ch04.solutions.js
│ ├── ch05.solutions.js
│ ├── ch08.solutions.js
│ ├── ch09.solutions.js
│ ├── ch10.solutions.js
│ ├── ch11.solutions.js
│ └── ch12.solutions.js
├── .eslintrc.js
├── ch06
│ ├── index.html
│ └── main.js
├── package.json
├── README-original.md
├── README.md
└── test-utils.js
├── .DS_Store
├── images
├── cat.png
├── jar.jpg
├── chain.jpg
├── cover.png
├── fists.jpg
├── onion.png
├── catmap.png
├── cats_ss.png
├── dominoes.jpg
├── fn_graph.png
├── canopener.jpg
├── cat_comp1.png
├── cat_comp2.png
├── cat_theory.png
├── console_ss.png
├── functormap.png
├── clean-cover.png
├── function-sets.gif
├── id_to_maybe.png
├── functormapmaybe.png
├── ship_in_a_bottle.jpg
├── triangle_identity.png
├── monad_associativity.png
├── natural_transformation.png
└── relation-not-function.gif
├── support
├── .npmignore
├── CHANGELOG.md
├── .eslintrc.js
├── package.json
├── README-original.md
├── README.md
└── build-appendixes
├── .gitignore
├── code
├── part1_demo
│ ├── flickr.html
│ └── flickr.js
├── part1_exercises
│ ├── README.md
│ ├── package.json
│ ├── answers
│ │ ├── compose
│ │ │ ├── compose_exercises_spec.js
│ │ │ └── compose_exercises.js
│ │ └── curry
│ │ │ ├── curry_exercises_spec.js
│ │ │ └── curry_exercises.js
│ ├── exercises
│ │ ├── compose
│ │ │ ├── compose_exercises_spec.js
│ │ │ └── compose_exercises.js
│ │ └── curry
│ │ │ ├── curry_exercises_spec.js
│ │ │ └── curry_exercises.js
│ └── support.js
├── part2_exercises
│ ├── README.md
│ ├── package.json
│ ├── answers
│ │ ├── monads
│ │ │ ├── monad_exercises_spec.js
│ │ │ └── monad_exercises.js
│ │ ├── applicative
│ │ │ ├── applicative_exercises_spec.js
│ │ │ └── applicative_exercises.js
│ │ └── functors
│ │ │ ├── functor_exercises_spec.js
│ │ │ └── functor_exercises.js
│ ├── exercises
│ │ ├── monads
│ │ │ ├── monad_exercises_spec.js
│ │ │ └── monad_exercises.js
│ │ ├── applicative
│ │ │ ├── applicative_exercises_spec.js
│ │ │ └── applicative_exercises.js
│ │ └── functors
│ │ │ ├── functor_exercises_spec.js
│ │ │ └── functor_exercises.js
│ └── support.js
└── lib
│ └── package.json
├── LICENSE
├── book.json
├── TRANSLATIONS.md
├── TRANSLATIONS-es.md
├── .generate-cover.sh
├── .generate-summary.pl
├── package.json
├── CONTRIBUTING.md
├── .github
└── workflows
│ └── build.yml
├── CONTRIBUTING-es.md
├── appendix_a.md
├── appendix_a-es.md
├── appendix_c.md
├── appendix_c-es.md
├── README-original.md
├── FAQ.md
├── README.md
├── ch02.md
├── FAQ-es.md
├── ch02-es.md
├── SUMMARY.md
├── ch01.md
├── ch01-es.md
└── ch04.md
/exercises/ch04/solution_a.js:
--------------------------------------------------------------------------------
1 | const words = split(' ');
2 |
--------------------------------------------------------------------------------
/exercises/ch08/solution_a.js:
--------------------------------------------------------------------------------
1 | const incrF = map(add(1));
2 |
--------------------------------------------------------------------------------
/exercises/ch10/solution_b.js:
--------------------------------------------------------------------------------
1 | const safeAdd = liftA2(add);
2 |
--------------------------------------------------------------------------------
/exercises/ch04/solution_b.js:
--------------------------------------------------------------------------------
1 | const filterQs = filter(match(/q/i));
2 |
--------------------------------------------------------------------------------
/exercises/ch04/solution_c.js:
--------------------------------------------------------------------------------
1 | const max = reduce(keepHighest, -Infinity);
2 |
--------------------------------------------------------------------------------
/exercises/ch05/solution_a.js:
--------------------------------------------------------------------------------
1 | const isLastInStock = compose(prop('in_stock'), last);
2 |
--------------------------------------------------------------------------------
/exercises/ch08/solution_b.js:
--------------------------------------------------------------------------------
1 | const initial = compose(map(head), safeProp('name'));
2 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/.DS_Store
--------------------------------------------------------------------------------
/exercises/ch10/solution_a.js:
--------------------------------------------------------------------------------
1 | const safeAdd = curry((a, b) => Maybe.of(add).ap(a).ap(b));
2 |
--------------------------------------------------------------------------------
/exercises/ch11/solution_a.js:
--------------------------------------------------------------------------------
1 | const eitherToMaybe = either(always(nothing), Maybe.of);
2 |
--------------------------------------------------------------------------------
/exercises/ch08/solution_c.js:
--------------------------------------------------------------------------------
1 | const eitherWelcome = compose(map(showWelcome), checkActive);
2 |
--------------------------------------------------------------------------------
/images/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/cat.png
--------------------------------------------------------------------------------
/images/jar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/jar.jpg
--------------------------------------------------------------------------------
/exercises/ch05/solution_b.js:
--------------------------------------------------------------------------------
1 | const averageDollarValue = compose(average, map(prop('dollar_value')));
2 |
--------------------------------------------------------------------------------
/exercises/ch11/solution_c.js:
--------------------------------------------------------------------------------
1 | const strToList = split('');
2 |
3 | const listToStr = intercalate('');
4 |
--------------------------------------------------------------------------------
/images/chain.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/chain.jpg
--------------------------------------------------------------------------------
/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/cover.png
--------------------------------------------------------------------------------
/images/fists.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/fists.jpg
--------------------------------------------------------------------------------
/images/onion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/onion.png
--------------------------------------------------------------------------------
/images/catmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/catmap.png
--------------------------------------------------------------------------------
/images/cats_ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/cats_ss.png
--------------------------------------------------------------------------------
/images/dominoes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/dominoes.jpg
--------------------------------------------------------------------------------
/images/fn_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/fn_graph.png
--------------------------------------------------------------------------------
/exercises/ch11/solution_b.js:
--------------------------------------------------------------------------------
1 | const findNameById = compose(map(prop('name')), chain(eitherToTask), findUserById);
2 |
--------------------------------------------------------------------------------
/images/canopener.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/canopener.jpg
--------------------------------------------------------------------------------
/images/cat_comp1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/cat_comp1.png
--------------------------------------------------------------------------------
/images/cat_comp2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/cat_comp2.png
--------------------------------------------------------------------------------
/images/cat_theory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/cat_theory.png
--------------------------------------------------------------------------------
/images/console_ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/console_ss.png
--------------------------------------------------------------------------------
/images/functormap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/functormap.png
--------------------------------------------------------------------------------
/images/clean-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/clean-cover.png
--------------------------------------------------------------------------------
/images/function-sets.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/function-sets.gif
--------------------------------------------------------------------------------
/images/id_to_maybe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/id_to_maybe.png
--------------------------------------------------------------------------------
/support/.npmignore:
--------------------------------------------------------------------------------
1 | ch*
2 | test*
3 | .eslintrc.js
4 | node-modules
5 | test-util.js
6 | support.js
7 | build-appendixes
8 |
--------------------------------------------------------------------------------
/images/functormapmaybe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/functormapmaybe.png
--------------------------------------------------------------------------------
/exercises/ch10/solution_c.js:
--------------------------------------------------------------------------------
1 | const startGame = IO.of(game)
2 | .ap(getFromCache('player1'))
3 | .ap(getFromCache('player2'));
4 |
--------------------------------------------------------------------------------
/images/ship_in_a_bottle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/ship_in_a_bottle.jpg
--------------------------------------------------------------------------------
/images/triangle_identity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/triangle_identity.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | grunt
3 | less
4 | Gruntfile.js
5 | npm-debug.log
6 | .DS_Store
7 | *.pdf
8 | *.epub
9 | _book
10 |
--------------------------------------------------------------------------------
/images/monad_associativity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/monad_associativity.png
--------------------------------------------------------------------------------
/exercises/ch12/solution_a.js:
--------------------------------------------------------------------------------
1 | // getJsons :: Map Route Route -> Task Error (Map Route JSON)
2 | const getJsons = traverse(Task.of, httpGet);
3 |
--------------------------------------------------------------------------------
/images/natural_transformation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/natural_transformation.png
--------------------------------------------------------------------------------
/images/relation-not-function.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MostlyAdequate/mostly-adequate-guide-es/HEAD/images/relation-not-function.gif
--------------------------------------------------------------------------------
/exercises/ch09/solution_c.js:
--------------------------------------------------------------------------------
1 | const joinMailingList = compose(
2 | map(compose(chain(emailBlast), addToMailingList)),
3 | validateEmail,
4 | );
5 |
--------------------------------------------------------------------------------
/exercises/ch09/solution_b.js:
--------------------------------------------------------------------------------
1 | const basename = compose(last, split('/'));
2 |
3 | const logFilename = compose(chain(pureLog), map(basename))(getFile);
4 |
--------------------------------------------------------------------------------
/exercises/ch09/solution_a.js:
--------------------------------------------------------------------------------
1 | const getStreetName = compose(
2 | chain(safeProp('name')),
3 | chain(safeProp('street')),
4 | safeProp('address'),
5 | );
6 |
--------------------------------------------------------------------------------
/exercises/ch05/solution_c.js:
--------------------------------------------------------------------------------
1 | const fastestCar = compose(
2 | append(' is the fastest'),
3 | prop('name'),
4 | last,
5 | sortBy(prop('horsepower')),
6 | );
7 |
--------------------------------------------------------------------------------
/exercises/ch12/solution_b.js:
--------------------------------------------------------------------------------
1 | // startGame :: [Player] -> Either Error String
2 | const startGame = compose(map(always('game started!')), traverse(Either.of, validate));
3 |
--------------------------------------------------------------------------------
/exercises/ch04/exercise_a.js:
--------------------------------------------------------------------------------
1 | // Refactor to remove all arguments by partially applying the function.
2 |
3 | // words :: String -> [String]
4 | const words = str => split(' ', str);
5 |
--------------------------------------------------------------------------------
/exercises/ch08/exercise_a.js:
--------------------------------------------------------------------------------
1 | // Use `add` and `map` to make a function that increments a value inside a functor.
2 |
3 | // incrF :: Functor f => f Int -> f Int
4 | const incrF = undefined;
5 |
--------------------------------------------------------------------------------
/exercises/ch11/exercise_a.js:
--------------------------------------------------------------------------------
1 | // Write a natural transformation that converts `Either b a` to `Maybe a`
2 |
3 | // eitherToMaybe :: Either b a -> Maybe a
4 | const eitherToMaybe = undefined;
5 |
--------------------------------------------------------------------------------
/exercises/ch10/exercise_b.js:
--------------------------------------------------------------------------------
1 | // Rewrite `safeAdd` from exercise_a to use `liftA2` instead of `ap`.
2 |
3 | // safeAdd :: Maybe Number -> Maybe Number -> Maybe Number
4 | const safeAdd = undefined;
5 |
--------------------------------------------------------------------------------
/exercises/test/ch04.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 04', () => {
5 | runExercises('ch04');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/test/ch05.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 05', () => {
5 | runExercises('ch05');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/test/ch08.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 08', () => {
5 | runExercises('ch08');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/test/ch09.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 09', () => {
5 | runExercises('ch09');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/ch04/exercise_b.js:
--------------------------------------------------------------------------------
1 | // Refactor to remove all arguments by partially applying the functions.
2 |
3 | // filterQs :: [String] -> [String]
4 | const filterQs = xs => filter(x => x.match(/q/i), xs);
5 |
--------------------------------------------------------------------------------
/exercises/ch12/solution_c.js:
--------------------------------------------------------------------------------
1 | // readFirst :: String -> Task Error (Maybe String)
2 | const readFirst = compose(
3 | chain(traverse(Task.of, readfile('utf-8'))),
4 | map(safeHead),
5 | readdir,
6 | );
7 |
--------------------------------------------------------------------------------
/exercises/test/ch10.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 10', () => {
5 | runExercises('ch10');
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/exercises/test/ch11.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 11', () => {
5 | runExercises('ch11');
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/exercises/test/ch12.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runExercises } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 12', () => {
5 | runExercises('ch12');
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/exercises/test/ch04.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 04', () => {
5 | runSolutions('ch04');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/test/ch05.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 05', () => {
5 | runSolutions('ch05');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/test/ch08.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 08', () => {
5 | runSolutions('ch08');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/test/ch09.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 09', () => {
5 | runSolutions('ch09');
6 | });
7 |
--------------------------------------------------------------------------------
/exercises/ch10/exercise_a.js:
--------------------------------------------------------------------------------
1 | // Write a function that adds two possibly null numbers together using `Maybe` and `ap`.
2 |
3 | // safeAdd :: Maybe Number -> Maybe Number -> Maybe Number
4 | const safeAdd = undefined;
5 |
--------------------------------------------------------------------------------
/exercises/test/ch10.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 10', () => {
5 | runSolutions('ch10');
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/exercises/test/ch11.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 11', () => {
5 | runSolutions('ch11');
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/exercises/test/ch12.solutions.js:
--------------------------------------------------------------------------------
1 | const { describe } = require('mocha');
2 | const { runSolutions } = require('../test-utils');
3 |
4 | describe('Exercises Chapter 12', () => {
5 | runSolutions('ch12');
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/exercises/ch11/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals strToList, listToStr */
2 |
3 | const sortLetters = compose(listToStr, sortBy(identity), strToList);
4 |
5 | assert(
6 | sortLetters('sortme') === 'emorst',
7 | 'The function gives incorrect results',
8 | );
9 |
--------------------------------------------------------------------------------
/exercises/ch08/exercise_b.js:
--------------------------------------------------------------------------------
1 | // Given the following User object:
2 | //
3 | // const user = { id: 2, name: 'Albert', active: true };
4 | //
5 | // Use `safeProp` and `head` to find the first initial of the user.
6 |
7 | // initial :: User -> Maybe String
8 | const initial = undefined;
9 |
--------------------------------------------------------------------------------
/support/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.0.1 - 2019-06-26
2 |
3 | - Fix `Maybe.sequence` missing a `return` statement
4 |
5 | # 2.0.0 - 2019-05-24
6 |
7 | - Change `nothing :: () -> Maybe a` to `nothing :: Maybe a`
8 |
9 | # 1.2.0 - 2019-05-05
10 |
11 | - Fix `inspect` deprecation warning on node.js
12 |
--------------------------------------------------------------------------------
/code/part1_demo/flickr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | * All text in this book is under:
2 | Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
3 | http://creativecommons.org/licenses/by-sa/4.0/
4 |
5 | * All artwork randomly stolen from google image search so the license doesn't apply. Please let me know if any artwork is yours and I'll give credit or remove.
6 |
--------------------------------------------------------------------------------
/exercises/ch04/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals words */
2 |
3 | assert.arrayEqual(
4 | words('Jingle bells Batman smells'),
5 | ['Jingle', 'bells', 'Batman', 'smells'],
6 | 'The function gives incorrect results.',
7 | );
8 |
9 | assert(
10 | split.partially,
11 | 'The answer is incorrect; hint: split is currified!',
12 | );
13 |
--------------------------------------------------------------------------------
/exercises/ch05/exercise_c.js:
--------------------------------------------------------------------------------
1 | // Refactor `fastestCar` using `compose()` and other functions in pointfree-style.
2 |
3 | // fastestCar :: [Car] -> String
4 | const fastestCar = (cars) => {
5 | const sorted = sortBy(car => car.horsepower, cars);
6 | const fastest = last(sorted);
7 | return concat(fastest.name, ' is the fastest');
8 | };
9 |
--------------------------------------------------------------------------------
/exercises/ch08/solution_d.js:
--------------------------------------------------------------------------------
1 | const validateName = ({ name }) => (name.length > 3
2 | ? Either.of(null)
3 | : left('Your name need to be > 3')
4 | );
5 |
6 | const saveAndWelcome = compose(map(showWelcome), save);
7 |
8 | const register = compose(
9 | either(IO.of, saveAndWelcome),
10 | validateUser(validateName),
11 | );
12 |
--------------------------------------------------------------------------------
/exercises/ch04/exercise_c.js:
--------------------------------------------------------------------------------
1 | // Considering the following function:
2 | //
3 | // const keepHighest = (x, y) => (x >= y ? x : y);
4 | //
5 | // Refactor `max` to not reference any arguments using the helper function `keepHighest`.
6 |
7 | // max :: [Number] -> Number
8 | const max = xs => reduce((acc, x) => (x >= acc ? x : acc), -Infinity, xs);
9 |
--------------------------------------------------------------------------------
/exercises/ch11/exercise_b.js:
--------------------------------------------------------------------------------
1 | // Using `eitherToTask`, simplify `findNameById` to remove the nested `Either`.
2 | //
3 | // // eitherToTask :: Either a b -> Task a b
4 | // const eitherToTask = either(Task.rejected, Task.of);
5 |
6 | // findNameById :: Number -> Task Error (Either Error User)
7 | const findNameById = compose(map(map(prop('name'))), findUserById);
8 |
--------------------------------------------------------------------------------
/code/part1_exercises/README.md:
--------------------------------------------------------------------------------
1 | Part 1 Exercises
2 | ==================
3 |
4 | **Installation**:
5 | `npm install`
6 |
7 | **Running tests**:
8 | Tests are located in their corresponding folders. To run:
9 |
10 | ```
11 | cd exercises/curry
12 | mocha *spec.js
13 | ```
14 |
15 | Some will fail and some will pass. You'll need to edit the exercises until the tests pass.
16 |
--------------------------------------------------------------------------------
/code/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 |
--------------------------------------------------------------------------------
/exercises/ch08/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals incrF */
2 |
3 | assert(
4 | incrF(Identity.of(2)).$value === 3,
5 | 'The function gives incorrect results.',
6 | );
7 |
8 | assert(
9 | add.partially,
10 | 'The answer is incorrect; hint: add is currified!',
11 | );
12 |
13 | assert(
14 | map.partially,
15 | 'The answer is incorrect; hint: map is currified!',
16 | );
17 |
--------------------------------------------------------------------------------
/exercises/ch10/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals startGame */
2 |
3 | assert(
4 | startGame instanceof IO && typeof startGame.unsafePerformIO() === 'string',
5 | 'The answer has a wrong type; `startGame` should be an `IO String`',
6 | );
7 |
8 | assert(
9 | startGame.unsafePerformIO() === `${albert.name} vs ${theresa.name}`,
10 | 'The answer gives incorrect results',
11 | );
12 |
--------------------------------------------------------------------------------
/exercises/ch12/exercise_c.js:
--------------------------------------------------------------------------------
1 | // Considering the following functions:
2 | //
3 | // readfile :: String -> String -> Task Error String
4 | // readdir :: String -> Task Error [String]
5 | //
6 | // Use traversable to rearrange and flatten the nested Tasks & Maybe
7 |
8 | // readFirst :: String -> Task Error (Maybe (Task Error String))
9 | const readFirst = compose(map(map(readfile('utf-8'))), map(safeHead), readdir);
10 |
--------------------------------------------------------------------------------
/exercises/ch05/exercise_b.js:
--------------------------------------------------------------------------------
1 | // Considering the following function:
2 | //
3 | // const average = xs => reduce(add, 0, xs) / xs.length;
4 | //
5 | // Use the helper function `average` to refactor `averageDollarValue` as a composition.
6 |
7 | // averageDollarValue :: [Car] -> Int
8 | const averageDollarValue = (cars) => {
9 | const dollarValues = map(c => c.dollar_value, cars);
10 | return average(dollarValues);
11 | };
12 |
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitbook": "3.2.2",
3 | "root": ".",
4 | "title": "La Guía del Profesor Frisby en su Mayor Parte Adecuada para la Programación Funcional",
5 | "cover": "images/cover.png",
6 | "plugins": [
7 | "exercises@git+https://github.com/MostlyAdequate/plugin-exercises.git",
8 | "include-codeblock@3.2.3"
9 | ],
10 | "structure": {
11 | "summary": "SUMMARY-es.md"
12 | },
13 | "language": "es"
14 | }
15 |
--------------------------------------------------------------------------------
/exercises/ch11/exercise_c.js:
--------------------------------------------------------------------------------
1 | // Write the isomorphisms between String and [Char].
2 | //
3 | // As a reminder, the following functions are available in the exercise's context:
4 | //
5 | // split :: String -> String -> [String]
6 | // intercalate :: String -> [String] -> String
7 |
8 | // strToList :: String -> [Char]
9 | const strToList = undefined;
10 |
11 | // listToStr :: [Char] -> String
12 | const listToStr = undefined;
13 |
--------------------------------------------------------------------------------
/exercises/ch12/exercise_b.js:
--------------------------------------------------------------------------------
1 | // Using traversable, and the `validate` function, update `startGame` (and its signature)
2 | // to only start the game if all players are valid
3 | //
4 | // // validate :: Player -> Either String Player
5 | // validate = player => (player.name ? Either.of(player) : left('must have name'));
6 |
7 | // startGame :: [Player] -> [Either Error String]
8 | const startGame = compose(map(map(always('game started!'))), map(validate));
9 |
--------------------------------------------------------------------------------
/exercises/ch05/exercise_a.js:
--------------------------------------------------------------------------------
1 | // We consider Car objects of the following shape:
2 | //
3 | // {
4 | // name: 'Aston Martin One-77',
5 | // horsepower: 750,
6 | // dollar_value: 1850000,
7 | // in_stock: true,
8 | // }
9 | //
10 | // Use `compose()` to rewrite the function below.
11 |
12 |
13 | // isLastInStock :: [Car] -> Boolean
14 | const isLastInStock = (cars) => {
15 | const lastCar = last(cars);
16 | return prop('in_stock', lastCar);
17 | };
18 |
--------------------------------------------------------------------------------
/exercises/ch09/exercise_a.js:
--------------------------------------------------------------------------------
1 | // Considering a User object as follow:
2 | //
3 | // const user = {
4 | // id: 1,
5 | // name: 'Albert',
6 | // address: {
7 | // street: {
8 | // number: 22,
9 | // name: 'Walnut St',
10 | // },
11 | // },
12 | // };
13 | //
14 | // Use `safeProp` and `map/join` or `chain` to safely get the street name when given a user
15 |
16 | // getStreetName :: User -> Maybe String
17 | const getStreetName = undefined;
18 |
--------------------------------------------------------------------------------
/exercises/ch04/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals max */
2 |
3 | assert(
4 | max([323, 523, 554, 123, 5234]) === 5234,
5 | 'The function gives incorrect results.',
6 | );
7 |
8 | assert(
9 | reduce.partially,
10 | 'The answer is incorrect; hint: look at the arguments for `reduce`!',
11 | );
12 |
13 | assert(
14 | keepHighest.calledBy && keepHighest.calledBy.name === '$reduceIterator',
15 | 'The answer is incorrect; hint: look closely to `reduce\'s` iterator and `keepHighest`!',
16 | );
17 |
--------------------------------------------------------------------------------
/exercises/ch12/exercise_a.js:
--------------------------------------------------------------------------------
1 | // Considering the following elements:
2 | //
3 | // // httpGet :: Route -> Task Error JSON
4 | // // routes :: Map Route Route
5 | // const routes = new Map({ '/': '/', '/about': '/about' });
6 | //
7 | // Use the traversable interface to change the type signature of `getJsons`.
8 | //
9 | // getJsons :: Map Route Route -> Task Error (Map Route JSON)
10 |
11 | // getJsons :: Map Route Route -> Map Route (Task Error JSON)
12 | const getJsons = map(httpGet);
13 |
--------------------------------------------------------------------------------
/exercises/ch09/exercise_c.js:
--------------------------------------------------------------------------------
1 | // For this exercise, we consider helpers with the following signatures:
2 | //
3 | // validateEmail :: Email -> Either String Email
4 | // addToMailingList :: Email -> IO([Email])
5 | // emailBlast :: [Email] -> IO ()
6 | //
7 | // Use `validateEmail`, `addToMailingList` and `emailBlast` to create a function
8 | // which adds a new email to the mailing list if valid, and then notify the whole
9 | // list.
10 |
11 | // joinMailingList :: Email -> Either String (IO ())
12 | const joinMailingList = undefined;
13 |
--------------------------------------------------------------------------------
/exercises/ch09/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals logFilename */
2 |
3 | assert(
4 | logFilename instanceof IO,
5 | 'The function gives incorrect results; hint: `logFilename` should be an IO()',
6 | );
7 |
8 | if (logFilename.unsafePerformIO() instanceof IO) {
9 | throw new Error('The function gives incorrect results; hint: make sure to `chain` effects as you go');
10 | }
11 |
12 | assert(
13 | logFilename.unsafePerformIO() === 'ch09.md',
14 | 'The function gives incorrect results; hint: did you retrieve the file\'s basename ?',
15 | );
16 |
--------------------------------------------------------------------------------
/code/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MAG_lib",
3 | "version": "0.0.1",
4 | "description": "Support libs for the book",
5 | "main": "index.js",
6 | "dependencies": {
7 | "ramda": "^0.13.0"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/DrBoolean/mostly-adequate-guide"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "bugs": {
16 | "url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
17 | },
18 | "homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
19 | }
20 |
--------------------------------------------------------------------------------
/exercises/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const globals = Object
2 | .keys(require('./support'))
3 | .reduce((o, k) => ({ ...o, [k]: true }), { requirejs: true, assert: true });
4 |
5 | module.exports = {
6 | extends: 'airbnb',
7 | env: {
8 | browser: true,
9 | amd: true,
10 | },
11 | globals,
12 | rules: {
13 | 'import/no-amd': 0,
14 | 'import/no-dynamic-require': 0,
15 | 'no-unused-vars': 0,
16 | 'object-curly-newline': [2, {
17 | multiline: true,
18 | consistent: true,
19 | minProperties: 5,
20 | }],
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/exercises/ch04/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals filterQs */
2 |
3 | filter.calledPartial = false;
4 | match.calledPartial = false;
5 |
6 | assert.arrayEqual(
7 | filterQs(['quick', 'camels', 'quarry', 'over', 'quails']),
8 | ['quick', 'quarry', 'quails'],
9 | 'The function gives incorrect results.',
10 | );
11 |
12 | assert(
13 | filter.partially,
14 | 'The answer is incorrect; hint: look at the arguments for `filter`.',
15 | );
16 |
17 | assert(
18 | match.partially,
19 | 'The answer is incorrect; hint: look at the arguments for `match`.',
20 | );
21 |
--------------------------------------------------------------------------------
/support/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const globals = Object
2 | .keys(require('./support'))
3 | .reduce((o, k) => ({ ...o, [k]: true }), { requirejs: true, assert: true });
4 |
5 | module.exports = {
6 | extends: 'airbnb',
7 | env: {
8 | browser: true,
9 | amd: true,
10 | },
11 | globals,
12 | rules: {
13 | 'import/no-amd': 0,
14 | 'import/no-dynamic-require': 0,
15 | 'no-unused-vars': 0,
16 | 'object-curly-newline': [2, {
17 | multiline: true,
18 | consistent: true,
19 | minProperties: 5,
20 | }],
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/exercises/ch09/exercise_b.js:
--------------------------------------------------------------------------------
1 | // We now consider the following items:
2 | //
3 | // // getFile :: IO String
4 | // const getFile = IO.of('/home/mostly-adequate/ch09.md');
5 | //
6 | // // pureLog :: String -> IO ()
7 | // const pureLog = str => new IO(() => console.log(str));
8 | //
9 | // Use getFile to get the filepath, remove the directory and keep only the basename,
10 | // then purely log it. Hint: you may want to use `split` and `last` to obtain the
11 | // basename from a filepath.
12 |
13 | // logFilename :: IO ()
14 | const logFilename = undefined;
15 |
--------------------------------------------------------------------------------
/exercises/ch11/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals eitherToMaybe */
2 |
3 | const just = eitherToMaybe(Either.of('one eyed willy'));
4 | const noth = eitherToMaybe(left('some error'));
5 |
6 | assert(
7 | just instanceof Maybe && just.isJust && just.$value === 'one eyed willy',
8 | 'The function maps the `Right()` side incorrectly; hint: `Right(14)` should be mapped to `Just(14)`',
9 | );
10 |
11 | assert(
12 | noth instanceof Maybe && noth.isNothing,
13 | 'The function maps the `Left()` side incorrectly; hint: `Left(\'error\')` should be mapped to `Nothing`',
14 | );
15 |
--------------------------------------------------------------------------------
/exercises/ch08/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals initial */
2 |
3 | if (!(initial(albert) instanceof Maybe) && initial.callees && initial.callees[0] === 'safeProp' && initial.callees[1] === 'head') {
4 | throw new Error('The function gives incorrect results; hint: look carefully at the signatures of `safeProp` and `head`!');
5 | }
6 |
7 | assert(
8 | initial(albert).$value === 'A',
9 | 'The function gives incorrect results.',
10 | );
11 |
12 | assert.arrayEqual(
13 | initial.callees || [],
14 | ['safeProp', 'map'],
15 | 'The answer is incorrect; hint: you can compose `safeProp` with `head` in a declarative way',
16 | );
17 |
--------------------------------------------------------------------------------
/exercises/ch10/exercise_c.js:
--------------------------------------------------------------------------------
1 | // For this exercise, we consider the following helpers:
2 | //
3 | // const localStorage = {
4 | // player1: { id:1, name: 'Albert' },
5 | // player2: { id:2, name: 'Theresa' },
6 | // };
7 | //
8 | // // getFromCache :: String -> IO User
9 | // const getFromCache = x => new IO(() => localStorage[x]);
10 | //
11 | // // game :: User -> User -> String
12 | // const game = curry((p1, p2) => `${p1.name} vs ${p2.name}`);
13 | //
14 | // Write an IO that gets both player1 and player2 from the cache and starts the game.
15 |
16 | // startGame :: IO String
17 | const startGame = undefined;
18 |
--------------------------------------------------------------------------------
/exercises/ch08/exercise_c.js:
--------------------------------------------------------------------------------
1 | // Given the following helper functions:
2 | //
3 | // // showWelcome :: User -> String
4 | // const showWelcome = compose(concat('Welcome '), prop('name'));
5 | //
6 | // // checkActive :: User -> Either String User
7 | // const checkActive = function checkActive(user) {
8 | // return user.active
9 | // ? Either.of(user)
10 | // : left('Your account is not active');
11 | // };
12 | //
13 | // Write a function that uses `checkActive` and `showWelcome` to grant access or return the error.
14 |
15 | // eitherWelcome :: User -> Either String String
16 | const eitherWelcome = undefined;
17 |
--------------------------------------------------------------------------------
/exercises/ch06/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flickr App
6 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/exercises/ch05/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals fastestCar */
2 |
3 | try {
4 | assert(
5 | fastestCar(cars) === 'Aston Martin One-77 is the fastest',
6 | 'The function gives incorrect results.',
7 | );
8 | } catch (err) {
9 | const callees = fastestCar.callees || [];
10 |
11 | if (callees.length > 0 && callees[0] !== 'sortBy') {
12 | throw new Error('The answer is incorrect; hint: functions are composed from right to left!');
13 | }
14 |
15 | throw err;
16 | }
17 |
18 | const callees = fastestCar.callees || [];
19 |
20 | assert.arrayEqual(
21 | callees.slice(0, 3),
22 | ['sortBy', 'last', 'prop'],
23 | 'The answer is incorrect; hint: Hindley-Milner signatures help a lot to reason about composition!',
24 | );
25 |
--------------------------------------------------------------------------------
/exercises/ch12/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals readFirst */
2 |
3 | const res = readFirst('__dirname');
4 |
5 | const throwUnexpected = () => {
6 | throw new Error('The function gives incorrect results; a Task has resolved unexpectedly!');
7 | };
8 |
9 | assert(
10 | res instanceof Task,
11 | 'The function has an invalid type; hint: `readFirst` must return a `Task`!',
12 | );
13 |
14 | res.fork(throwUnexpected, ($res) => {
15 | assert(
16 | $res instanceof Maybe,
17 | 'The function has an invalid type; hint: `readFirst` must return a `Task Error (Maybe String)`!',
18 | );
19 |
20 | assert(
21 | $res.isJust && $res.$value === 'content of file1 (utf-8)',
22 | 'The function gives incorrect results.',
23 | );
24 | });
25 |
--------------------------------------------------------------------------------
/exercises/ch05/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals averageDollarValue */
2 |
3 | try {
4 | assert(
5 | averageDollarValue(cars) === 790700,
6 | 'The function gives incorrect results.',
7 | );
8 | } catch (err) {
9 | const callees = averageDollarValue.callees || [];
10 |
11 | if (callees[0] === 'average' && callees[1] === 'map') {
12 | throw new Error('The answer is incorrect; hint: functions are composed from right to left!');
13 | }
14 |
15 | throw err;
16 | }
17 |
18 | assert.arrayEqual(
19 | averageDollarValue.callees || [],
20 | ['map', 'average'],
21 | 'The answer is incorrect; hint: map is currified!',
22 | );
23 |
24 | assert(
25 | prop.partially,
26 | 'The answer is almost correct; hint: you can use prop to access objects\' properties!',
27 | );
28 |
--------------------------------------------------------------------------------
/code/part1_exercises/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MAG_Part1_Exercises",
3 | "version": "0.0.1",
4 | "description": "Exercises for Part 1 of the Book",
5 | "main": "index.js",
6 | "dependencies": {
7 | "accounting": "^0.4.1",
8 | "chai": "^1.9.1",
9 | "ramda": "^0.13.0"
10 | },
11 | "devDependencies": {
12 | "mocha": "^1.17.1"
13 | },
14 | "scripts": {
15 | "test": "mocha exercises/**/*_spec.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/DrBoolean/mostly-adequate-guide"
20 | },
21 | "author": "",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
25 | },
26 | "homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
27 | }
28 |
--------------------------------------------------------------------------------
/code/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 |
--------------------------------------------------------------------------------
/exercises/ch09/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals joinMailingList */
2 |
3 | const res = joinMailingList('email@email.com');
4 |
5 | assert(
6 | res instanceof Either,
7 | 'The function has an invalid type; hint: `joinMailingList` should return an Either String (IO ())',
8 | );
9 |
10 | if (res.$value.unsafePerformIO() instanceof IO) {
11 | throw new Error('The function gives incorrect results; hint: make sure to `chain` effects as you go');
12 | }
13 |
14 | const getResult = either(identity, unsafePerformIO);
15 |
16 | assert(
17 | getResult(joinMailingList('sleepy@grandpa.net')) === 'sleepy@grandpa.net',
18 | 'The function gives incorrect results.',
19 | );
20 |
21 | assert(
22 | getResult(joinMailingList('notanemail')) === 'invalid email',
23 | 'The function gives incorrect results.',
24 | );
25 |
--------------------------------------------------------------------------------
/exercises/ch05/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals isLastInStock */
2 |
3 | const fixture01 = cars.slice(0, 3);
4 | const fixture02 = cars.slice(3);
5 |
6 | try {
7 | assert(
8 | isLastInStock(fixture01),
9 | 'The function gives incorrect results.',
10 | );
11 |
12 | assert(
13 | !isLastInStock(fixture02),
14 | 'The function gives incorrect results.',
15 | );
16 | } catch (err) {
17 | const callees = isLastInStock.callees || [];
18 |
19 | if (callees[0] === 'prop' && callees[1] === 'last') {
20 | throw new Error('The answer is incorrect; hint: functions are composed from right to left!');
21 | }
22 |
23 | throw err;
24 | }
25 |
26 | assert.arrayEqual(
27 | isLastInStock.callees || [],
28 | ['last', 'prop'],
29 | 'The answer is incorrect; hint: prop is currified!',
30 | );
31 |
--------------------------------------------------------------------------------
/exercises/ch08/validation_c.js:
--------------------------------------------------------------------------------
1 | /* globals eitherWelcome */
2 |
3 | if (!(eitherWelcome(gary) instanceof Either) && eitherWelcome.callees && eitherWelcome.callees[0] === 'checkActive' && eitherWelcome.callees[1] === 'showWelcome') {
4 | throw new Error('The function gives incorrect results; hint: look carefully at the signatures of `checkActive` and `showWelcome`!');
5 | }
6 |
7 | assert(
8 | eitherWelcome(gary).$value === 'Your account is not active',
9 | 'The function gives incorrect results.',
10 | );
11 |
12 | assert(
13 | eitherWelcome(theresa).$value === 'Welcome Theresa',
14 | 'The function gives incorrect results.',
15 | );
16 |
17 | assert.arrayEqual(
18 | eitherWelcome.callees || [],
19 | ['checkActive', 'map'],
20 | 'The answer is incorrect; hint: you can compose `checkActive` with `showWelcome` in a declarative way!',
21 | );
22 |
--------------------------------------------------------------------------------
/exercises/ch08/exercise_d.js:
--------------------------------------------------------------------------------
1 | // We now consider the following functions:
2 | //
3 | // // validateUser :: (User -> Either String ()) -> User -> Either String User
4 | // const validateUser = curry((validate, user) => validate(user).map(_ => user));
5 | //
6 | // // save :: User -> IO User
7 | // const save = user => new IO(() => ({ ...user, saved: true }));
8 | //
9 | // Write a function `validateName` which checks whether a user has a name longer than 3 characters
10 | // or return an error message. Then use `either`, `showWelcome` and `save` to write a `register`
11 | // function to signup and welcome a user when the validation is ok.
12 | //
13 | // Remember either's two arguments must return the same type.
14 |
15 | // validateName :: User -> Either String ()
16 | const validateName = undefined;
17 |
18 | // register :: User -> IO String
19 | const register = compose(undefined, validateUser(validateName));
20 |
--------------------------------------------------------------------------------
/exercises/ch12/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals getJsons */
2 |
3 | const throwUnexpected = () => {
4 | throw new Error('The function gives incorrect results; a Task has resolved unexpectedly!');
5 | };
6 |
7 | const res = getJsons(routes);
8 |
9 | assert(
10 | res instanceof Task,
11 | 'The function has an invalid type; hint: `getJsons` must return a `Task`!',
12 | );
13 |
14 | res.fork(throwUnexpected, ($res) => {
15 | assert(
16 | $res.$value['/'] === 'json for /' && $res.$value['/about'] === 'json for /about',
17 | 'The function gives incorrect results; hint: did you correctly map `httpGet` over the Map\'s values?',
18 | );
19 |
20 | const callees = getJsons.callees; // eslint-disable-line prefer-destructuring
21 |
22 | if (callees && callees[0] === 'map' && callees[1] === 'sequence') {
23 | throw new Error('The function could be written in a simpler form; hint: compose(sequence(of), map(fn)) === traverse(of, fn)');
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/exercises/ch10/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals safeAdd */
2 |
3 | const res = safeAdd(Maybe.of(2), Maybe.of(3));
4 | assert(
5 | res instanceof Maybe && typeof res.$value === 'number',
6 | 'The function has a wrong type; make sure to wrap your numbers inside `Maybe.of`',
7 | );
8 |
9 | assert(
10 | safeAdd(Maybe.of(2), Maybe.of(3)).$value === 5,
11 | 'The function gives incorrect results; did you use `add` ?',
12 | );
13 |
14 | assert(
15 | safeAdd(Maybe.of(null), Maybe.of(3)).isNothing,
16 | 'The function gives incorrect results; `Nothing` should be returned when at least one value is `null`',
17 | );
18 |
19 | assert(
20 | safeAdd(Maybe.of(2), Maybe.of(null)).isNothing,
21 | 'The function gives incorrect results; `Nothing` should be returned when at least one value is `null`',
22 | );
23 |
24 | assert(
25 | withSpyOn('ap', Maybe.prototype, () => safeAdd(Maybe.of(2), Maybe.of(3))),
26 | 'The function seems incorrect; did you use `ap` ?',
27 | );
28 |
--------------------------------------------------------------------------------
/exercises/ch10/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals safeAdd */
2 |
3 | const res = safeAdd(Maybe.of(2), Maybe.of(3));
4 | assert(
5 | res instanceof Maybe && typeof res.$value === 'number',
6 | 'The function has a wrong type; make sure to wrap your numbers inside `Maybe.of`',
7 | );
8 |
9 | assert(
10 | safeAdd(Maybe.of(2), Maybe.of(3)).$value === 5,
11 | 'The function gives incorrect results; did you use `add` ?',
12 | );
13 |
14 | assert(
15 | safeAdd(Maybe.of(null), Maybe.of(3)).isNothing,
16 | 'The function gives incorrect results; `Nothing` should be returned when at least one value is `null`',
17 | );
18 |
19 | assert(
20 | safeAdd(Maybe.of(2), Maybe.of(null)).isNothing,
21 | 'The function gives incorrect results; `Nothing` should be returned when at least one value is `null`',
22 | );
23 |
24 | assert(
25 | liftA2.partially && safeAdd.name === 'liftA2',
26 | 'The function seems incorrect; did you use `liftA2`? Remember that `liftA2` is currified!',
27 | );
28 |
--------------------------------------------------------------------------------
/exercises/ch12/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals startGame */
2 |
3 | const res = startGame(new List([albert, theresa]));
4 |
5 | assert(
6 | res instanceof Either,
7 | 'The function has an invalid type; hint: `startGame` must return a `Either`!',
8 | );
9 |
10 | assert(
11 | res.isRight && res.$value === 'game started!',
12 | 'The function gives incorrect results; a game should have started for a list of valid players!',
13 | );
14 |
15 | const rej = startGame(new List([gary, { what: 14 }]));
16 | assert(
17 | rej.isLeft && rej.$value === 'must have name',
18 | 'The function gives incorrect results; a game shouldn\'t be started if the list contains invalid players!',
19 | );
20 |
21 | const callees = startGame.callees; // eslint-disable-line prefer-destructuring
22 |
23 | if (callees && callees[0] === 'map' && callees[1] === 'sequence') {
24 | throw new Error('The function could be written in a simpler form; hint: compose(sequence(of), map(fn)) === traverse(of, fn)');
25 | }
26 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/monads/monad_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./monad_exercises');
3 | var assert = require("chai").assert
4 | var _ = require('ramda');
5 |
6 | describe("Monad Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(E.ex1(E.user), Maybe.of('Walnut St'));
10 | });
11 |
12 | it('Exercise 2', function(){
13 | assert.equal(E.ex2(undefined).unsafePerformIO(), 'logged monad_exercises.js');
14 | });
15 |
16 | it('Exercise 3', function(done){
17 | E.ex3(13).fork(console.log, function (res) {
18 | assert.deepEqual(res.map(_.prop('post_id')), [13, 13]);
19 | done();
20 | });
21 | });
22 |
23 | it('Exercise 4', function(){
24 | var getResult = either(_.identity, unsafePerformIO);
25 | assert.equal(getResult(E.ex4('notanemail')), 'invalid email');
26 | assert.equal(getResult(E.ex4('sleepy@grandpa.net')), 'emailed: sleepy@grandpa.net');
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/monads/monad_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./monad_exercises');
3 | var assert = require("chai").assert
4 | var _ = require('ramda');
5 |
6 | describe("Monad Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(E.ex1(E.user), Maybe.of('Walnut St'));
10 | });
11 |
12 | it('Exercise 2', function(){
13 | assert.equal(E.ex2(undefined).unsafePerformIO(), 'logged monad_exercises.js');
14 | });
15 |
16 | it('Exercise 3', function(done){
17 | E.ex3(13).fork(console.log, function (res) {
18 | assert.deepEqual(res.map(_.prop('post_id')), [13, 13]);
19 | done();
20 | });
21 | });
22 |
23 | it('Exercise 4', function(){
24 | var getResult = either(_.identity, unsafePerformIO);
25 | assert.equal(getResult(E.ex4('notanemail')), 'invalid email');
26 | assert.equal(getResult(E.ex4('sleepy@grandpa.net')), 'emailed: sleepy@grandpa.net');
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/compose/compose_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./compose_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Compose Exercises", function(){
5 | var CARS = E.CARS;
6 |
7 | it('Exercise 1', function(){
8 | assert.equal(E.isLastInStock(CARS), false);
9 | });
10 |
11 | it('Exercise 2', function(){
12 | assert.equal(E.nameOfFirstCar(CARS), "Ferrari FF");
13 | });
14 |
15 | it('Exercise 3', function(){
16 | assert.equal(E.averageDollarValue(CARS), 790700);
17 | });
18 |
19 | it('Exercise 4', function(){
20 | assert.deepEqual(E.sanitizeNames(CARS), ['ferrari_ff', 'spyker_c12_zagato', 'jaguar_xkr_s', 'audi_r8', 'aston_martin_one_77', 'pagani_huayra']);
21 | });
22 |
23 | it('Bonus 1', function(){
24 | assert.equal(E.availablePrices(CARS), '$700,000.00, $1,850,000.00');
25 | });
26 |
27 | it('Bonus 2', function(){
28 | assert.equal(E.fastestCar(CARS), 'Aston Martin One-77 is the fastest');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/compose/compose_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./compose_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Compose Exercises", function(){
5 | var CARS = E.CARS
6 |
7 | it('Exercise 1', function(){
8 | assert.equal(E.isLastInStock(CARS), false);
9 | });
10 |
11 | it('Exercise 2', function(){
12 | assert.equal(E.nameOfFirstCar(CARS), "Ferrari FF");
13 | });
14 |
15 | it('Exercise 3', function(){
16 | assert.equal(E.averageDollarValue(CARS), 790700);
17 | });
18 |
19 | it('Exercise 4', function(){
20 | assert.deepEqual(E.sanitizeNames(CARS), ['ferrari_ff', 'spyker_c12_zagato', 'jaguar_xkr_s', 'audi_r8', 'aston_martin_one_77', 'pagani_huayra']);
21 | });
22 |
23 | it('Bonus 1', function(){
24 | assert.equal(E.availablePrices(CARS), '$700,000.00, $1,850,000.00');
25 | });
26 |
27 | it('Bonus 2', function(){
28 | assert.equal(E.fastestCar(CARS), 'Aston Martin One-77 is the fastest');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/applicative/applicative_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./applicative_exercises');
3 | var _ = require('ramda');
4 | var assert = require("chai").assert
5 |
6 | describe("Applicative Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(Maybe.of(5), E.ex1(2, 3));
10 | assert.deepEqual(Maybe.of(null), E.ex1(null, 3));
11 | });
12 |
13 | it('Exercise 2', function(){
14 | assert.deepEqual(Maybe.of(5), E.ex2(Maybe.of(2), Maybe.of(3)));
15 | assert.deepEqual(Maybe.of(null), E.ex2(Maybe.of(null), Maybe.of(3)));
16 | });
17 |
18 | it('Exercise 3', function(done){
19 | E.ex3.fork(console.log, function (html) {
20 | assert.equal("Love them tasks
This book should be illegalMonads are like space burritos", html);
21 | done();
22 | });
23 | });
24 |
25 | it('Exercise 4', function(){
26 | assert.equal("toby vs sally", E.ex4.unsafePerformIO());
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/applicative/applicative_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./applicative_exercises');
3 | var _ = require('ramda');
4 | var assert = require("chai").assert
5 |
6 | describe("Applicative Exercises", function(){
7 |
8 | it('Exercise 1', function(){
9 | assert.deepEqual(Maybe.of(5), E.ex1(2, 3));
10 | assert.deepEqual(Maybe.of(null), E.ex1(null, 3));
11 | });
12 |
13 | it('Exercise 2', function(){
14 | assert.deepEqual(Maybe.of(5), E.ex2(Maybe.of(2), Maybe.of(3)));
15 | assert.deepEqual(Maybe.of(null), E.ex2(Maybe.of(null), Maybe.of(3)));
16 | });
17 |
18 | it('Exercise 3', function(done){
19 | E.ex3.fork(console.log, function (html) {
20 | assert.equal("Love them futures
This book should be illegalMonads are like space burritos", html);
21 | done();
22 | });
23 | });
24 |
25 | it('Exercise 4', function(){
26 | assert.equal("toby vs sally", E.ex4.unsafePerformIO());
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/support/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mostly-adequate/support",
3 | "version": "2.0.1",
4 | "description": "Support functions and data-structures from the Mostly Adequate Guide to Functional Programming",
5 | "license": "MIT",
6 | "main": "index.js",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/MostlyAdequate/mostly-adequate-guide"
10 | },
11 | "author": "@mostly-adequate",
12 | "bugs": {
13 | "url": "https://github.com/MostlyAdequate/mostly-adequate-guide/issues"
14 | },
15 | "homepage": "https://github.com/MostlyAdequate/mostly-adequate-guide/support",
16 | "keywords": [
17 | "functional programming",
18 | "mostly adequate",
19 | "guide",
20 | "fp"
21 | ],
22 | "dependencies": {},
23 | "devDependencies": {
24 | "eslint": "^5.9.0",
25 | "eslint-config-airbnb": "^16.1.0",
26 | "eslint-plugin-import": "^2.8.0",
27 | "eslint-plugin-jsx-a11y": "^6.0.2",
28 | "eslint-plugin-react": "^7.5.1"
29 | },
30 | "scripts": {
31 | "lint": "eslint ."
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/exercises/ch09/validation_a.js:
--------------------------------------------------------------------------------
1 | /* globals getStreetName */
2 |
3 | const res = getStreetName(albert);
4 |
5 | if ((!(res instanceof Maybe) || typeof res.$value !== 'string' || res.isNothing) && getStreetName.callees) {
6 | [1, 2, 3].forEach((i) => {
7 | if (getStreetName.callees[i] === 'map' && getStreetName.callees[i + 1] !== 'join') {
8 | throw new Error('The function gives incorrect results; hint: you can use `join` to flatten two monads!');
9 | }
10 | });
11 |
12 | [1, 2].forEach((i) => {
13 | if (!['map', 'chain'].includes(getStreetName.callees[i])) {
14 | throw new Error('The function gives incorrect results; hint: look carefully at the signature of `safeProp` and `chain`!');
15 | }
16 | });
17 | }
18 |
19 | assert(
20 | getStreetName(albert).$value === 'Walnut St',
21 | 'The function gives incorrect results.',
22 | );
23 |
24 | assert(
25 | getStreetName(gary).isNothing,
26 | 'The function gives incorrect results.',
27 | );
28 |
29 | assert(
30 | getStreetName(theresa).isNothing,
31 | 'The function gives incorrect results.',
32 | );
33 |
--------------------------------------------------------------------------------
/TRANSLATIONS.md:
--------------------------------------------------------------------------------
1 | # Available Translations
2 |
3 | * [中文版 (Chinese)](https://github.com/llh911001/mostly-adequate-guide-chinese) by Linghao Li @llh911001
4 | * [Русский (Russian)](https://github.com/MostlyAdequate/mostly-adequate-guide-ru) by Maksim Filippov @maksimf
5 | * [Українська (Ukrainian)](https://github.com/ivanzusko/mostly-adequate-guide-uk) by Ivan Zusko @ivanzusko
6 | * [Français (French)](https://github.com/MostlyAdequate/mostly-adequate-guide-fr) by Benkort Matthias @KtorZ
7 | * [Português (Portuguese)](https://github.com/MostlyAdequate/mostly-adequate-guide-pt-BR) by Palmer Oliveira @expalmer
8 | * [Español (Spanish)](https://github.com/MostlyAdequate/mostly-adequate-guide-es) by Gustavo Marin @guumaster
9 | * [Italiano (Italian)](https://github.com/MostlyAdequate/mostly-adequate-guide-it) by Elia Gentili @eliagentili
10 | * [한국어 (Korean)](https://github.com/enshahar/mostly-adequate-guide-kr) by Frank Hyunsok Oh @enshahar
11 | * [bahasa Indonesia (Indonesian)](https://github.com/MostlyAdequate/mostly-adequate-guide-id) by Ahmad Naufal Mukhtar @anaufalm
12 |
13 |
14 | ## Creating new Translations
15 |
16 | See [Creating new translations](CONTRIBUTING.md#Translations)
17 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/curry/curry_exercises_spec.js:
--------------------------------------------------------------------------------
1 | var E = require('./curry_exercises');
2 | var assert = require("chai").assert;
3 |
4 | describe("Curry Exercises", function(){
5 |
6 | it('Exercise 1', function(){
7 | assert.deepEqual(E.words("Jingle bells Batman smells"), ['Jingle', 'bells', 'Batman', 'smells']);
8 | });
9 |
10 | it('Exercise 1a', function(){
11 | assert.deepEqual(E.sentences(["Jingle bells Batman smells", "Robin laid an egg"]), [['Jingle', 'bells', 'Batman', 'smells'], ['Robin', 'laid', 'an', 'egg']]);
12 | });
13 |
14 | it('Exercise 2', function(){
15 | assert.deepEqual(E.filterQs(['quick', 'camels', 'quarry', 'over', 'quails']), ['quick', 'quarry', 'quails']);
16 | });
17 |
18 | it('Exercise 3', function(){
19 | assert.equal(E.max([323,523,554,123,5234]), 5234);
20 | });
21 |
22 | if (E.slice != undefined) {
23 | it('Curry Bonus 1', function(){
24 | assert.deepEqual(E.slice(1)(3)(['a', 'b', 'c']), ['b', 'c']);
25 | });
26 | }
27 |
28 | if (E.take != undefined) {
29 | it('Curry Bonus 2', function(){
30 | assert.deepEqual(E.take(2)(['a', 'b', 'c']), ['a', 'b']);
31 | });
32 | }
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/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 |
--------------------------------------------------------------------------------
/TRANSLATIONS-es.md:
--------------------------------------------------------------------------------
1 | # Traducciones Disponibles
2 |
3 | * [中文版 (Chinese)](https://github.com/llh911001/mostly-adequate-guide-chinese) by Linghao Li @llh911001
4 | * [Русский (Russian)](https://github.com/MostlyAdequate/mostly-adequate-guide-ru) by Maksim Filippov @maksimf
5 | * [Українська (Ukrainian)](https://github.com/ivanzusko/mostly-adequate-guide-uk) by Ivan Zusko @ivanzusko
6 | * [Français (French)](https://github.com/MostlyAdequate/mostly-adequate-guide-fr) by Benkort Matthias @KtorZ
7 | * [Português (Portuguese)](https://github.com/MostlyAdequate/mostly-adequate-guide-pt-BR) by Palmer Oliveira @expalmer
8 | * [Español (Spanish)](https://github.com/MostlyAdequate/mostly-adequate-guide-es) by Gustavo Marin @guumaster
9 | * [Italiano (Italian)](https://github.com/MostlyAdequate/mostly-adequate-guide-it) by Elia Gentili @eliagentili
10 | * [한국어 (Korean)](https://github.com/enshahar/mostly-adequate-guide-kr) by Frank Hyunsok Oh @enshahar
11 | * [bahasa Indonesia (Indonesian)](https://github.com/MostlyAdequate/mostly-adequate-guide-id) by Ahmad Naufal Mukhtar @anaufalm
12 |
13 |
14 | ## Creando Nuevas Traducciones
15 |
16 | Ver [Creando nuevas traducciones](CONTRIBUTING-es.md#Traducciones)
17 |
--------------------------------------------------------------------------------
/.generate-cover.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | json_file_with_title="book.json"
4 | title_font="Courier-10-Pitch"
5 |
6 | font_is_on_system () {
7 | convert -list font | grep -q $title_font
8 | }
9 |
10 | get_title_from_json () {
11 | grep -Po '"title":\s"\K[^"]+' $json_file_with_title
12 | }
13 |
14 | put_padding_into_image () {
15 | local top_border="0"
16 | local left_border="20"
17 | echo "-gravity northwest -splice ${left_border}x${top_border}"
18 | }
19 |
20 | if [ ! -f $json_file_with_title ]
21 | then
22 | echo "The file $json_file_with_title was not found"
23 | exit 1
24 | fi
25 |
26 | if [ $(get_title_from_json | wc -c) -eq 0 ]
27 | then
28 | echo "The title key was not found into $json_file_with_title file"
29 | exit 1
30 | fi
31 |
32 | if ! $(font_is_on_system)
33 | then
34 | echo "The font $title_font needs to be installed on the system"
35 | exit 1
36 | fi
37 |
38 | convert -font $title_font \
39 | -gravity west \
40 | -background "#0000" \
41 | -fill black \
42 | -size 590x300 \
43 | caption:"$(get_title_from_json)" \
44 | $(put_padding_into_image) \
45 | ./images/clean-cover.png \
46 | +swap \
47 | -gravity northwest \
48 | -composite ./images/cover.png
49 |
50 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/curry/curry_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 |
4 | console.log('add', map(add(2)));
5 |
6 |
7 | // Exercise 1
8 | //==============
9 |
10 | var words = split(' ');
11 |
12 | // Exercise 1a
13 | //==============
14 |
15 | var sentences = map(words);
16 |
17 |
18 | // Exercise 2
19 | //==============
20 |
21 | var filterQs = filter(match(/q/i));
22 |
23 |
24 | // Exercise 3
25 | //==============
26 | // Use the helper function _keepHighest to refactor max
27 |
28 | var _keepHighest = function(x,y){ return x >= y ? x : y; }; // <- leave be
29 |
30 | var max = reduce(_keepHighest, -Infinity);
31 |
32 |
33 | // Bonus 1:
34 | // ============
35 | // wrap array's slice to be functional and curried.
36 | // //[1,2,3].slice(0, 2)
37 | var slice = _.curry(function(start, end, xs){ return xs.slice(start, end); });
38 |
39 |
40 | // Bonus 2:
41 | // ============
42 | // use slice to define a function "take" that takes n elements. make it curried
43 | var take = slice(0);
44 |
45 |
46 | module.exports = { words: words,
47 | sentences: sentences,
48 | filterQs: filterQs,
49 | max: max,
50 | slice: slice,
51 | take: take
52 | };
53 |
--------------------------------------------------------------------------------
/.generate-summary.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl -n
2 | use Encode;
3 | use utf8;
4 | use open ':encoding(UTF-8)', ':std';
5 |
6 | # This stupid function makes to decode only the first file.
7 | # If I don't do this, perl is not doing it by himself and I don't know why.
8 | # Even the headers of this file are telling to use utf8 encoding!
9 | # But does not do it in the first file that reads.
10 | # And you do it in all the files it inverts to only be ok in the first file. PFFF
11 | # Two hours of research is enough!
12 | # At least... The function is pure!!
13 | sub decode_if_is_file {
14 | my ($first_file, $line) = @_;
15 | return ($ARGV eq $first_file) ? decode_utf8($line) : $line;
16 | }
17 |
18 | BEGIN {
19 | print "# Sumario\n\n";
20 | }
21 |
22 | # Trim whitespace
23 | s/^\s+|\s+$//g;
24 |
25 | # Print headlines
26 | if (/^# (.*)/) {
27 | $headline = decode_if_is_file("README.md", $1);
28 | print "* [$headline]($ARGV)\n";
29 | }
30 |
31 | # Print subheadlines
32 | if (/^## (.*)/) {
33 | $subheadline = decode_if_is_file("README.md", $1);
34 | my $anchor = lc $subheadline;
35 |
36 | # Remove all but word characters and whitespace
37 | $anchor =~ s/[^\wö ]//g;
38 | # Replace whitespace with dashes
39 | $anchor =~ tr/ /-/d;
40 |
41 | print " * [$subheadline]($ARGV#$anchor)\n";
42 | }
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mostly-adequate-guide-es",
3 | "version": "1.0.0",
4 | "description": "La Guía del Profesor Frisby en su Mayor Parte Adecuada para la Programación Funcional",
5 | "dependencies": {
6 | "gitbook-plugin-exercises": "git+https://github.com/MostlyAdequate/plugin-exercises.git",
7 | "gitbook-plugin-include-codeblock": "^3.2.3"
8 | },
9 | "devDependencies": {
10 | "gitbook-cli": "^2.3.2"
11 | },
12 | "scripts": {
13 | "setup": "$(npm bin)/gitbook install",
14 | "start": "$(npm bin)/gitbook serve",
15 | "generate-summary": "perl .generate-summary.pl README.md ch*-es.md appendix_*-es.md > SUMMARY-es.md",
16 | "generate-cover": "sh ./.generate-cover.sh",
17 | "generate-epub": "gitbook epub",
18 | "generate-pdf": "gitbook pdf"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/MostlyAdequate/mostly-adequate-guide-es.git"
23 | },
24 | "keywords": [
25 | "gitbook",
26 | "javascript",
27 | "guide",
28 | "functional programming"
29 | ],
30 | "author": "Mostly Adequate Core Team",
31 | "license": "CC BY-SA",
32 | "bugs": {
33 | "url": "https://github.com/MostlyAdequate/mostly-adequate-guide-es/issues"
34 | },
35 | "homepage": "https://github.com/MostlyAdequate/mostly-adequate-guide-es#readme"
36 | }
37 |
--------------------------------------------------------------------------------
/exercises/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mostly-adequate/exercises",
3 | "version": "1.0.0",
4 | "description": "Exercises coming with the Mostly Adequate Guide to Functional Programming",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/MostlyAdequate/mostly-adequate-guide"
9 | },
10 | "author": "@mostly-adequate",
11 | "bugs": {
12 | "url": "https://github.com/MostlyAdequate/mostly-adequate-guide/issues"
13 | },
14 | "homepage": "https://github.com/MostlyAdequate/mostly-adequate-guide",
15 | "dependencies": {},
16 | "devDependencies": {
17 | "cli": "^1.0.1",
18 | "eslint": "^5.9.0",
19 | "eslint-config-airbnb": "^16.1.0",
20 | "eslint-plugin-import": "^2.8.0",
21 | "eslint-plugin-jsx-a11y": "^6.0.2",
22 | "eslint-plugin-react": "^7.5.1",
23 | "live-server": "^1.2.1",
24 | "mocha": "^4.1.0"
25 | },
26 | "scripts": {
27 | "test": "mocha test/*.solutions.js",
28 | "lint": "eslint .",
29 | "ch04": "mocha test/ch04.js",
30 | "ch05": "mocha test/ch05.js",
31 | "ch06": "live-server --ignore='node_modules/*/**' ch06",
32 | "ch08": "mocha test/ch08.js",
33 | "ch09": "mocha test/ch09.js",
34 | "ch10": "mocha test/ch10.js",
35 | "ch11": "mocha test/ch11.js",
36 | "ch12": "mocha test/ch12.js"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/support/README-original.md:
--------------------------------------------------------------------------------
1 | # Mostly Adequate Guide to Functional Programming - Support
2 |
3 | ## Overview
4 |
5 | This package contains all functions and data-structure referenced in the
6 | appendixes of the [Mostly Adequate Guide to Functional Programming](https://github.com/MostlyAdequate/mostly-adequate-guide).
7 |
8 | These functions have an educational purpose and aren't intended to be used in
9 | any production environment. They are however, a good learning material for anyone
10 | interested in functional programming.
11 |
12 | ## How to install
13 |
14 | The package is available on `npm` and can be installed via the following incantation:
15 |
16 | ```
17 | npm install @mostly-adequate/support
18 | ```
19 |
20 | ## How to use
21 |
22 | There's no particular structure to the module, everything is flat and exported
23 | from the root (the curious reader may have a quick glance at the `index.js` to
24 | get convinced about this).
25 |
26 | Also, all top-level functions are curried so you don't have to worry about calling
27 | `curry` on any of them.
28 |
29 | For example:
30 |
31 | ```js
32 | const { Maybe, liftA2, append, concat, reverse } = require('@mostly-adequate/support');
33 |
34 | const a = Maybe.of("yltsoM").map(reverse);
35 | const b = Maybe.of("Adequate").map(concat(" "));
36 |
37 | liftA2(append)(b)(a);
38 | // Just("Mostly Adequate")
39 | ```
40 |
--------------------------------------------------------------------------------
/exercises/ch06/main.js:
--------------------------------------------------------------------------------
1 | const CDN = s => `https://cdnjs.cloudflare.com/ajax/libs/${s}`;
2 | const ramda = CDN('ramda/0.21.0/ramda.min');
3 | const jquery = CDN('jquery/3.0.0-rc1/jquery.min');
4 |
5 | requirejs.config({ paths: { ramda, jquery } });
6 | require(['jquery', 'ramda'], ($, { compose, curry, map, prop }) => {
7 | // -- Utils ----------------------------------------------------------
8 | const Impure = {
9 | trace: curry((tag, x) => { console.log(tag, x); return x; }), // eslint-disable-line no-console
10 | getJSON: curry((callback, url) => $.getJSON(url, callback)),
11 | setHtml: curry((sel, html) => $(sel).html(html)),
12 | };
13 |
14 | // -- Pure -----------------------------------------------------------
15 | const host = 'api.flickr.com';
16 | const path = '/services/feeds/photos_public.gne';
17 | const query = t => `?tags=${t}&format=json&jsoncallback=?`;
18 | const url = t => `https://${host}${path}${query(t)}`;
19 |
20 | const img = src => $('
', { src });
21 | const mediaUrl = compose(prop('m'), prop('media'));
22 | const mediaUrls = compose(map(mediaUrl), prop('items'));
23 | const images = compose(map(img), mediaUrls);
24 |
25 | // -- Impure ---------------------------------------------------------
26 | const render = compose(Impure.setHtml('#js-main'), images);
27 | const app = compose(Impure.getJSON(render), url);
28 |
29 | app('cats');
30 | });
31 |
--------------------------------------------------------------------------------
/support/README.md:
--------------------------------------------------------------------------------
1 | # La Más Que Adecuada Guía de Programación Funcional - Soporte
2 |
3 | ## Información General
4 |
5 | Este paquete contiene todas las funciones y estructuras de datos referenciadas en los
6 | apéndices de [La Guía del Profesor Frisby en su Mayor Parte Adecuada para la Programación Funcional](https://github.com/MostlyAdequate/mostly-adequate-guide-es).
7 |
8 | Estas funciones tienen un propósito pedagógico y no pretenden que sean utilizadas en
9 | ningún entorno en producción. Son sin embargo, un buen material de aprendizaje para
10 | cualquiera interesado en la programación funcional.
11 |
12 | ## Cómo instalarlo
13 |
14 | La versión en inglés del paquete está disponible en `npm` y puede instalarse mediante el siguiente conjuro:
15 |
16 | ```
17 | npm install @mostly-adequate/support
18 | ```
19 |
20 | ## Cómo utilizarlo
21 |
22 | El módulo no está estructurado de ninguna forma en particular, todo está plano y exportado
23 | desde la raíz (el lector curioso puede echar un vistazo rápido al `index.js` para quedar convencido).
24 |
25 | Además, todas la funciones del nivel superior, están currificadas para que no tengas que preocuparte de
26 | llamar a `curry` en ninguna de ellas.
27 |
28 | Por ejemplo:
29 |
30 | ```js
31 | const { Maybe, liftA2, append, concat, reverse } = require('@mostly-adequate/support');
32 |
33 | const a = Maybe.of("yltsoM").map(reverse);
34 | const b = Maybe.of("Adequate").map(concat(" "));
35 |
36 | liftA2(append)(b)(a);
37 | // Just("Mostly Adequate")
38 | ```
39 |
--------------------------------------------------------------------------------
/exercises/ch11/validation_b.js:
--------------------------------------------------------------------------------
1 | /* globals findNameById */
2 |
3 | const throwUnexpected = () => {
4 | throw new Error('The function gives incorrect results; a Task has resolved unexpectedly!');
5 | };
6 |
7 | const res = findNameById(1);
8 |
9 | assert(
10 | res instanceof Task,
11 | 'The function has an incorrect type; hint: `findNameById` must return a `Task String User`!',
12 | );
13 |
14 | res.fork(throwUnexpected, (val) => {
15 | assert(
16 | !(val instanceof Task),
17 | 'The function has an incorrect type; hint: `findNameById` must return a `Task String User`, make sure to flatten any nested Functor!',
18 | );
19 |
20 | assert(
21 | !(val instanceof Either),
22 | 'The function has an incorrect type; hint: did you use `eitherToTask` ?',
23 | );
24 |
25 | assert(
26 | val === 'Albert',
27 | 'The function gives incorrect results for the `Right` side of `Either`.',
28 | );
29 | });
30 |
31 |
32 | const rej = findNameById(999);
33 |
34 | assert(
35 | rej instanceof Task,
36 | 'The function has an incorrect type; hint: `findNameById` must return a `Task String User`!',
37 | );
38 |
39 | rej.fork((val) => {
40 | assert(
41 | !(val instanceof Task),
42 | 'The function has an incorrect type; hint: `findNameById` must return a `Task String User`, make sure to flatten any nested Functor!',
43 | );
44 |
45 | assert(
46 | val === 'not found',
47 | 'The function gives incorrect results for the `Left` side of `Either`.',
48 | );
49 | }, throwUnexpected);
50 |
--------------------------------------------------------------------------------
/code/part1_demo/flickr.js:
--------------------------------------------------------------------------------
1 | requirejs.config({
2 | paths: {
3 | ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min',
4 | jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min'
5 | }
6 | });
7 |
8 | require([
9 | 'ramda',
10 | 'jquery'
11 | ],
12 | function (_, $) {
13 | ////////////////////////////////////////////
14 | // Utils
15 | //
16 | var img = function (url) {
17 | return $('
', { src: url });
18 | };
19 |
20 | var Impure = {
21 | getJSON: _.curry(function(callback, url) {
22 | $.getJSON(url, callback)
23 | }),
24 |
25 | setHtml: _.curry(function(sel, html) {
26 | $(sel).html(html)
27 | })
28 | }
29 |
30 | var trace = _.curry(function(tag, x) {
31 | console.log(tag, x);
32 | return x;
33 | })
34 |
35 | ////////////////////////////////////////////
36 | // Pure
37 |
38 | // url :: String -> URL
39 | var url = function (t) {
40 | return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?';
41 | };
42 |
43 | var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
44 |
45 | var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
46 |
47 | var images = _.compose(_.map(img), srcs);
48 |
49 |
50 | ////////////////////////////////////////////
51 | // Impure
52 | //
53 | var renderImages = _.compose(Impure.setHtml("body"), images)
54 | var app = _.compose(Impure.getJSON(renderImages), url)
55 |
56 | app("cats")
57 | });
58 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/functors/functor_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./functor_exercises');
3 | var assert = require("chai").assert;
4 |
5 | describe("Functor Exercises", function(){
6 |
7 | it('Exercise 1', function(){
8 | assert.deepEqual(Identity.of(3), E.ex1(Identity.of(2)));
9 | });
10 |
11 | it('Exercise 2', function(){
12 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
13 | assert.deepEqual(Identity.of('do'), E.ex2(xs));
14 | });
15 |
16 | it('Exercise 3', function(){
17 | var user = { id: 2, name: "Albert" };
18 | assert.deepEqual(Maybe.of('A'), E.ex3(user));
19 | });
20 |
21 | it('Exercise 4', function(){
22 | assert.deepEqual(Maybe.of(4), E.ex4("4"));
23 | });
24 |
25 | it('Exercise 5', function(done){
26 | E.ex5(13).fork(console.log, function(res){
27 | assert.deepEqual('LOVE THEM FUTURES', res);
28 | done();
29 | })
30 | });
31 |
32 | it('Exercise 6', function(){
33 | assert.deepEqual(Left.of('Your account is not active'), E.ex6({active: false, name: 'Gary'}));
34 | assert.deepEqual(Right.of('Welcome Theresa'), E.ex6({active: true, name: 'Theresa'}));
35 | });
36 |
37 | it('Exercise 7', function(){
38 | assert.deepEqual(Right.of("fpguy99"), E.ex7("fpguy99"));
39 | assert.deepEqual(Left.of("You need > 3"), E.ex7("..."));
40 | });
41 |
42 | it('Exercise 8', function(){
43 | assert.deepEqual("fpguy99-saved", E.ex8("fpguy99").unsafePerformIO());
44 | assert.deepEqual("You need > 3", E.ex8("...").unsafePerformIO());
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/functors/functor_exercises_spec.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var E = require('./functor_exercises');
3 | var assert = require("chai").assert;
4 |
5 | describe("Functor Exercises", function(){
6 |
7 | it('Exercise 1', function(){
8 | assert.deepEqual(E.ex1(Identity.of(2)), Identity.of(3));
9 | });
10 |
11 | it('Exercise 2', function(){
12 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
13 | assert.deepEqual(E.ex2(xs), Identity.of('do'));
14 | });
15 |
16 | it('Exercise 3', function(){
17 | var user = { id: 2, name: "Albert" };
18 | assert.deepEqual(E.ex3(user), Maybe.of('A'));
19 | });
20 |
21 | it('Exercise 4', function(){
22 | assert.deepEqual(E.ex4("4"), Maybe.of(4));
23 | });
24 |
25 | it('Exercise 5', function(done){
26 | E.ex5(13).fork(console.log, function(res){
27 | assert.deepEqual(res, 'LOVE THEM FUTURES');
28 | done();
29 | })
30 | });
31 |
32 | it('Exercise 6', function(){
33 | assert.deepEqual(E.ex6({active: false, name: 'Gary'}), Left.of('Your account is not active'));
34 | assert.deepEqual(E.ex6({active: true, name: 'Theresa'}), Right.of('Welcome Theresa'));
35 | });
36 |
37 | it('Exercise 7', function(){
38 | assert.deepEqual(E.ex7("fpguy99"), Right.of("fpguy99"));
39 | assert.deepEqual(E.ex7("..."), Left.of("You need > 3"));
40 | });
41 |
42 | it('Exercise 8', function(){
43 | assert.deepEqual(E.ex8("fpguy99").unsafePerformIO(), "fpguy99-saved");
44 | assert.deepEqual(E.ex8("...").unsafePerformIO(), "You need > 3");
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/code/part1_exercises/exercises/curry/curry_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 |
4 |
5 | // Exercise 1
6 | //==============
7 | // Refactor to remove all arguments by partially applying the function
8 |
9 | var words = function(str) {
10 | return split(' ', str);
11 | };
12 |
13 | // Exercise 1a
14 | //==============
15 | // Use map to make a new words fn that works on an array of strings.
16 |
17 | var sentences = undefined;
18 |
19 |
20 | // Exercise 2
21 | //==============
22 | // Refactor to remove all arguments by partially applying the functions
23 |
24 | var filterQs = function(xs) {
25 | return filter(function(x){ return match(/q/i, x); }, xs);
26 | };
27 |
28 |
29 | // Exercise 3
30 | //==============
31 | // Use the helper function _keepHighest to refactor max to not reference any arguments
32 |
33 | // LEAVE BE:
34 | var _keepHighest = function(x,y){ return x >= y ? x : y; };
35 |
36 | // REFACTOR THIS ONE:
37 | var max = function(xs) {
38 | return reduce(function(acc, x){
39 | return _keepHighest(acc, x);
40 | }, 0, xs);
41 | };
42 |
43 |
44 | // Bonus 1:
45 | // ============
46 | // wrap array's slice to be functional and curried.
47 | // //[1,2,3].slice(0, 2)
48 | var slice = undefined;
49 |
50 |
51 | // Bonus 2:
52 | // ============
53 | // use slice to define a function "take" that takes n elements. Make it curried
54 | var take = undefined;
55 |
56 |
57 | module.exports = { words: words,
58 | sentences: sentences,
59 | filterQs: filterQs,
60 | max: max,
61 | slice: slice,
62 | take: take
63 | };
64 |
--------------------------------------------------------------------------------
/exercises/README-original.md:
--------------------------------------------------------------------------------
1 | # Mostly Adequate Exercises
2 |
3 | ## Overview
4 |
5 | All exercises from the book can be completed either:
6 |
7 | - in browser (using the version of the book published on [gitbook.io](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/))
8 | - in your editor & terminal, using `npm`
9 |
10 | In every folder named `ch**` from this `exercises/` folder, you'll find three types of files:
11 |
12 | - exercises
13 | - solutions
14 | - validations
15 |
16 | Exercises are structured with a statement in comment, followed by an incomplete
17 | or incorrect function. For example, `exercise_a` from `ch04` looks like this:
18 |
19 |
20 | ```js
21 | // Refactor to remove all arguments by partially applying the function.
22 |
23 | // words :: String -> [String]
24 | const words = str => split(' ', str);
25 | ```
26 |
27 | Following the statement, your goal is to refactor the given function `words`. Once done,
28 | your proposal can be verified by running:
29 |
30 | ```
31 | npm run ch04
32 | ```
33 |
34 | Alternatively, you can also have a peak at the corresponding solution file: in this case
35 | `solution_a.js`.
36 |
37 | > The files `validation_*.js` aren't really part of the exercises but are used
38 | > internally to verify your proposal and, offer hints when adequate. The curious
39 | > reader may have a look at them :).
40 |
41 | Now go and learn some functional programming λ!
42 |
43 | ## About the Appendixes
44 |
45 | Important notice: the exercise runner takes care of bringing all
46 | data-structures and functions from the appendixes into scope. Therefore, you
47 | may assume that any function present in the appendix is just available for you
48 | to use! Amazing, isn't it?
49 |
--------------------------------------------------------------------------------
/support/build-appendixes:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const { promisify } = require('util');
6 |
7 | /* Input / Output Configuration */
8 | const OUTPUT = path.join(__dirname, 'index.js');
9 | const INPUTDIR = path.join(__dirname, '..');
10 |
11 |
12 | /* Helpers */
13 | Function.prototype.compose = function (f) { return x => f(this(x)); }
14 |
15 | function readFileP(filename, inDir = INPUTDIR) {
16 | return promisify(fs.readFile)(path.join(inDir, filename));
17 | }
18 |
19 | function toString(x) {
20 | return x.toString();
21 | }
22 |
23 | function parseDefinitions(data) {
24 | const re = new RegExp('```js(.*)```', 'sm');
25 | const paragraphs = data.split(/###?#?/);
26 | return paragraphs
27 | .map(x => re.exec(x))
28 | .filter(x => x != null)
29 | .map(x => x[1]);
30 | }
31 |
32 | function flatten(xss) {
33 | return xss.reduce((acc, xs) => acc.concat(xs), []);
34 | }
35 |
36 | const header = `/* LICENSE MIT-2.0 - @MostlyAdequate */
37 | /* eslint-disable no-use-before-define, max-len, class-methods-use-this */
38 | `;
39 |
40 |
41 | /* Actual function */
42 | Promise
43 | .all([
44 | readFileP('appendix_a.md').then(toString.compose(parseDefinitions)),
45 | readFileP('appendix_b.md').then(toString.compose(parseDefinitions)),
46 | readFileP('appendix_c.md').then(toString.compose(parseDefinitions)),
47 | ])
48 | .then((definitions) => {
49 | definitions = flatten(definitions);
50 | const exports_ = definitions
51 | .map(x => x.match(/(const|class|function) ([a-zA-Z0-9]+)( |\()/)[2])
52 | .map(x => `exports.${x} = ${x};`);
53 | const output = header + definitions.join('\n') + '\n\n' + exports_.join('\n');
54 | return promisify(fs.writeFile)(OUTPUT, output);
55 | })
56 | .catch(console.error);
57 |
--------------------------------------------------------------------------------
/exercises/README.md:
--------------------------------------------------------------------------------
1 | # Los Ejercicios Más Adecuados
2 |
3 | ## Información General
4 |
5 | Todos los ejercicios del libro pueden completarse de dos maneras:
6 |
7 | - en el navegador (utilizando la versión del libro publicada en [gitbook.io](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/))
8 | - en tu editor y terminal, usando `npm`
9 |
10 | En cada carpeta llamada `ch**` de esta carpeta `exercises/`, encontrarás tres tipos de archivos:
11 |
12 | - exercises
13 | - solutions
14 | - validations
15 |
16 | Los ejercicios están estructurados con un enunciado en un comentario, seguido por una función incompleta o incorrecta. Por ejemplo, el `exercise_a` del `ch04` luce así:
17 |
18 |
19 | ```js
20 | // Refactor to remove all arguments by partially applying the function.
21 |
22 | // words :: String -> [String]
23 | const words = str => split(' ', str);
24 | ```
25 |
26 | Siguiendo el enunciado, tu objetivo es refactorizar la función `words` que se te proporciona. Una vez hecho,
27 | tu propuesta puede ser verificada ejecutando:
28 |
29 | ```
30 | npm run ch04
31 | ```
32 |
33 | Alternativamente, también puedes echar un vistazo al archivo con la solución que corresponda: en este caso
34 | `solution_a.js`.
35 |
36 | > Los archivos `validation_*.js` realmente no forman parte de los ejercicios, pero son utilizados
37 | > internamente para verificar tu propuesta y dar pistas cuando procede. El lector curioso
38 | > puede echarles un ojo :).
39 |
40 | ¡Ahora ve y aprende algo de programación funcional λ!
41 |
42 | ## Sobre Los Apéndices
43 |
44 | Aviso importante: el ejecutor de los ejercicios se encarga de traer al contexto
45 | de ejecución todas las estructuras de datos y funciones de los apéndices. Por esto,
46 | ¡puedes dar por hecho que cualquier función presente en los apéndices está
47 | disponible para que puedas utilizarla! Increíble, ¿verdad?
48 |
--------------------------------------------------------------------------------
/exercises/ch08/validation_d.js:
--------------------------------------------------------------------------------
1 | /* globals validateName, register */
2 |
3 | const validateGary = validateName(gary);
4 | assert(
5 | validateGary instanceof Either && validateGary.isRight,
6 | 'The function `validateName` gives incorrect results.',
7 | );
8 |
9 | const validateYi = validateName(yi);
10 | assert(
11 | validateYi instanceof Either && validateYi.isLeft && typeof validateYi.$value === 'string',
12 | 'The function `validateName` gives incorrect results!',
13 | );
14 |
15 | const registerAlbert = register(albert);
16 | assert(
17 | registerAlbert instanceof IO,
18 | 'The right outcome to `register` is incorrect; hint: `save` returns an `IO` and you\'ll need `map` to manipulate the inner value!',
19 | );
20 |
21 | const msgAlbert = registerAlbert.unsafePerformIO();
22 | assert(
23 | typeof msgAlbert === 'string',
24 | 'The right outcome to `register` is incorrect; hint: look carefully at your signatures, `register` should return an `IO(String)` in every scenarios!',
25 | );
26 |
27 | const callees = register.callees || [];
28 |
29 | assert(
30 | callees[callees.length - 1] === 'either',
31 | 'The function `register` seems incorrect; hint: you can use `either` to branch an `Either` to different outcomes!',
32 | );
33 |
34 | assert(
35 | msgAlbert === showWelcome(albert),
36 | 'The function `register` returns a correct type, but the inner value is incorrect! Did you use `showWelcome`?',
37 | );
38 |
39 | const registerYi = register(yi);
40 | assert(
41 | registerYi instanceof IO,
42 | 'The left outcome to `register` is incorrect; hint: look carefully at your signatures, `register` should return an `IO` in every scenarios!',
43 | );
44 |
45 | const msgYi = registerYi.unsafePerformIO();
46 | assert(
47 | typeof msgYi === 'string',
48 | 'The left outcome to `register` is incorrect; hint: look carefully at your signatures, `register` should return an `IO(String)` in every scenarios!',
49 | );
50 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/applicative/applicative_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // fib browser for test
6 | var localStorage = {};
7 |
8 |
9 |
10 | // Exercise 1
11 | // ==========
12 | // Write a function that add's two possibly null numbers together using Maybe and ap()
13 |
14 | // ex1 :: Number -> Number -> Maybe Number
15 | var ex1 = function(x, y) {
16 | // write me
17 | };
18 |
19 |
20 | // Exercise 2
21 | // ==========
22 | // Now write a function that takes 2 Maybe's and adds them. Use liftA2 instead of ap().
23 |
24 | // ex2 :: Maybe Number -> Maybe Number -> Maybe Number
25 | var ex2 = undefined;
26 |
27 |
28 |
29 | // Exercise 3
30 | // ==========
31 | // Run both getPost(n) and getComments(n) then render the page with both. (the n arg is arbitrary)
32 | var makeComments = _.reduce(function(acc, c){ return acc+""+c+"" }, "");
33 | var render = _.curry(function(p, cs) { return ""+p.title+"
"+makeComments(cs); });
34 |
35 | // ex3 :: Task Error HTML
36 | var ex3 = undefined;
37 |
38 |
39 |
40 | // Exercise 4
41 | // ==========
42 | // Write an IO that gets both player1 and player2 from the cache and starts the game
43 | localStorage.player1 = "toby";
44 | localStorage.player2 = "sally";
45 |
46 | var getCache = function(x) {
47 | return new IO(function() { return localStorage[x]; });
48 | }
49 | var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; });
50 |
51 | // ex4 :: IO String
52 | var ex4 = undefined;
53 |
54 |
55 |
56 |
57 |
58 | // TEST HELPERS
59 | // =====================
60 |
61 | function getPost(i) {
62 | return new Task(function (rej, res) {
63 | setTimeout(function () { res({ id: i, title: 'Love them tasks' }); }, 300);
64 | });
65 | }
66 |
67 | function getComments(i) {
68 | return new Task(function (rej, res) {
69 | setTimeout(function () {
70 | res(["This book should be illegal", "Monads are like space burritos"]);
71 | }, 300);
72 | });
73 | }
74 |
75 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4}
76 |
--------------------------------------------------------------------------------
/code/part2_exercises/answers/applicative/applicative_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // fib browser for test
6 | var localStorage = {};
7 |
8 |
9 |
10 | // Exercise 1
11 | // ==========
12 | // Write a function that add's two possibly null numbers together using Maybe and ap()
13 |
14 | var ex1 = function(x, y) {
15 | return Maybe.of(_.add).ap(Maybe.of(x)).ap(Maybe.of(y));
16 | };
17 |
18 |
19 | // Exercise 2
20 | // ==========
21 | // Rewrite 1 to use liftA2 instead of ap()
22 |
23 | var ex2 = liftA2(_.add);
24 |
25 |
26 |
27 | // Exercise 3
28 | // ==========
29 | // Run both getPost(n) and getComments(n) then render the page with both. (the n arg is arbitrary)
30 | var makeComments = _.reduce(function(acc, c){ return acc+""+c+"" }, "");
31 | var render = _.curry(function(p, cs) { return ""+p.title+"
"+makeComments(cs); });
32 |
33 |
34 | var ex3 = Task.of(render).ap(getPost(2)).ap(getComments(2));
35 | // or
36 | // var ex3 = liftA2(render, getPost(2), getComments(2))
37 |
38 |
39 |
40 |
41 | // Exercise 4
42 | // ==========
43 | // Write an IO that gets both player1 and player2 from the cache and starts the game
44 | localStorage.player1 = "toby";
45 | localStorage.player2 = "sally";
46 |
47 | var getCache = function(x) {
48 | return new IO(function() { return localStorage[x]; });
49 | }
50 | var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; });
51 |
52 | var ex4 = liftA2(game, getCache('player1'), getCache('player2'));
53 |
54 |
55 |
56 |
57 |
58 | // TEST HELPERS
59 | // =====================
60 |
61 | function getPost(i) {
62 | return new Task(function (rej, res) {
63 | setTimeout(function () { res({ id: i, title: 'Love them tasks' }); }, 300);
64 | });
65 | }
66 |
67 | function getComments(i) {
68 | return new Task(function (rej, res) {
69 | setTimeout(function () {
70 | res(["This book should be illegal", "Monads are like space burritos"]);
71 | }, 300);
72 | });
73 | }
74 |
75 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4}
76 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributing to Mostly Adequate Guide to Functional Programming
3 |
4 | ## Licensing
5 |
6 | By opening a pull request to this repository, you agree to provide your work under the [project license](LICENSE). Also, you agree to grant such license of your work as is required for the purposes of future print editions to @DrBoolean. Should your changes appear in a printed edition, you'll be included in the contributors list.
7 |
8 | ## Small Corrections
9 |
10 | Errata and basic clarifications will be accepted if we agree that they improve the content. You can also open an issue so we can figure out how or if it needs to be addressed. If you've never done this before, the [flow guide](https://guides.github.com/introduction/flow/) might be useful.
11 |
12 | ## Questions or Clarifications
13 |
14 | Please, have a look at the [FAQ](FAQ.md) before you open an issue. Your question may already
15 | have been answered. Should you still need to ask something? Feel free to open an issue and to
16 | explain yourself.
17 |
18 | ## Translations
19 |
20 | Translations to other languages are highly encouraged. Each official translation will be held as a separate repository in the [MostlyAdequate organization](https://github.com/MostlyAdequate) and linked from the English version book.
21 | Since each translation is a different repository, we can also have different maintainers for each project.
22 |
23 | ### Creating a New Translation Repo
24 |
25 | In order to create a new translation, you need to follow these steps:
26 |
27 | * Fork the [main repo](https://github.com/MostlyAdequate/mostly-adequate-guide).
28 | * Add yourself to the watch list of the main repo, to keep up with changes.
29 | * When translating chapters, **create NEW files with suffix of your language**.
30 | * For example, Spanish tranlation for `ch01.md` will be on `ch01-es.md`.
31 | * Open a [new issue](https://github.com/MostlyAdequate/mostly-adequate-guide/issues/new) and ask to be part of the organization.
32 | * Transfer the repo to the organization.
33 | * Merge new content from the main repo.
34 | * keep translating...
35 | * Rinse/repeat last two steps until the book is done.
36 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | env:
9 | BOOK_FILENAME: la-guia-del-profesor-frisby-en-su-mayor-parte-adecuada-para-la-programacion-funcional
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-22.04
14 | steps:
15 | - name: Checkout source code
16 | uses: actions/checkout@v3
17 |
18 | - name: Install Calibre
19 | run: |
20 | sudo apt install -y libopengl0 libegl1 libxcb-cursor0
21 | wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin
22 | mkdir -p ~/.local/bin
23 | ln -s /opt/calibre/calibre ~/.local/bin/calibre
24 | ln -s /opt/calibre/ebook-convert ~/.local/bin/ebook-convert
25 |
26 | - name: Setup Node.js
27 | uses: actions/setup-node@v1
28 | with:
29 | node-version: 10.22.1
30 |
31 | - name: Put title on cover
32 | run: |
33 | sudo apt install -y xfonts-scalable
34 | npm run generate-cover
35 |
36 | - name: Generate summary
37 | run: |
38 | npm run generate-summary
39 |
40 | - name: Setup gitbook
41 | run: |
42 | npm ci
43 | npm run setup
44 |
45 | - name: Generate PDF
46 | run: |
47 | npm run generate-pdf
48 | mv book.pdf ${BOOK_FILENAME}.pdf
49 |
50 | - name: Generate EPUB
51 | run: |
52 | npm run generate-epub
53 | mv book.epub ${BOOK_FILENAME}.epub
54 |
55 | - uses: actions/upload-artifact@v2
56 | with:
57 | name: PDF
58 | path: ${{ env.BOOK_FILENAME }}.pdf
59 |
60 | - uses: actions/upload-artifact@v2
61 | with:
62 | name: EPUB
63 | path: ${{ env.BOOK_FILENAME }}.epub
64 |
65 | - run: echo "ID=$(git describe --tags --always)" >> $GITHUB_OUTPUT
66 | id: release-id
67 |
68 | - uses: softprops/action-gh-release@v1
69 | with:
70 | tag_name: ${{ steps.release-id.outputs.ID }}
71 | files: |
72 | ${{ env.BOOK_FILENAME }}.pdf
73 | ${{ env.BOOK_FILENAME }}.epub
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING-es.md:
--------------------------------------------------------------------------------
1 | # Contribuyendo a La Más Que Adecuada Guía de Programación Funcional
2 |
3 | ## Concesión de Licencias
4 |
5 | Al abrir un `pull request` a este repositorio, estás de acuerdo en proveer tu trabajo bajo la [licencia del proyecto](LICENSE). Además, estás de acuerdo en ceder tal licencia de tu trabajo como sea requerida para las futuras ediciones impresas a @DrBoolean. Si tus cambios aparecen en una edición impresa, serás incluido en la lista de contribuidores.
6 |
7 | ## Pequeñas Correcciones
8 |
9 | Las erratas y aclaraciones simples serán aceptadas si consideramos que mejoran el contenido. También puedes abrir un `issue`, así podemos considerar si necesita ser atendido. Si nunca has hecho esto antes, la [guía de flow](https://guides.github.com/introduction/flow/) puede ser útil.
10 |
11 | ## Preguntas o Aclaraciones
12 |
13 | Por favor, dale un vistazo a [FAQ](FAQ-es.md) antes de abrir un issue. Tu pregunta puede haber sido respondida con anterioridad. ¿Sigues teniendo que preguntar algo? Siéntete libre de abrir un issue explicarte tú mismo.
14 |
15 | ## Traducciones
16 |
17 | Se anima insistentemente a realizar traducciones a otros lenguajes. Cada traducción oficial será alojada en un repositorio separado en la [Organización MostlyAdequate](https://github.com/MostlyAdequate) y enlazada desde la versión inglesa.
18 | Ya que cada traducción es un repositorio diferente, podremos tener diferentes administradores para cada proyecto.
19 |
20 | ### Creando Nuevos Repositorios de Traducciones
21 |
22 | Con el fin de crear una nueva traducción, necesitas seguir estos pasos:
23 |
24 | * Haz un fork del [repositorio principal](https://github.com/MostlyAdequate/mostly-adequate-guide).
25 | * Agrégate a ti mismo a la lista de observadores(watch list) del repositorio principal, para mantenerte informado de los cambios.
26 | * Cuando traduzcas un capítulo, **crea un NUEVO fichero con el sufijo de tu lenguaje**.
27 | * Por ejemplo, la traducción al español del `ch01.md` deberá estar en `ch01-es.md`.
28 | * Abre un [nuevo issue](https://github.com/MostlyAdequate/mostly-adequate-guide/issues/new) y pide ser parte de la organización.
29 |
30 | * Transfiere el repo a la organización.
31 | * Mergea el contenido del repositorio principal.
32 | * continúa traduciendo...
33 | * Repite los últimos dos pasos hasta que el libro esté acabado.
34 |
--------------------------------------------------------------------------------
/code/part1_exercises/support.js:
--------------------------------------------------------------------------------
1 | //var curry = require('ramda').curry;
2 | //
3 | //
4 | //
5 | //
6 | function inspect(x) {
7 | return (typeof x === 'function') ? inspectFn(x) : inspectArgs(x);
8 | }
9 |
10 | function inspectFn(f) {
11 | return (f.name) ? f.name : f.toString();
12 | }
13 |
14 | function inspectArgs(args) {
15 | return args.reduce(function(acc, x){
16 | return acc += inspect(x);
17 | }, '(') + ')';
18 | }
19 |
20 | function curry(fx) {
21 | var arity = fx.length;
22 |
23 | return function f1() {
24 | var args = Array.prototype.slice.call(arguments, 0);
25 | if (args.length >= arity) {
26 | return fx.apply(null, args);
27 | }
28 | else {
29 | var f2 = function f2() {
30 | var args2 = Array.prototype.slice.call(arguments, 0);
31 | return f1.apply(null, args.concat(args2));
32 | }
33 | f2.toString = function() {
34 | return inspectFn(fx) + inspectArgs(args);
35 | }
36 | return f2;
37 | }
38 | };
39 | }
40 |
41 | compose = function() {
42 | var fns = toArray(arguments),
43 | arglen = fns.length;
44 |
45 | return function(){
46 | for(var i=arglen;--i>=0;) {
47 | var fn = fns[i]
48 | , args = fn.length ? Array.prototype.slice.call(arguments, 0, fn.length) : arguments
49 | , next_args = Array.prototype.slice.call(arguments, (fn.length || 1)); //not right with *args
50 | next_args.unshift(fn.apply(this,args));
51 | arguments = next_args;
52 | }
53 | return arguments[0];
54 | }
55 | }
56 |
57 | add = curry(function(x, y) {
58 | return x + y;
59 | });
60 |
61 | match = curry(function(what, x) {
62 | return x.match(what);
63 | });
64 |
65 | replace = curry(function(what, replacement, x) {
66 | return x.replace(what, replacement);
67 | });
68 |
69 | filter = curry(function(f, xs) {
70 | return xs.filter(f);
71 | });
72 |
73 | map = curry(function map(f, xs) {
74 | return xs.map(f);
75 | });
76 |
77 | reduce = curry(function(f, a, xs) {
78 | return xs.reduce(f, a);
79 | });
80 |
81 | split = curry(function(what, x) {
82 | return x.split(what);
83 | });
84 |
85 | join = curry(function(what, x) {
86 | return x.join(what);
87 | });
88 |
89 | toUpperCase = function(x) {
90 | return x.toUpperCase()
91 | };
92 |
93 | toLowerCase = function(x) {
94 | return x.toLowerCase()
95 | };
96 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/monads/monad_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // Exercise 1
6 | // ==========
7 | // Use safeProp and map/join or chain to safely get the street name when given a user
8 |
9 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
10 | var user = {
11 | id: 2,
12 | name: "albert",
13 | address: {
14 | street: {
15 | number: 22,
16 | name: 'Walnut St'
17 | }
18 | }
19 | };
20 |
21 | var ex1 = undefined;
22 |
23 |
24 | // Exercise 2
25 | // ==========
26 | // Use getFile to get the filename, remove the directory so it's just the file, then purely log it.
27 |
28 | var getFile = function() {
29 | return new IO(function(){ return __filename; });
30 | }
31 |
32 | var pureLog = function(x) {
33 | return new IO(function(){
34 | console.log(x);
35 | return 'logged ' + x; // for testing w/o mocks
36 | });
37 | }
38 |
39 | var ex2 = undefined;
40 |
41 |
42 |
43 | // Exercise 3
44 | // ==========
45 | // Use getPost() then pass the post's id to getComments().
46 |
47 | var getPost = function(i) {
48 | return new Task(function (rej, res) {
49 | setTimeout(function () {
50 | res({ id: i, title: 'Love them tasks' }); // THE POST
51 | }, 300);
52 | });
53 | }
54 |
55 | var getComments = function(i) {
56 | return new Task(function (rej, res) {
57 | setTimeout(function () {
58 | res([{post_id: i, body: "This book should be illegal"}, {post_id: i, body:"Monads are like smelly shallots"}]);
59 | }, 300);
60 | });
61 | }
62 |
63 | var ex3 = undefined;
64 |
65 |
66 | // Exercise 4
67 | // ==========
68 | // Use validateEmail, addToMailingList and emailBlast to implement ex4's type signature.
69 | // It should safely add a new subscriber to the list, then email everyone with this happy news.
70 |
71 | // addToMailingList :: Email -> IO [Email]
72 | var addToMailingList = (function(list){
73 | return function(email) {
74 | return new IO(function(){
75 | list.push(email);
76 | return list;
77 | });
78 | }
79 | })([]);
80 |
81 | // emailBlast :: [Email] -> IO String
82 | function emailBlast(list) {
83 | return new IO(function(){
84 | return 'emailed: ' + list.join(','); // for testing w/o mocks
85 | });
86 | }
87 |
88 | // validateEmail :: Email -> Either String Email
89 | var validateEmail = function(x){
90 | return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
91 | }
92 |
93 | // ex4 :: Email -> Either String (IO String)
94 | var ex4 = undefined;
95 |
96 |
97 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, user: user}
98 |
--------------------------------------------------------------------------------
/code/part1_exercises/answers/compose/compose_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var _ = require('ramda');
3 | var accounting = require('accounting');
4 |
5 | // Example Data
6 | var CARS = [
7 | {name: "Ferrari FF", horsepower: 660, dollar_value: 700000, in_stock: true},
8 | {name: "Spyker C12 Zagato", horsepower: 650, dollar_value: 648000, in_stock: false},
9 | {name: "Jaguar XKR-S", horsepower: 550, dollar_value: 132000, in_stock: false},
10 | {name: "Audi R8", horsepower: 525, dollar_value: 114200, in_stock: false},
11 | {name: "Aston Martin One-77", horsepower: 750, dollar_value: 1850000, in_stock: true},
12 | {name: "Pagani Huayra", horsepower: 700, dollar_value: 1300000, in_stock: false}
13 | ];
14 |
15 | // Exercise 1:
16 | // ============
17 | var isLastInStock = _.compose(_.prop('in_stock'), _.last);
18 |
19 | // Exercise 2:
20 | // ============
21 | var nameOfFirstCar = _.compose(_.prop('name'), _.head);
22 |
23 |
24 | // Exercise 3:
25 | // ============
26 | // Use the helper function _average to refactor averageDollarValue as a composition
27 |
28 | var _average = function(xs) { return reduce(add, 0, xs) / xs.length; }; // <- leave be
29 |
30 | var averageDollarValue = _.compose(_average, _.map(_.prop('dollar_value')));
31 |
32 |
33 | // Exercise 4:
34 | // ============
35 | // Write a function: sanitizeNames() using compose that returns a list of lowercase and underscored names: e.g: sanitizeNames(["Hello World"]) //=> ["hello_world"].
36 |
37 | var _underscore = replace(/\W+/g, '_'); //<-- leave this alone and use to sanitize
38 |
39 | var sanitizeNames = _.map(_.compose(_underscore, toLowerCase, _.prop('name')));
40 |
41 |
42 | // Bonus 1:
43 | // ============
44 | // Refactor availablePrices with compose.
45 |
46 | var formatPrice = _.compose(accounting.formatMoney, _.prop('dollar_value'));
47 | var availablePrices = _.compose(join(', '), _.map(formatPrice), _.filter(_.prop('in_stock')));
48 |
49 | // Bonus 2:
50 | // ============
51 | // Refactor to pointfree. Hint: you can use _.flip()
52 |
53 | //+ fastestCar :: [Car] -> String
54 | var append = _.flip(_.concat);
55 | var fastestCar = _.compose(append(' is the fastest'),
56 | _.prop('name'),
57 | _.last,
58 | _.sortBy(_.prop('horsepower')));
59 |
60 | module.exports = { CARS: CARS,
61 | isLastInStock: isLastInStock,
62 | nameOfFirstCar: nameOfFirstCar,
63 | fastestCar: fastestCar,
64 | averageDollarValue: averageDollarValue,
65 | availablePrices: availablePrices,
66 | sanitizeNames: sanitizeNames
67 | };
68 |
--------------------------------------------------------------------------------
/code/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 safely get the street name when given a user
8 |
9 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
10 | var user = {
11 | id: 2,
12 | name: "albert",
13 | address: {
14 | street: {
15 | number: 22,
16 | name: 'Walnut St'
17 | }
18 | }
19 | };
20 |
21 | var ex1 = _.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 implement 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 |
--------------------------------------------------------------------------------
/code/part2_exercises/exercises/functors/functor_exercises.js:
--------------------------------------------------------------------------------
1 | require('../../support');
2 | var Task = require('data.task');
3 | var _ = require('ramda');
4 |
5 | // Exercise 1
6 | // ==========
7 | // Use _.add(x,y) and _.map(f,x) to make a function that increments a value inside a functor
8 |
9 | var ex1 = undefined;
10 |
11 |
12 |
13 | // Exercise 2
14 | // ==========
15 | // Use _.head to get the first element of the list
16 | var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
17 |
18 | var ex2 = undefined;
19 |
20 |
21 |
22 | // Exercise 3
23 | // ==========
24 | // Use safeProp and _.head to find the first initial of the user
25 | var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });
26 |
27 | var user = { id: 2, name: "Albert" };
28 |
29 | var ex3 = undefined;
30 |
31 |
32 |
33 | // Exercise 4
34 | // ==========
35 | // Use Maybe to rewrite ex4 without an if statement
36 |
37 | var ex4 = function (n) {
38 | if (n) { return parseInt(n); }
39 | };
40 |
41 | var ex4 = undefined;
42 |
43 |
44 |
45 | // Exercise 5
46 | // ==========
47 | // Write a function that will getPost then _.toUpper the post's title
48 |
49 | // getPost :: Int -> Future({id: Int, title: String})
50 | var getPost = function (i) {
51 | return new Task(function(rej, res) {
52 | setTimeout(function(){
53 | res({id: i, title: 'Love them futures'})
54 | }, 300)
55 | });
56 | };
57 |
58 | var ex5 = undefined;
59 |
60 |
61 |
62 | // Exercise 6
63 | // ==========
64 | // Write a function that uses checkActive() and showWelcome() to grant access or return the error
65 |
66 | var showWelcome = _.compose(_.add( "Welcome "), _.prop('name'));
67 |
68 | var checkActive = function(user) {
69 | return user.active ? Right.of(user) : Left.of('Your account is not active')
70 | };
71 |
72 | var ex6 = undefined;
73 |
74 |
75 |
76 | // Exercise 7
77 | // ==========
78 | // Write a validation function that checks for a length > 3. It should return Right(x) if it is greater than 3 and Left("You need > 3") otherwise
79 |
80 | var ex7 = function(x) {
81 | return undefined; // <--- write me. (don't be pointfree)
82 | };
83 |
84 |
85 |
86 | // Exercise 8
87 | // ==========
88 | // Use ex7 above and Either as a functor to save the user if they are valid or return the error message string. Remember either's two arguments must return the same type.
89 |
90 | var save = function(x) {
91 | return new IO(function() {
92 | console.log("SAVED USER!");
93 | return x + '-saved';
94 | });
95 | };
96 |
97 | var ex8 = undefined;
98 |
99 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7, ex8: ex8};
100 |
--------------------------------------------------------------------------------
/code/part2_exercises/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 |
84 |
85 | // Exercise 8
86 | // ==========
87 | // Use ex7 above and Either as a functor to save the user if they are valid or return the error message string. Remember either's two arguments must return the same type.
88 |
89 | var save = function(x){
90 | return new IO(function(){
91 | console.log("SAVED USER!");
92 | return x + '-saved';
93 | });
94 | }
95 |
96 | var ex8 = _.compose(either(IO.of, save), ex7)
97 |
98 | module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7, ex8: ex8}
99 |
--------------------------------------------------------------------------------
/code/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 |
--------------------------------------------------------------------------------
/appendix_a.md:
--------------------------------------------------------------------------------
1 | # Appendix A: Essential Functions Support
2 |
3 | In this appendix, you'll find some basic JavaScript implementations of various functions
4 | described in the book. Keep in mind that these implementations may not be the fastest or the
5 | most efficient implementation out there; they *solely serve an educational purpose*.
6 |
7 | In order to find functions that are more production-ready, have a peek at
8 | [ramda](https://ramdajs.com/), [lodash](https://lodash.com/), or [folktale](http://folktale.origamitower.com/).
9 |
10 | Note that some functions also refer to algebraic structures defined in the [Appendix B](./appendix_b.md)
11 |
12 | ## always
13 |
14 | ```js
15 | // always :: a -> b -> a
16 | const always = curry((a, b) => a);
17 | ```
18 |
19 |
20 | ## compose
21 |
22 | ```js
23 | // compose :: ((y -> z), (x -> y), ..., (a -> b)) -> a -> z
24 | const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];
25 | ```
26 |
27 |
28 | ## curry
29 |
30 | ```js
31 | // curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c
32 | function curry(fn) {
33 | const arity = fn.length;
34 |
35 | return function $curry(...args) {
36 | if (args.length < arity) {
37 | return $curry.bind(null, ...args);
38 | }
39 |
40 | return fn.call(null, ...args);
41 | };
42 | }
43 | ```
44 |
45 |
46 | ## either
47 |
48 | ```js
49 | // either :: (a -> c) -> (b -> c) -> Either a b -> c
50 | const either = curry((f, g, e) => {
51 | if (e.isLeft) {
52 | return f(e.$value);
53 | }
54 |
55 | return g(e.$value);
56 | });
57 | ```
58 |
59 |
60 | ## identity
61 |
62 | ```js
63 | // identity :: x -> x
64 | const identity = x => x;
65 | ```
66 |
67 |
68 | ## inspect
69 |
70 | ```js
71 | // inspect :: a -> String
72 | const inspect = (x) => {
73 | if (x && typeof x.inspect === 'function') {
74 | return x.inspect();
75 | }
76 |
77 | function inspectFn(f) {
78 | return f.name ? f.name : f.toString();
79 | }
80 |
81 | function inspectTerm(t) {
82 | switch (typeof t) {
83 | case 'string':
84 | return `'${t}'`;
85 | case 'object': {
86 | const ts = Object.keys(t).map(k => [k, inspect(t[k])]);
87 | return `{${ts.map(kv => kv.join(': ')).join(', ')}}`;
88 | }
89 | default:
90 | return String(t);
91 | }
92 | }
93 |
94 | function inspectArgs(args) {
95 | return Array.isArray(args) ? `[${args.map(inspect).join(', ')}]` : inspectTerm(args);
96 | }
97 |
98 | return (typeof x === 'function') ? inspectFn(x) : inspectArgs(x);
99 | };
100 | ```
101 |
102 |
103 | ## left
104 |
105 | ```js
106 | // left :: a -> Either a b
107 | const left = a => new Left(a);
108 | ```
109 |
110 |
111 | ## liftA2
112 |
113 | ```js
114 | // liftA2 :: (Applicative f) => (a1 -> a2 -> b) -> f a1 -> f a2 -> f b
115 | const liftA2 = curry((fn, a1, a2) => a1.map(fn).ap(a2));
116 | ```
117 |
118 |
119 | ## liftA3
120 |
121 | ```js
122 | // liftA3 :: (Applicative f) => (a1 -> a2 -> a3 -> b) -> f a1 -> f a2 -> f a3 -> f b
123 | const liftA3 = curry((fn, a1, a2, a3) => a1.map(fn).ap(a2).ap(a3));
124 | ```
125 |
126 |
127 | ## maybe
128 |
129 | ```js
130 | // maybe :: b -> (a -> b) -> Maybe a -> b
131 | const maybe = curry((v, f, m) => {
132 | if (m.isNothing) {
133 | return v;
134 | }
135 |
136 | return f(m.$value);
137 | });
138 | ```
139 |
140 |
141 | ## nothing
142 |
143 | ```js
144 | // nothing :: Maybe a
145 | const nothing = Maybe.of(null);
146 | ```
147 |
148 |
149 | ## reject
150 |
151 | ```js
152 | // reject :: a -> Task a b
153 | const reject = a => Task.rejected(a);
154 | ```
155 |
--------------------------------------------------------------------------------
/appendix_a-es.md:
--------------------------------------------------------------------------------
1 | # Apéndice A: Funciones Esenciales de Soporte
2 |
3 | En este apéndice, encontrarás algunas implementaciones básicas en Javascript de varias funciones
4 | descritas en el libro. Ten en cuenta que estas implementaciones pueden no ser las más rápidas o las
5 | más eficientes; *tienen únicamente una finalidad educativa*.
6 |
7 | Para encontrar funciones más preparadas para un entorno en producción, échale un vistazo a
8 | [ramda](https://ramdajs.com/), [lodash](https://lodash.com/), o [folktale](http://folktale.origamitower.com/).
9 |
10 | Ten en cuenta que algunas funciones también hacen referencia a estructuras algebraicas definidas en el [Apéndice B](./appendix_b-es.md)
11 |
12 | ## always
13 |
14 | ```js
15 | // always :: a -> b -> a
16 | const always = curry((a, b) => a);
17 | ```
18 |
19 |
20 | ## compose
21 |
22 | ```js
23 | // compose :: ((y -> z), (x -> y), ..., (a -> b)) -> a -> z
24 | const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];
25 | ```
26 |
27 |
28 | ## curry
29 |
30 | ```js
31 | // curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c
32 | function curry(fn) {
33 | const arity = fn.length;
34 |
35 | return function $curry(...args) {
36 | if (args.length < arity) {
37 | return $curry.bind(null, ...args);
38 | }
39 |
40 | return fn.call(null, ...args);
41 | };
42 | }
43 | ```
44 |
45 |
46 | ## either
47 |
48 | ```js
49 | // either :: (a -> c) -> (b -> c) -> Either a b -> c
50 | const either = curry((f, g, e) => {
51 | if (e.isLeft) {
52 | return f(e.$value);
53 | }
54 |
55 | return g(e.$value);
56 | });
57 | ```
58 |
59 |
60 | ## identity
61 |
62 | ```js
63 | // identity :: x -> x
64 | const identity = x => x;
65 | ```
66 |
67 |
68 | ## inspect
69 |
70 | ```js
71 | // inspect :: a -> String
72 | const inspect = (x) => {
73 | if (x && typeof x.inspect === 'function') {
74 | return x.inspect();
75 | }
76 |
77 | function inspectFn(f) {
78 | return f.name ? f.name : f.toString();
79 | }
80 |
81 | function inspectTerm(t) {
82 | switch (typeof t) {
83 | case 'string':
84 | return `'${t}'`;
85 | case 'object': {
86 | const ts = Object.keys(t).map(k => [k, inspect(t[k])]);
87 | return `{${ts.map(kv => kv.join(': ')).join(', ')}}`;
88 | }
89 | default:
90 | return String(t);
91 | }
92 | }
93 |
94 | function inspectArgs(args) {
95 | return Array.isArray(args) ? `[${args.map(inspect).join(', ')}]` : inspectTerm(args);
96 | }
97 |
98 | return (typeof x === 'function') ? inspectFn(x) : inspectArgs(x);
99 | };
100 | ```
101 |
102 |
103 | ## left
104 |
105 | ```js
106 | // left :: a -> Either a b
107 | const left = a => new Left(a);
108 | ```
109 |
110 |
111 | ## liftA2
112 |
113 | ```js
114 | // liftA2 :: (Applicative f) => (a1 -> a2 -> b) -> f a1 -> f a2 -> f b
115 | const liftA2 = curry((fn, a1, a2) => a1.map(fn).ap(a2));
116 | ```
117 |
118 |
119 | ## liftA3
120 |
121 | ```js
122 | // liftA3 :: (Applicative f) => (a1 -> a2 -> a3 -> b) -> f a1 -> f a2 -> f a3 -> f b
123 | const liftA3 = curry((fn, a1, a2, a3) => a1.map(fn).ap(a2).ap(a3));
124 | ```
125 |
126 |
127 | ## maybe
128 |
129 | ```js
130 | // maybe :: b -> (a -> b) -> Maybe a -> b
131 | const maybe = curry((v, f, m) => {
132 | if (m.isNothing) {
133 | return v;
134 | }
135 |
136 | return f(m.$value);
137 | });
138 | ```
139 |
140 |
141 | ## nothing
142 |
143 | ```js
144 | // nothing :: Maybe a
145 | const nothing = Maybe.of(null);
146 | ```
147 |
148 |
149 | ## reject
150 |
151 | ```js
152 | // reject :: a -> Task a b
153 | const reject = a => Task.rejected(a);
154 | ```
155 |
--------------------------------------------------------------------------------
/code/part2_exercises/support.js:
--------------------------------------------------------------------------------
1 | require('../part1_exercises/support');
2 | var _ = require('ramda');
3 | var Task = require('data.task');
4 | var curry = _.curry;
5 |
6 | inspect = function(x) {
7 | return (x && x.inspect) ? x.inspect() : x;
8 | };
9 |
10 | toUpperCase = function(x) {
11 | return x.toUpperCase();
12 | };
13 |
14 | // Identity
15 | Identity = function(x) {
16 | this.__value = x;
17 | };
18 |
19 | Identity.of = function(x) { return new Identity(x); };
20 |
21 | Identity.prototype.map = function(f) {
22 | return Identity.of(f(this.__value));
23 | };
24 |
25 | Identity.prototype.inspect = function() {
26 | return 'Identity('+inspect(this.__value)+')';
27 | };
28 |
29 | // Maybe
30 | Maybe = function(x) {
31 | this.__value = x;
32 | };
33 |
34 | Maybe.of = function(x) {
35 | return new Maybe(x);
36 | };
37 |
38 | Maybe.prototype.isNothing = function(f) {
39 | return (this.__value === null || this.__value === undefined);
40 | };
41 |
42 | Maybe.prototype.map = function(f) {
43 | return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
44 | };
45 |
46 | Maybe.prototype.chain = function(f) {
47 | return this.map(f).join();
48 | };
49 |
50 | Maybe.prototype.ap = function(other) {
51 | return this.isNothing() ? Maybe.of(null) : other.map(this.__value);
52 | };
53 |
54 | Maybe.prototype.join = function() {
55 | return this.isNothing() ? Maybe.of(null) : this.__value;
56 | }
57 |
58 | Maybe.prototype.inspect = function() {
59 | return 'Maybe('+inspect(this.__value)+')';
60 | }
61 |
62 |
63 | // Either
64 | Either = function() {};
65 | Either.of = function(x) {
66 | return new Right(x);
67 | }
68 |
69 | Left = function(x) {
70 | this.__value = x;
71 | }
72 |
73 | // TODO: remove this nonsense
74 | Left.of = function(x) {
75 | return new Left(x);
76 | }
77 |
78 | Left.prototype.map = function(f) { return this; }
79 | Left.prototype.ap = function(other) { return this; }
80 | Left.prototype.join = function() { return this; }
81 | Left.prototype.chain = function() { return this; }
82 | Left.prototype.inspect = function() {
83 | return 'Left('+inspect(this.__value)+')';
84 | }
85 |
86 |
87 | Right = function(x) {
88 | this.__value = x;
89 | }
90 |
91 | // TODO: remove in favor of Either.of
92 | Right.of = function(x) {
93 | return new Right(x);
94 | }
95 |
96 | Right.prototype.map = function(f) {
97 | return Right.of(f(this.__value));
98 | }
99 |
100 | Right.prototype.join = function() {
101 | return this.__value;
102 | }
103 |
104 | Right.prototype.chain = function(f) {
105 | return f(this.__value);
106 | }
107 |
108 | Right.prototype.ap = function(other) {
109 | return this.chain(function(f) {
110 | return other.map(f);
111 | });
112 | }
113 |
114 | Right.prototype.join = function() {
115 | return this.__value;
116 | }
117 |
118 | Right.prototype.chain = function(f) {
119 | return f(this.__value);
120 | }
121 |
122 | Right.prototype.inspect = function() {
123 | return 'Right('+inspect(this.__value)+')';
124 | }
125 |
126 | // IO
127 | IO = function(f) {
128 | this.unsafePerformIO = f;
129 | }
130 |
131 | IO.of = function(x) {
132 | return new IO(function() {
133 | return x;
134 | });
135 | }
136 |
137 | IO.prototype.map = function(f) {
138 | return new IO(_.compose(f, this.unsafePerformIO));
139 | }
140 |
141 | IO.prototype.join = function() {
142 | return this.unsafePerformIO();
143 | }
144 |
145 | IO.prototype.chain = function(f) {
146 | return this.map(f).join();
147 | }
148 |
149 | IO.prototype.ap = function(a) {
150 | return this.chain(function(f) {
151 | return a.map(f);
152 | });
153 | }
154 |
155 | IO.prototype.inspect = function() {
156 | return 'IO('+inspect(this.unsafePerformIO)+')';
157 | }
158 |
159 | unsafePerformIO = function(x) { return x.unsafePerformIO(); }
160 |
161 | either = curry(function(f, g, e) {
162 | switch(e.constructor) {
163 | case Left: return f(e.__value);
164 | case Right: return g(e.__value);
165 | }
166 | });
167 |
168 | // overwriting join from pt 1
169 | join = function(m){ return m.join(); };
170 |
171 | chain = curry(function(f, m){
172 | return m.map(f).join(); // or compose(join, map(f))(m)
173 | });
174 |
175 | liftA2 = curry(function(f, a1, a2){
176 | return a1.map(f).ap(a2);
177 | });
178 |
179 | liftA3 = curry(function(f, a1, a2, a3){
180 | return a1.map(f).ap(a2).ap(a3);
181 | });
182 |
183 |
184 | Task.prototype.join = function(){ return this.chain(_.identity); }
185 |
--------------------------------------------------------------------------------
/exercises/test-utils.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const cli = require('cli');
6 | const process = require('process');
7 | const { it } = require('mocha');
8 | const {
9 | compose,
10 | concat,
11 | curry,
12 | forEach,
13 | map,
14 | reduce,
15 | } = require('./support');
16 |
17 |
18 | /* -------- Internal -------- */
19 |
20 | const filenameRe = base => new RegExp(`${base}_([a-z]).js`);
21 |
22 | const readDir = d => fs.readdirSync(d);
23 |
24 | const readFile = f => fs.readFileSync(f).toString('utf-8');
25 |
26 | const writeFile = curry((f, data) => fs.writeFileSync(f, data));
27 |
28 | const mkdir = d => fs.mkdirSync(d);
29 |
30 | const mconcat = zero => reduce(concat, zero);
31 |
32 | const evalFiles = compose(eval, mconcat(''), map(readFile)); // eslint-disable-line no-eval
33 |
34 | const listToMap = xs => xs.reduce((m, [k, v]) => m.set(k, v), new Map());
35 |
36 |
37 | /* -------- Utils -------- */
38 |
39 | const readExercises = (ch, sections = ['exercise', 'solution', 'validation']) => {
40 | const folder = path.join(__dirname, ch);
41 |
42 | const partition = reduce((pts, f) => {
43 | forEach((section) => {
44 | const re = filenameRe(section);
45 |
46 | if (re.test(f)) {
47 | const spec = re.exec(f)[1];
48 | pts.get(section).set(spec, path.join(folder, f));
49 | }
50 | }, sections);
51 |
52 | return pts;
53 | }, listToMap(map(s => [s, new Map()], sections)));
54 |
55 | return partition(readDir(folder));
56 | };
57 |
58 | const defineSpecs = curry((input, exercises) => {
59 | const support = path.join(__dirname, 'support.js');
60 |
61 | forEach(([num, exercise]) => {
62 | it(`exercise_${num}`, () => {
63 | const validation = exercises.get('validation').get(num);
64 |
65 | evalFiles([support, exercise, validation]);
66 | });
67 | }, [...exercises.get(input)]);
68 | });
69 |
70 | const runSolutions = compose(defineSpecs('solution'), readExercises);
71 |
72 | const runExercises = compose(defineSpecs('exercise'), readExercises);
73 |
74 |
75 | /* -------- Cli -------- */
76 |
77 | if (process.argv.length > 2 && process.argv[1] === __filename) {
78 | const usage = `Usage: test-utils new
79 |
80 | Available options:
81 | -h, --help Show this help text
82 | -c, --chapter New chapter to create`;
83 |
84 | const spec = curry((runner, chapter) => `const { describe } = require('mocha');
85 | const { ${runner} } = require('../test-utils');
86 |
87 | describe('Exercises Chapter ${chapter}', () => {
88 | ${runner}('ch${chapter}');
89 | });`);
90 |
91 | const addTestScript = curry((pkg, chapter) => {
92 | const { scripts } = pkg;
93 |
94 | return {
95 | ...pkg,
96 | scripts: {
97 | ...scripts,
98 | [`ch${chapter}`]: `mocha test/ch${chapter}.js`,
99 | },
100 | };
101 | });
102 |
103 | const failWith = curry((ctx, msg) => {
104 | ctx.fatal([msg, usage].join('\n\n'));
105 | });
106 |
107 | // NOTE Override the `getUsage` method of the cli which is... not satisfactory.
108 | cli.getUsage = () => { console.error(usage); cli.exit(0); }; // eslint-disable-line no-console
109 |
110 | cli.parse({
111 | chapter: ['c', 'New chapter to create', 'int'],
112 | });
113 |
114 | cli.main(function main(args, options) {
115 | cli.failWith = failWith(this);
116 |
117 | if (args.length !== 1 || args[0] !== 'new') {
118 | cli.failWith(`Unknown command: ${args.join(' ')}`);
119 | }
120 |
121 | if (!options.chapter) {
122 | cli.failWith('Missing required Chapter');
123 | }
124 |
125 | if (readDir(__dirname).includes(`ch${options.chapter}`)) {
126 | cli.failWith(`Chapter ${options.chapter} already exists`);
127 | }
128 |
129 | mkdir(path.join(__dirname, `ch${options.chapter}`));
130 |
131 | writeFile(
132 | path.join(__dirname, 'test', `ch${options.chapter}.js`),
133 | spec('runExercises', options.chapter),
134 | );
135 |
136 | writeFile(
137 | path.join(__dirname, 'test', `ch${options.chapter}.solutions.js`),
138 | spec('runSolutions', options.chapter),
139 | );
140 |
141 | const pkg = addTestScript(require('./package.json'), options.chapter); // eslint-disable-line global-require
142 |
143 | writeFile(
144 | path.join(__dirname, 'package.json'),
145 | JSON.stringify(pkg, null, 2),
146 | );
147 |
148 | console.error(`Bootstrapped Chapter ${options.chapter}.`); // eslint-disable-line no-console
149 | });
150 | }
151 |
152 |
153 | /* -------- Exports -------- */
154 |
155 | module.exports = {
156 | readExercises,
157 | defineSpecs,
158 | runSolutions,
159 | runExercises,
160 | };
161 |
--------------------------------------------------------------------------------
/appendix_c.md:
--------------------------------------------------------------------------------
1 | # Appendix C: Pointfree Utilities
2 |
3 | In this appendix, you'll find pointfree versions of rather classic JavaScript functions
4 | described in the book. All of the following functions are seemingly available in exercises, as
5 | part of the global context. Keep in mind that these implementations may not be the fastest or
6 | the most efficient implementation out there; they *solely serve an educational purpose*.
7 |
8 | In order to find functions that are more production-ready, have a peek at
9 | [ramda](https://ramdajs.com/), [lodash](https://lodash.com/), or [folktale](http://folktale.origamitower.com/).
10 |
11 | Note that functions refer to the `curry` & `compose` functions defined in [Appendix A](./appendix_a.md)
12 |
13 | ## add
14 |
15 | ```js
16 | // add :: Number -> Number -> Number
17 | const add = curry((a, b) => a + b);
18 | ```
19 |
20 | ## append
21 |
22 | ```js
23 | // append :: String -> String -> String
24 | const append = flip(concat);
25 | ```
26 |
27 | ## chain
28 |
29 | ```js
30 | // chain :: Monad m => (a -> m b) -> m a -> m b
31 | const chain = curry((fn, m) => m.chain(fn));
32 | ```
33 |
34 | ## concat
35 |
36 | ```js
37 | // concat :: String -> String -> String
38 | const concat = curry((a, b) => a.concat(b));
39 | ```
40 |
41 | ## eq
42 |
43 | ```js
44 | // eq :: Eq a => a -> a -> Boolean
45 | const eq = curry((a, b) => a === b);
46 | ```
47 |
48 | ## filter
49 |
50 | ```js
51 | // filter :: (a -> Boolean) -> [a] -> [a]
52 | const filter = curry((fn, xs) => xs.filter(fn));
53 | ```
54 |
55 | ## flip
56 |
57 | ```js
58 | // flip :: (a -> b -> c) -> b -> a -> c
59 | const flip = curry((fn, a, b) => fn(b, a));
60 | ```
61 |
62 | ## forEach
63 |
64 | ```js
65 | // forEach :: (a -> ()) -> [a] -> ()
66 | const forEach = curry((fn, xs) => xs.forEach(fn));
67 | ```
68 |
69 | ## head
70 |
71 | ```js
72 | // head :: [a] -> a
73 | const head = xs => xs[0];
74 | ```
75 |
76 | ## intercalate
77 |
78 | ```js
79 | // intercalate :: String -> [String] -> String
80 | const intercalate = curry((str, xs) => xs.join(str));
81 | ```
82 |
83 | ## join
84 |
85 | ```js
86 | // join :: Monad m => m (m a) -> m a
87 | const join = m => m.join();
88 | ```
89 |
90 | ## last
91 |
92 | ```js
93 | // last :: [a] -> a
94 | const last = xs => xs[xs.length - 1];
95 | ```
96 |
97 | ## map
98 |
99 | ```js
100 | // map :: Functor f => (a -> b) -> f a -> f b
101 | const map = curry((fn, f) => f.map(fn));
102 | ```
103 |
104 | ## match
105 |
106 | ```js
107 | // match :: RegExp -> String -> Boolean
108 | const match = curry((re, str) => re.test(str));
109 | ```
110 |
111 | ## prop
112 |
113 | ```js
114 | // prop :: String -> Object -> a
115 | const prop = curry((p, obj) => obj[p]);
116 | ```
117 |
118 | ## reduce
119 |
120 | ```js
121 | // reduce :: (b -> a -> b) -> b -> [a] -> b
122 | const reduce = curry((fn, zero, xs) => xs.reduce(fn, zero));
123 | ```
124 |
125 | ## replace
126 |
127 | ```js
128 | // replace :: RegExp -> String -> String -> String
129 | const replace = curry((re, rpl, str) => str.replace(re, rpl));
130 | ```
131 |
132 | ## reverse
133 |
134 | ```js
135 | // reverse :: [a] -> [a]
136 | const reverse = x => (Array.isArray(x) ? x.reverse() : x.split('').reverse().join(''));
137 | ```
138 |
139 | ## safeHead
140 |
141 | ```js
142 | // safeHead :: [a] -> Maybe a
143 | const safeHead = compose(Maybe.of, head);
144 | ```
145 |
146 | ## safeLast
147 |
148 | ```js
149 | // safeLast :: [a] -> Maybe a
150 | const safeLast = compose(Maybe.of, last);
151 | ```
152 |
153 | ## safeProp
154 |
155 | ```js
156 | // safeProp :: String -> Object -> Maybe a
157 | const safeProp = curry((p, obj) => compose(Maybe.of, prop(p))(obj));
158 | ```
159 |
160 | ## sequence
161 |
162 | ```js
163 | // sequence :: (Applicative f, Traversable t) => (a -> f a) -> t (f a) -> f (t a)
164 | const sequence = curry((of, f) => f.sequence(of));
165 | ```
166 |
167 | ## sortBy
168 |
169 | ```js
170 | // sortBy :: Ord b => (a -> b) -> [a] -> [a]
171 | const sortBy = curry((fn, xs) => xs.sort((a, b) => {
172 | if (fn(a) === fn(b)) {
173 | return 0;
174 | }
175 |
176 | return fn(a) > fn(b) ? 1 : -1;
177 | }));
178 | ```
179 |
180 | ## split
181 |
182 | ```js
183 | // split :: String -> String -> [String]
184 | const split = curry((sep, str) => str.split(sep));
185 | ```
186 |
187 | ## take
188 |
189 | ```js
190 | // take :: Number -> [a] -> [a]
191 | const take = curry((n, xs) => xs.slice(0, n));
192 | ```
193 |
194 | ## toLowerCase
195 |
196 | ```js
197 | // toLowerCase :: String -> String
198 | const toLowerCase = s => s.toLowerCase();
199 | ```
200 |
201 | ## toString
202 |
203 | ```js
204 | // toString :: a -> String
205 | const toString = String;
206 | ```
207 |
208 | ## toUpperCase
209 |
210 | ```js
211 | // toUpperCase :: String -> String
212 | const toUpperCase = s => s.toUpperCase();
213 | ```
214 |
215 | ## traverse
216 |
217 | ```js
218 | // traverse :: (Applicative f, Traversable t) => (a -> f a) -> (a -> f b) -> t a -> f (t b)
219 | const traverse = curry((of, fn, f) => f.traverse(of, fn));
220 | ```
221 |
222 | ## unsafePerformIO
223 |
224 | ```js
225 | // unsafePerformIO :: IO a -> a
226 | const unsafePerformIO = io => io.unsafePerformIO();
227 | ```
228 |
--------------------------------------------------------------------------------
/appendix_c-es.md:
--------------------------------------------------------------------------------
1 | # Apéndice C: Utilidades Pointfree
2 |
3 | En este apéndice, encontrarás versiones pointfree de funciones clásicas de JavaScript
4 | descritas en el libro. Todas las siguientes funciones están disponibles en los ejercicios como parte de su contexto global. Ten en cuenta que estas implementaciones pueden no ser las más rápidas o las
5 | más eficientes; *tienen únicamente una finalidad educativa*.
6 |
7 | Para encontrar funciones más preparadas para un entorno en producción, échale un vistazo a
8 | [ramda](https://ramdajs.com/), [lodash](https://lodash.com/), o [folktale](http://folktale.origamitower.com/).
9 |
10 | Ten en cuenta que las funciones hacen referencia a las funciones `curry` y `compose` definidas en el [Apéndice A](./appendix_a-es.md)
11 |
12 | ## add
13 |
14 | ```js
15 | // add :: Number -> Number -> Number
16 | const add = curry((a, b) => a + b);
17 | ```
18 |
19 | ## append
20 |
21 | ```js
22 | // append :: String -> String -> String
23 | const append = flip(concat);
24 | ```
25 |
26 | ## chain
27 |
28 | ```js
29 | // chain :: Monad m => (a -> m b) -> m a -> m b
30 | const chain = curry((fn, m) => m.chain(fn));
31 | ```
32 |
33 | ## concat
34 |
35 | ```js
36 | // concat :: String -> String -> String
37 | const concat = curry((a, b) => a.concat(b));
38 | ```
39 |
40 | ## eq
41 |
42 | ```js
43 | // eq :: Eq a => a -> a -> Boolean
44 | const eq = curry((a, b) => a === b);
45 | ```
46 |
47 | ## filter
48 |
49 | ```js
50 | // filter :: (a -> Boolean) -> [a] -> [a]
51 | const filter = curry((fn, xs) => xs.filter(fn));
52 | ```
53 |
54 | ## flip
55 |
56 | ```js
57 | // flip :: (a -> b -> c) -> b -> a -> c
58 | const flip = curry((fn, a, b) => fn(b, a));
59 | ```
60 |
61 | ## forEach
62 |
63 | ```js
64 | // forEach :: (a -> ()) -> [a] -> ()
65 | const forEach = curry((fn, xs) => xs.forEach(fn));
66 | ```
67 |
68 | ## head
69 |
70 | ```js
71 | // head :: [a] -> a
72 | const head = xs => xs[0];
73 | ```
74 |
75 | ## intercalate
76 |
77 | ```js
78 | // intercalate :: String -> [String] -> String
79 | const intercalate = curry((str, xs) => xs.join(str));
80 | ```
81 |
82 | ## join
83 |
84 | ```js
85 | // join :: Monad m => m (m a) -> m a
86 | const join = m => m.join();
87 | ```
88 |
89 | ## last
90 |
91 | ```js
92 | // last :: [a] -> a
93 | const last = xs => xs[xs.length - 1];
94 | ```
95 |
96 | ## map
97 |
98 | ```js
99 | // map :: Functor f => (a -> b) -> f a -> f b
100 | const map = curry((fn, f) => f.map(fn));
101 | ```
102 |
103 | ## match
104 |
105 | ```js
106 | // match :: RegExp -> String -> Boolean
107 | const match = curry((re, str) => re.test(str));
108 | ```
109 |
110 | ## prop
111 |
112 | ```js
113 | // prop :: String -> Object -> a
114 | const prop = curry((p, obj) => obj[p]);
115 | ```
116 |
117 | ## reduce
118 |
119 | ```js
120 | // reduce :: (b -> a -> b) -> b -> [a] -> b
121 | const reduce = curry((fn, zero, xs) => xs.reduce(fn, zero));
122 | ```
123 |
124 | ## replace
125 |
126 | ```js
127 | // replace :: RegExp -> String -> String -> String
128 | const replace = curry((re, rpl, str) => str.replace(re, rpl));
129 | ```
130 |
131 | ## reverse
132 |
133 | ```js
134 | // reverse :: [a] -> [a]
135 | const reverse = x => (Array.isArray(x) ? x.reverse() : x.split('').reverse().join(''));
136 | ```
137 |
138 | ## safeHead
139 |
140 | ```js
141 | // safeHead :: [a] -> Maybe a
142 | const safeHead = compose(Maybe.of, head);
143 | ```
144 |
145 | ## safeLast
146 |
147 | ```js
148 | // safeLast :: [a] -> Maybe a
149 | const safeLast = compose(Maybe.of, last);
150 | ```
151 |
152 | ## safeProp
153 |
154 | ```js
155 | // safeProp :: String -> Object -> Maybe a
156 | const safeProp = curry((p, obj) => compose(Maybe.of, prop(p))(obj));
157 | ```
158 |
159 | ## sequence
160 |
161 | ```js
162 | // sequence :: (Applicative f, Traversable t) => (a -> f a) -> t (f a) -> f (t a)
163 | const sequence = curry((of, f) => f.sequence(of));
164 | ```
165 |
166 | ## sortBy
167 |
168 | ```js
169 | // sortBy :: Ord b => (a -> b) -> [a] -> [a]
170 | const sortBy = curry((fn, xs) => xs.sort((a, b) => {
171 | if (fn(a) === fn(b)) {
172 | return 0;
173 | }
174 |
175 | return fn(a) > fn(b) ? 1 : -1;
176 | }));
177 | ```
178 |
179 | ## split
180 |
181 | ```js
182 | // split :: String -> String -> [String]
183 | const split = curry((sep, str) => str.split(sep));
184 | ```
185 |
186 | ## take
187 |
188 | ```js
189 | // take :: Number -> [a] -> [a]
190 | const take = curry((n, xs) => xs.slice(0, n));
191 | ```
192 |
193 | ## toLowerCase
194 |
195 | ```js
196 | // toLowerCase :: String -> String
197 | const toLowerCase = s => s.toLowerCase();
198 | ```
199 |
200 | ## toString
201 |
202 | ```js
203 | // toString :: a -> String
204 | const toString = String;
205 | ```
206 |
207 | ## toUpperCase
208 |
209 | ```js
210 | // toUpperCase :: String -> String
211 | const toUpperCase = s => s.toUpperCase();
212 | ```
213 |
214 | ## traverse
215 |
216 | ```js
217 | // traverse :: (Applicative f, Traversable t) => (a -> f a) -> (a -> f b) -> t a -> f (t b)
218 | const traverse = curry((of, fn, f) => f.traverse(of, fn));
219 | ```
220 |
221 | ## unsafePerformIO
222 |
223 | ```js
224 | // unsafePerformIO :: IO a -> a
225 | const unsafePerformIO = io => io.unsafePerformIO();
226 | ```
227 |
--------------------------------------------------------------------------------
/README-original.md:
--------------------------------------------------------------------------------
1 | [](SUMMARY.md)
2 |
3 | ## About this book
4 |
5 | This is a book on the functional paradigm in general. We'll use the world's most popular functional programming language: JavaScript. Some may feel this is a poor choice as it's against the grain of the current culture which, at the moment, feels predominately imperative. However, I believe it is the best way to learn FP for several reasons:
6 |
7 | * **You likely use it every day at work.**
8 |
9 | This makes it possible to practice and apply your acquired knowledge each day on real world programs rather than pet projects on nights and weekends in an esoteric FP language.
10 |
11 |
12 | * **We don't have to learn everything up front to start writing programs.**
13 |
14 | In a pure functional language, you cannot log a variable or read a DOM node without using monads. Here we can cheat a little as we learn to purify our codebase. It's also easier to get started in this language since it's mixed paradigm and you can fall back on your current practices while there are gaps in your knowledge.
15 |
16 |
17 | * **The language is fully capable of writing top notch functional code.**
18 |
19 | We have all the features we need to mimic a language like Scala or Haskell with the help of a tiny library or two. Object-oriented programming currently dominates the industry, but it's clearly awkward in JavaScript. It's akin to camping off of a highway or tap dancing in galoshes. We have to `bind` all over the place lest `this` change out from under us, we have various work arounds for the quirky behavior when the `new` keyword is forgotten, private members are only available via closures. To a lot of us, FP feels more natural anyways.
20 |
21 | That said, typed functional languages will, without a doubt, be the best place to code in the style presented by this book. JavaScript will be our means of learning a paradigm, where you apply it is up to you. Luckily, the interfaces are mathematical and, as such, ubiquitous. You'll find yourself at home with Swiftz, Scalaz, Haskell, PureScript, and other mathematically inclined environments.
22 |
23 |
24 | ## Read it Online
25 |
26 | For a best reading experience, [read it online via Gitbook](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/).
27 |
28 | - Quick-access side-bar
29 | - In-browser exercises
30 | - In-depth examples
31 |
32 |
33 | ## Play Around with Code
34 |
35 | To make the training efficient and not get too bored while I am telling you another story, make sure to play around with the concepts introduced in this book. Some can be tricky to catch at first and are better understood by getting your hands dirty.
36 | All functions and algebraic data-structures presented in the book are gathered in the appendixes. The corresponding code is also available as an npm module:
37 |
38 | ```bash
39 | $ npm i @mostly-adequate/support
40 | ```
41 |
42 | Alternatively, exercises of each chapter are runnable and can be completed in your editor! For example, complete the `exercise_*.js` in `exercises/ch04` and then run:
43 |
44 | ```bash
45 | $ npm run ch04
46 | ```
47 |
48 | ## Download it
49 |
50 | Find pre-generated **PDF** and **EPUB** as [build artifacts of the latest release](https://github.com/MostlyAdequate/mostly-adequate-guide/releases/latest).
51 |
52 | ## Do it yourself
53 |
54 | > ⚠️ This project setup is now a bit old and thus, you may run into various issues when building this locally. We recommend to use node v10.22.1 and the latest version of Calibre if possible.
55 |
56 | ```
57 | git clone https://github.com/MostlyAdequate/mostly-adequate-guide.git
58 | cd mostly-adequate-guide/
59 | npm install
60 | npm run setup
61 | npm run generate-pdf
62 | npm run generate-epub
63 | ```
64 |
65 | > Note! To generate the ebook version you will need to install `ebook-convert`. [Installation instructions](https://gitbookio.gitbooks.io/documentation/content/build/ebookconvert.html).
66 |
67 | # Table of Contents
68 |
69 | See [SUMMARY.md](SUMMARY.md)
70 |
71 | ### Contributing
72 |
73 | See [CONTRIBUTING.md](CONTRIBUTING.md)
74 |
75 | ### Translations
76 |
77 | See [TRANSLATIONS.md](TRANSLATIONS.md)
78 |
79 | ### FAQ
80 |
81 | See [FAQ.md](FAQ.md)
82 |
83 |
84 |
85 | # Plans for the future
86 |
87 | * **Part 1** (chapters 1-7) is a guide to the basics. I'm updating as I find errors since this is the initial draft. Feel free to help!
88 | * **Part 2** (chapters 8-13) address type classes like functors and monads all the way through to traversable. I hope to squeeze in transformers and a pure application.
89 | * **Part 3** (chapters 14+) will start to dance the fine line between practical programming and academic absurdity. We'll look at comonads, f-algebras, free monads, yoneda, and other categorical constructs.
90 |
91 |
92 | ---
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
101 |
102 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | ## FAQ
2 |
3 | - [Why are snippets written sometimes with semicolons and sometimes
4 | without?](#why-are-snippets-written-sometimes-with-semicolons-and-sometimes-without)
5 | - [Aren't external libraries like _ (ramda) or $ (jquery) making calls impure?](#arent-external-libraries-like-_-ramda-or--jquery-making-calls-impure)
6 | - [What is the meaning of `f a` in signature?](#what-is-the-meaning-of-f-a-in-signature)
7 | - [Is there any "real world" examples available?](#is-there-any-real-world-examples-available)
8 | - [Why does the book uses ES5? Is any ES6 version available?](#why-does-the-book-uses-es5-is-any-es6-version-available)
9 | - [What the heck is that reduce function about?](#what-the-heck-is-that-reduce-function-about)
10 | - [Wouldn't you use a simplified English rather than the current style?](#wouldnt-you-use-a-simplified-english-rather-than-the-current-style)
11 | - [What is Either? What is Future? What is Task?](#what-is-either-what-is-future-what-is-task)
12 | - [Where do map, filter, compose ... methods come from?](#where-do-map-filter-compose--methods-come-from)
13 |
14 | ### Why are snippets written sometimes with semicolons and sometimes without?
15 |
16 | > see [#6]
17 |
18 | There are two schools in JavaScript, people who use them, and people who don't. We've made the
19 | choice here to use them, and now, we strive to be consistent with that choice. If some are
20 | missing, please let us know and we will take care of the oversight.
21 |
22 | ### Aren't external libraries like _ (ramda) or $ (jquery) making calls impure?
23 |
24 | > see [#50]
25 |
26 | Those dependencies are available as if they were in the global context, part of the language.
27 | So, no, calls can still be considered as pure.
28 | For further reading, have a look at [this article about
29 | CoEffects](http://tomasp.net/blog/2014/why-coeffects-matter/)
30 |
31 | ### What is the meaning of `f a` in signature?
32 |
33 | > see [#62]
34 |
35 | In a signature, like:
36 |
37 | `map :: Functor f => (a -> b) -> f a -> f b`
38 |
39 | `f` refers to a `functor` that could be, for instance, Maybe or IO. Thus, the signature abstracts
40 | the choice of that functor by using a type variable which basically means that any functor
41 | might be used where `f` stands as long as all `f` are of the same type (if the first `f a` in
42 | the signature represents a `Maybe a`, then the second one **cannot refer to** an `IO b` but
43 | should be a `Maybe b`. For instance:
44 |
45 | ```javascript
46 | let maybeString = Maybe.of("Patate")
47 | let f = function (x) { return x.length }
48 | let maybeNumber = map(f, maybeString) // Maybe(6)
49 |
50 | // With the following 'refined' signature:
51 | // map :: (string -> number) -> Maybe string -> Maybe number
52 | ```
53 |
54 | ### Is there any "real world" examples available?
55 |
56 | > see [#77], [#192]
57 |
58 | Should you haven't reached it yet, you may have a look at the [Chapter
59 | 6](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch06.md) which presents a
60 | simple flickr application.
61 | Other examples are likely to come later on. By the way, feel free to share with us your
62 | experience!
63 |
64 | ### Why does the book uses ES5? Is any ES6 version available?
65 |
66 | > see [#83], [#235]
67 |
68 | The book aims at being widely accessible. It started before ES6 went out, and now, as the new
69 | standard is being more and more accepted, we are considering making two separated editions with
70 | ES5 and ES6. Members of the community are already working on the ES6 version (have a look to
71 | [#235] for more information).
72 |
73 | ### What the heck is that reduce function about?
74 |
75 | > see [#109]
76 |
77 | Reduce, accumulate, fold, inject are all usual functions in functional programming used to
78 | combine the elements of a data structure successively. You might have a look at [this
79 | talk](https://www.youtube.com/watch?v=JZSoPZUoR58&ab_channel=NewCircleTraining) to get some
80 | more insights about the reduce function.
81 |
82 | ### Wouldn't you use a simplified English rather than the current style?
83 |
84 | > see [#176]
85 |
86 | The book is written in its own style which contributes to make it consistent as a whole. If
87 | you're not familiar with English, see it as good training. Nevertheless, should you need help
88 | to understand the meaning sometimes, there are now [several
89 | translations](https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/TRANSLATIONS.md)
90 | available that could probably help you.
91 |
92 | ### What is Either? What is Future? What is Task?
93 |
94 | > see [#194]
95 |
96 | We introduce all of those structures throughout the book. Therefore, you won't find any use of a
97 | structure that hasn't previously be defined. Do not hesitate to read again some old parts if
98 | you ever feel uncomfortable with those types.
99 | A glossary/vade mecum will come at the end to synthesize all these notions.
100 |
101 | ### Where do map, filter, compose ... methods come from?
102 |
103 | > see [#198]
104 |
105 | Most of the time, those methods are defined in specific vendor libraries such as `ramda` or
106 | `underscore`. You should also have a look at [Appendix A](./appendix_a.md),
107 | [Appendix B](./appendix_b.md) and [Appendix C](./appendix_c.md) in which we define
108 | several implementations used for the exercises. Those functions are really
109 | common in functional programming and even though their implementations may vary a bit, their
110 | meanings remain fairly consistent between libraries.
111 |
112 |
113 | [#6]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/6
114 | [#50]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/50
115 | [#62]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/62
116 | [#77]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/77
117 | [#83]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/83
118 | [#109]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/109
119 | [#176]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/176
120 | [#192]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/192
121 | [#194]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/194
122 | [#198]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/198
123 | [#235]: https://github.com/MostlyAdequate/mostly-adequate-guide/pull/235
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](SUMMARY.md)
2 |
3 | # Introducción
4 |
5 | ## Sobre este libro
6 |
7 | Este es un libro sobre el paradigma funcional en general. Utilizaremos el lenguaje de programación funcional más popular del mundo: JavaScript. Hay quien vaya a pensar que es una mala elección, ya que va en contra de la cultura que, por el momento, es predominantemente imperativa. Sin embargo, creo que esta es la mejor forma de aprender programación funcional por diversas razones:
8 |
9 | * **Seguramente lo utilizes cada día en el trabajo.**
10 |
11 | Esto hace posible practicar y aplicar cada día en programas del mundo real los conocimientos adquiridos en vez de en proyectos de una sola noche o de un fin de semana con un lenguaje de programación funcional esotérico.
12 |
13 |
14 | * **No tenemos que aprenderlo todo desde cero para empezar a escribir programas.**
15 |
16 | En un lenguaje funcional puro, no puedes registrar una variable o leer un nodo DOM sin usar mónadas. Aquí podemos hacer un poco de trampas mientras aprendemos a purificar nuestra base de código. También es más fácil empezar con este lenguaje debido a su paradigma mixto y a que te puedes apoyar en lo que ya conoces mientras haya huecos en tu conocimiento.
17 |
18 |
19 | * **El lenguaje está completamente capacitado para escribir código funcional de primera categoría.**
20 |
21 | Tenemos todas las características necesarias para imitar a un lenguaje como Scala o Haskell con la ayuda de una o dos pequeñas librerías. La programación orientada a objetos domina actualmente la industria, pero es claramente torpe en JavaScript. Es similar a acampar en una autopista o bailar claqué con botas de agua. Tenemos que usar `bind` por todas partes para que `this` no cambie sin que nos demos cuenta, tenemos varias alternativas al peculiar comportamiento cuando olvidamos utilizar `new`, los miembros privados solo están disponibles mediante clausuras [*closures*]. En fin, para muchas personas la programación funcional parece más natural.
22 |
23 | Dicho esto, los lenguajes funcionales tipados serán, sin ninguna duda, el mejor lugar para programar con el estilo que se presenta en este libro. JavaScript será nuestro medio para aprender un paradigma, dónde lo apliques depende de tí. Afortunadamente, las interfaces son matemáticas y, como tal, ubicuas. Te sentirás como en casa con Swiftz, Scalaz, Haskell, PureScript, y otros entornos con inclinación por las matemáticas.
24 |
25 | ## Sobre la traducción
26 |
27 | Se han añadido notas de traducción donde se ha visto necesario. Para no interferir con el ritmo de la lectura se ha optado por incluir la nota entre corchetes y en cursiva seguidamente de aquello que se esté anotando. Por ejemplo, "solo necesitas saber cómo encontrar y matar algunos bugs [*bichos*]". En caso de anotar un título la anotación se incluirá al comienzo del párrafo que le siga.
28 |
29 | También se han modificado algunas frases para mantener un género neutro. Por ejemplo en vez de traducir "Some will argue that" a "Algunos argumentarán que", se ha traducido a "Hay quien argumentará que".
30 |
31 | ## Léelo Online
32 |
33 | Para una mejor experiencia en la lectura, [léelo online a través de Gitbook](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/).
34 |
35 | - Barra lateral de acceso rápido
36 | - Ejercicios en el propio navegador
37 | - Ejemplos en profundidad
38 |
39 |
40 | ## Juega Con el Código
41 |
42 | Para que el entrenamiento sea efectivo y no te aburras demasiado mientras te cuento otra história, asegúrate de jugar con los conceptos introducidos en este libro. Algunos pueden ser difíciles de entender a la primera y se comprenden mejor cuándo te ensucias las manos.
43 | Todas las funciones y estructuras de datos algebraicas presentadas en el libro están reunidas en los apéndices. El correspondiente código también está disponible como un módulo de npm:
44 |
45 | ```bash
46 | $ npm i @mostly-adequate/support
47 | ```
48 |
49 | Alternativamente, ¡los ejercicios de cada capítulo son ejecutables y pueden ser completados en tu editor! Por ejemplo, completa `exercise_*.js` en `exercises/ch04` y después ejecuta:
50 |
51 | ```bash
52 | $ npm run ch04
53 | ```
54 |
55 | ## Descárgalo
56 |
57 | Encuentra archivos **PDF** y **EPUB** pregenerados como [artefactos construidos desde la última versión](https://github.com/MostlyAdequate/mostly-adequate-guide-es/releases/latest).
58 |
59 | ## Hazlo tú mismo
60 |
61 | > ⚠️ La preparación del proyecto es un poco antigua, puedes encontrarte con distintos problemas cuando lo construyas localmente. Recomendamos el uso de node v10.22.1 y la última versión de Calibre si es posible.
62 |
63 | ```
64 | git clone https://github.com/MostlyAdequate/mostly-adequate-guide-es.git
65 | cd mostly-adequate-guide-es/
66 | npm install
67 | npm run setup
68 | npm run generate-pdf
69 | npm run generate-epub
70 | ```
71 |
72 | > ¡Nota! Para generar la versión ebook necesitarás instalar `ebook-convert`. [Instrucciones de
73 | > instalación](https://gitbookio.gitbooks.io/documentation/content/build/ebookconvert.html).
74 |
75 | ## Contenido
76 |
77 | Ver [SUMMARY-es.md](SUMMARY-es.md)
78 |
79 | ### Contribuir
80 |
81 | Ver [CONTRIBUTING-es.md](CONTRIBUTING-es.md)
82 |
83 | ### Traducciones
84 |
85 | Ver [TRANSLATIONS-es.md](TRANSLATIONS-es.md)
86 |
87 | ### FAQ
88 |
89 | Ver [FAQ-es.md](FAQ-es.md)
90 |
91 |
92 |
93 | ## Planes para el futuro
94 |
95 | * **Parte 1** (capítulos 1-7) es una guía básica. La actualizaré a medida que encuentre errores, ya que esto es un borrador inicial. ¡Siéntete libre de ayudar!
96 | * **Parte 2** (capítulos 8-13) aborda clases de tipos como funtores y mónadas llegando hasta traversable. Espero poder alcanzar transformadores y una aplicación pura.
97 | * **Parte 3** (capítulos 14+) cruzará la delgada línea entre la programación práctica y la absurdidad académica. Veremos comónadas, f-algebras, mónadas libres, yoneda, y otras construcciones categóricas.
98 |
99 |
100 | ---
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Este trabajo está licenciado bajo licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional.
109 |
110 |
--------------------------------------------------------------------------------
/ch02.md:
--------------------------------------------------------------------------------
1 | # Chapter 02: First Class Functions
2 |
3 | ## A Quick Review
4 | When we say functions are "first class", we mean they are just like everyone else... so in other words a normal class. We can treat functions like any other data type and there is nothing particularly special about them - they may be stored in arrays, passed around as function parameters, assigned to variables, and what have you.
5 |
6 | That is JavaScript 101, but worth mentioning since a quick code search on github will reveal the collective evasion, or perhaps widespread ignorance of this concept. Shall we go for a feigned example? We shall.
7 |
8 | ```js
9 | const hi = name => `Hi ${name}`;
10 | const greeting = name => hi(name);
11 | ```
12 |
13 | Here, the function wrapper around `hi` in `greeting` is completely redundant. Why? Because functions are *callable* in JavaScript. When `hi` has the `()` at the end it will run and return a value. When it does not, it simply returns the function stored in the variable. Just to be sure, have a look yourself:
14 |
15 |
16 | ```js
17 | hi; // name => `Hi ${name}`
18 | hi("jonas"); // "Hi jonas"
19 | ```
20 |
21 | Since `greeting` is merely in turn calling `hi` with the very same argument, we could simply write:
22 |
23 | ```js
24 | const greeting = hi;
25 | greeting("times"); // "Hi times"
26 | ```
27 |
28 | In other words, `hi` is already a function that expects one argument, why place another function around it that simply calls `hi` with the same bloody argument? It doesn't make any damn sense. It's like donning your heaviest parka in the dead of July just to blast the air and demand an ice lolly.
29 |
30 | It is obnoxiously verbose and, as it happens, bad practice to surround a function with another function merely to delay evaluation (we'll see why in a moment, but it has to do with maintenance)
31 |
32 | A solid understanding of this is critical before moving on, so let's examine a few more fun examples excavated from the library of npm packages.
33 |
34 | ```js
35 | // ignorant
36 | const getServerStuff = callback => ajaxCall(json => callback(json));
37 |
38 | // enlightened
39 | const getServerStuff = ajaxCall;
40 | ```
41 |
42 | The world is littered with ajax code exactly like this. Here is the reason both are equivalent:
43 |
44 | ```js
45 | // this line
46 | ajaxCall(json => callback(json));
47 |
48 | // is the same as this line
49 | ajaxCall(callback);
50 |
51 | // so refactor getServerStuff
52 | const getServerStuff = callback => ajaxCall(callback);
53 |
54 | // ...which is equivalent to this
55 | const getServerStuff = ajaxCall; // <-- look mum, no ()'s
56 | ```
57 |
58 | And that, folks, is how it is done. Once more so that we understand why I'm being so persistent.
59 |
60 | ```js
61 | const BlogController = {
62 | index(posts) { return Views.index(posts); },
63 | show(post) { return Views.show(post); },
64 | create(attrs) { return Db.create(attrs); },
65 | update(post, attrs) { return Db.update(post, attrs); },
66 | destroy(post) { return Db.destroy(post); },
67 | };
68 | ```
69 |
70 | This ridiculous controller is 99% fluff. We could either rewrite it as:
71 |
72 | ```js
73 | const BlogController = {
74 | index: Views.index,
75 | show: Views.show,
76 | create: Db.create,
77 | update: Db.update,
78 | destroy: Db.destroy,
79 | };
80 | ```
81 |
82 | ... or scrap it altogether since it does nothing more than just bundle our Views and Db together.
83 |
84 | ## Why Favor First Class?
85 |
86 | Okay, let's get down to the reasons to favor first class functions. As we saw in the `getServerStuff` and `BlogController` examples, it's easy to add layers of indirection that provide no added value and only increase the amount of redundant code to maintain and search through.
87 |
88 | In addition, if such a needlessly wrapped function must be changed, we must also need to change our wrapper function as well.
89 |
90 | ```js
91 | httpGet('/post/2', json => renderPost(json));
92 | ```
93 |
94 | If `httpGet` were to change to send a possible `err`, we would need to go back and change the "glue".
95 |
96 | ```js
97 | // go back to every httpGet call in the application and explicitly pass err along.
98 | httpGet('/post/2', (json, err) => renderPost(json, err));
99 | ```
100 |
101 | Had we written it as a first class function, much less would need to change:
102 |
103 | ```js
104 | // renderPost is called from within httpGet with however many arguments it wants
105 | httpGet('/post/2', renderPost);
106 | ```
107 |
108 | Besides the removal of unnecessary functions, we must name and reference arguments. Names are a bit of an issue, you see. We have potential misnomers - especially as the codebase ages and requirements change.
109 |
110 | Having multiple names for the same concept is a common source of confusion in projects. There is also the issue of generic code. For instance, these two functions do exactly the same thing, but one feels infinitely more general and reusable:
111 |
112 | ```js
113 | // specific to our current blog
114 | const validArticles = articles =>
115 | articles.filter(article => article !== null && article !== undefined),
116 |
117 | // vastly more relevant for future projects
118 | const compact = xs => xs.filter(x => x !== null && x !== undefined);
119 | ```
120 |
121 | By using specific naming, we've seemingly tied ourselves to specific data (in this case `articles`). This happens quite a bit and is a source of much reinvention.
122 |
123 | I must mention that, just like with Object-Oriented code, you must be aware of `this` coming to bite you in the jugular. If an underlying function uses `this` and we call it first class, we are subject to this leaky abstraction's wrath.
124 |
125 | ```js
126 | const fs = require('fs');
127 |
128 | // scary
129 | fs.readFile('freaky_friday.txt', Db.save);
130 |
131 | // less so
132 | fs.readFile('freaky_friday.txt', Db.save.bind(Db));
133 | ```
134 |
135 | Having been bound to itself, the `Db` is free to access its prototypical garbage code. I avoid using `this` like a dirty nappy. There's really no need when writing functional code. However, when interfacing with other libraries, you might have to acquiesce to the mad world around us.
136 |
137 | Some will argue that `this` is necessary for optimizing speed. If you are the micro-optimization sort, please close this book. If you cannot get your money back, perhaps you can exchange it for something more fiddly.
138 |
139 | And with that, we're ready to move on.
140 |
141 | [Chapter 03: Pure Happiness with Pure Functions](ch03.md)
142 |
--------------------------------------------------------------------------------
/FAQ-es.md:
--------------------------------------------------------------------------------
1 | ## FAQ
2 |
3 | - [¿Por qué hay fragmentos de código escritos a veces con puntos y comas y a veces sin?](#por-qué-hay-fragmentos-de-código-escritos-a-veces-con-puntos-y-comas-y-a-veces-sin)
4 | - [Librerías externas como _ (ramda) o $ (jquery), ¿no están haciendo llamadas impuras?](#librerías-externas-como-_-ramda-o--jquery-no-están-haciendo-llamadas-impuras)
5 | - [¿Cuál es el significado de `f a` en una firma?](#cuál-es-el-significado-de-f-a-en-una-firma)
6 | - [¿Hay disponibles ejemplos de "la vida real"?](#hay-disponibles-ejemplos-de-la-vida-real)
7 | - [¿Por qué el libro utiliza ES5? ¿Hay disponible alguna versión con ES6?](#por-qué-el-libro-utiliza-es5-hay-disponible-alguna-versión-con-es6)
8 | - [¿A qué viene esa función reduce?](#a-qué-viene-esa-función-reduce)
9 | - [¿No podrías utilizar un inglés más simple en lugar del estilo actual?](#no-podrías-utilizar-un-inglés-más-simple-en-lugar-del-estilo-actual)
10 | - [¿Qué es Either? ¿Qué es Future? ¿Qué es Task?](#qué-es-either-qué-es-future-qué-es-task)
11 | - [¿De dónde vienen métodos como map, filter, compose...?](#de-dónde-vienen-métodos-como-map-filter-compose)
12 |
13 | ### ¿Por qué hay fragmentos de código escritos a veces con puntos y comas y a veces sin?
14 |
15 | > ver [#6]
16 |
17 | Hay dos escuelas en JavaScript, gente que los usa, y gente que no. Nosotros hemos elegido
18 | usarlos, y ahora, nos esforzamos en ser consistentes con la decisión. Si falta alguno,
19 | por favor háznoslo saber y nos ocuparemos del descuido.
20 |
21 | ### Librerías externas como _ (ramda) o $ (jquery), ¿no están haciendo llamadas impuras?
22 |
23 | > ver [#50]
24 |
25 | Estas dependencias están disponibles como si estuviesen en el contexto global,
26 | parte del lenguaje.
27 | Así que, no, las llamadas aún se pueden considerar como puras.
28 |
29 | Para más información, dale un vistazo a [este artículo sobre los CoEffects](http://tomasp.net/blog/2014/why-coeffects-matter/)
30 |
31 | ### ¿Cuál es el significado de `f a` en una firma?
32 |
33 | > ver [#62]
34 |
35 | En una firma, como:
36 |
37 | `map :: Functor f => (a -> b) -> f a -> f b`
38 |
39 | `f` se refiere a un `funtor` que puede ser, por ejemplo, Maybe o IO. Así pues, la firma
40 | abstrae la elección de ese funtor mediante el uso de una variable de tipo, lo que básicamente
41 | significa que cualquier funtor puede ser usado donde aparece `f` siempre que todas las `f`
42 | sean del mismo tipo (si el primer `f a` en la firma representa un `Maybe a`, entonces el
43 | segundo **no puede referirse a** un `IO b` si no que debe referirse a un `Maybe b`). Por ejemplo:
44 |
45 | ```javascript
46 | let maybeString = Maybe.of("Patate")
47 | let f = function (x) { return x.length }
48 | let maybeNumber = map(f, maybeString) // Maybe(6)
49 |
50 | // Con la siguiente 'refinada' firma:
51 | // map :: (string -> number) -> Maybe string -> Maybe number
52 | ```
53 |
54 | ### ¿Hay disponibles ejemplos de "la vida real"?
55 |
56 | > ver [#77], [#192]
57 |
58 | Si aún no has llegado, puedes echar un vistazo al [Capítulo 6](https://github.com/MostlyAdequate/mostly-adequate-guide-es/blob/master/ch06-es.md), el cual presenta una aplicación sencilla sobre flick
59 | Pronto llegarán otros ejemplos. Por cierto, ¡eres libre de compartir con nosotros tu experiencia!
60 |
61 | ### ¿Por qué el libro utiliza ES5? ¿Hay disponible alguna versión con ES6?
62 |
63 | > ver [#83], [#235]
64 |
65 | El libro pretende ser ampliamente accesible. Empezó antes de la salida de ES6, y, ahora que el nuevo
66 | standard está siendo más y más aceptado, estamos considerando hacer dos ediciones separadas con
67 | ES5 y ES6. Miembros de la comunidad ya están trabajando en la versión ES6 (echa un vistazo a
68 | [#235] para más información).
69 |
70 | ### ¿A qué viene esa función reduce?
71 |
72 | > ver [#109]
73 |
74 | Reduce, accumulate, fold, inject son funciones usuales en programación funcional utilizadas para
75 | combinar sucesivamente los elementos de una estructura de datos. Quizás quieras ver [esta charla]
76 | (https://www.youtube.com/watch?v=JZSoPZUoR58&ab_channel=NewCircleTraining) para obtener más
77 | información sobre la función reduce.
78 |
79 | ### ¿No podrías utilizar un inglés más simple en lugar del estilo actual?
80 |
81 | > ver [#176]
82 |
83 | El libro está escrito en su propio estilo, lo cual contribuye a hacerlo consistente como un todo. Si
84 | no estás familiarizado con el inglés, puedes verlo como un buen entrenamiento.
85 | Sin embargo, si alguna vez necesitas ayuda para entender algún significado, ahora
86 | hay [numerosas traducciones](https://github.com/MostlyAdequate/mostly-adequate-guide-es/blob/master/TRANSLATIONS-es.md)
87 | disponibles que probablemente te sean de ayuda.
88 |
89 | ### ¿Qué es Either? ¿Qué es Future? ¿Qué es Task?
90 |
91 | > ver [#194]
92 |
93 | Vamos presentando todas estas estructuras a lo largo del libro. Por lo tanto, no encontrarás ningún uso
94 | de una estructura que no haya sido previamente definida. No dudes en releer partes antiguas si alguna
95 | vez sientes incomodidad con estos tipos.
96 | Al final habrá un glosario/vademécum que sintetizará todos estos conceptos.
97 |
98 | ### ¿De dónde vienen métodos como map, filter, compose...?
99 |
100 | > ver [#198]
101 |
102 | La mayor parte del tiempo, estos métodos están definidos en librerías de proveedores específicos como
103 | `ramda` o `underscore`. Deberías también echarle un vistazo al [Apéndice A](./appendix_a-es.md),
104 | [Apéndice B](./appendix_b-es.md) y [Apéndice C](./appendix_c-es.md) en los cuales se definen
105 | numerosas implementaciones utilizadas para los ejercicios. Estas funciones son realmente comunes
106 | en programación funcional y a pesar de que sus implementaciones pueden variar un poco, sus
107 | significados permanecen bastante constantes entre librerías.
108 |
109 |
110 | [#6]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/6
111 | [#50]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/50
112 | [#62]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/62
113 | [#77]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/77
114 | [#83]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/83
115 | [#109]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/109
116 | [#176]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/176
117 | [#192]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/192
118 | [#194]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/194
119 | [#198]: https://github.com/MostlyAdequate/mostly-adequate-guide/issues/198
120 | [#235]: https://github.com/MostlyAdequate/mostly-adequate-guide/pull/235
121 |
--------------------------------------------------------------------------------
/ch02-es.md:
--------------------------------------------------------------------------------
1 | # Capítulo 02: Funciones de Primera Clase
2 |
3 | ## Un Repaso Rápido
4 | Cuando decimos que las funciones son de "primera clase", queremos decir que son como cualquier otra clase... o sea, una clase normal. Podemos tratar a las funciones como a cualquier otro tipo de dato y no hay nada particularmente especial en ellas; pueden ser almacenadas en arreglos, pasadas como parámetros de otras funciones, asignadas a variables, y lo que quieras.
5 |
6 | Esto es de primero de JavaScript, pero vale la pena mencionarlo, pues una búsqueda rápida de código en GitHub mostrará la evasión colectiva, o tal vez la ignorancia generalizada de este concepto. ¿Deberíamos poner un ejemplo ficticio? Deberíamos.
7 |
8 | ```js
9 | const hi = name => `Hi ${name}`;
10 | const greeting = name => hi(name);
11 | ```
12 |
13 | Aquí, en `greeting`, la función que envuelve a `hi` es completamente redundante. ¿Por qué? Porque las funciones son *llamables* en JavaScript. Cuando `hi` tiene los `()` al final, se ejecutará y devolverá un valor. Cuando no los tiene, simplemente devolverá la función almacenada en la variable. Solo para asegurarte, echa un vistazo tú mismo.
14 |
15 | ```js
16 | hi; // name => `Hi ${name}`
17 | hi("jonas"); // "Hi jonas"
18 | ```
19 |
20 | Dado que `greeting` tan solo está llamando a `hi` con el mismo argumento, podríamos simplemente escribir:
21 |
22 | ```js
23 | const greeting = hi;
24 | greeting("times"); // "Hi times"
25 | ```
26 |
27 | En otras palabras, `hi` ya es una función que espera un argumento, ¿por qué colocar otra función alrededor de ella que simplemente llame a `hi` con el mismo condenado argumento? No tiene ningún maldito sentido. Es como ponerte tu parka más pesada al final de un julio mortal solo para subir el aire acondicionado y pedir un helado.
28 |
29 | Rodear una función con otra función simplemente para retrasar la evaluación es demasiado detallado y también una mala práctica. (Veremos por qué en un momento, pero tiene que ver con el mantenimiento.)
30 |
31 | Es fundamental comprender bien esto antes de continuar, así que vamos a examinar algunos otros divertidos ejemplos extraídos de paquetes de npm.
32 |
33 | ```js
34 | // ignorante
35 | const getServerStuff = callback => ajaxCall(json => callback(json));
36 |
37 | // iluminado
38 | const getServerStuff = ajaxCall;
39 | ```
40 |
41 | El mundo está repleto de código ajax exactamente igual a este. He aquí la razón por la que ambos son equivalentes:
42 |
43 | ```js
44 | // esta línea
45 | ajaxCall(json => callback(json));
46 |
47 | // es lo mismo que esta línea
48 | ajaxCall(callback);
49 |
50 | // así que refactorizamos getServerStuff
51 | const getServerStuff = callback => ajaxCall(callback);
52 |
53 | // ...la cual es equivalente a esto
54 | const getServerStuff = ajaxCall; // <-- mira mama, sin ()'s
55 | ```
56 |
57 | Y así, amigos, es cómo se hace. Uno más para que entendamos por qué estoy siendo tan insistente.
58 |
59 | ```js
60 | const BlogController = {
61 | index(posts) { return Views.index(posts); },
62 | show(post) { return Views.show(post); },
63 | create(attrs) { return Db.create(attrs); },
64 | update(post, attrs) { return Db.update(post, attrs); },
65 | destroy(post) { return Db.destroy(post); },
66 | };
67 | ```
68 |
69 | Este ridículo controlador es 99% aire. Podríamos reescribirlo como:
70 |
71 | ```js
72 | const BlogController = {
73 | index: Views.index,
74 | show: Views.show,
75 | create: Db.create,
76 | update: Db.update,
77 | destroy: Db.destroy
78 | };
79 | ```
80 |
81 | ...o desecharlo por completo, puesto que no hace más que agrupar `Views` y `Db`.
82 |
83 | ## ¿Por Qué Favorecer a las Funciones de Primera Clase?
84 |
85 | Vale, vayamos a las razones por las que favorecer a las funciones de primera clase. Como vimos en los ejemplos `getServerStuff` y `BlogController`, es fácil agregar capas de indirección que no añaden ningún valor y que lo único que hacen es incrementar la cantidad de código en el que rebuscar y que mantener.
86 |
87 | Además, si cambia una función que estamos envolviendo innecesariamente, también deberemos cambiar la función que la envuelve.
88 |
89 | ```js
90 | httpGet('/post/2', json => renderPost(json));
91 | ```
92 |
93 | Si `httpGet` cambiase para enviar un posible error (`err`), necesitaríamos cambiar la función interna.
94 |
95 | ```js
96 | // ir a cada llamada a httpGet en la aplicación y pasar explícitamente err.
97 | httpGet('/post/2', (json, err) => renderPost(json, err));
98 | ```
99 |
100 | Si la hubiéramos escrito como una función de primera clase, mucho menos necesitaríamos cambiar:
101 |
102 | ```js
103 | // renderPost es llamada desde dentro de httpGet, con todos los argumentos que quiera
104 | httpGet('/post/2', renderPost);
105 | ```
106 |
107 | Si no eliminamos las funciones innecesarias deberemos nombrar a los argumentos y hacer referencia a ellos. Los nombres son algo problemáticos, ya sabes. Existen potenciales errores de nombrado, especialmente cuando la base de código envejece y los requerimientos cambian.
108 |
109 | Tener múltiples nombres para el mismo concepto suele ser una fuente de confusión en los proyectos. También existe el problema del código genérico. Por ejemplo, estas dos funciones hacen exactamente lo mismo, pero una es infinitamente más general y reusable.
110 |
111 | ```js
112 | // específico para nuestro blog actual
113 | const validArticles = articles =>
114 | articles.filter(article => article !== null && article !== undefined),
115 |
116 | // mucho más relevante para futuros proyectos
117 | const compact = xs => xs.filter(x => x !== null && x !== undefined);
118 | ```
119 |
120 | Usando nombres concretos, aparentemente nos atamos a datos específicos (en este caso `articles`). Esto sucede bastante a menudo y es una fuente de mucha de la reinvención.
121 |
122 | Debo mencionar que, al igual que con código orientado a objetos, debes ser consciente de que `this` puede morderte en la yugular. Si una función subyacente usa `this` y la llamamos como si fuese de primera clase, estamos sujetos a esta cólera de la abstracción con fugas.
123 |
124 | ```js
125 | const fs = require('fs');
126 |
127 | // aterrador
128 | fs.readFile('freaky_friday.txt', Db.save);
129 |
130 | // no tanto
131 | fs.readFile('freaky_friday.txt', Db.save.bind(Db));
132 | ```
133 |
134 | Después de haber sido enlazada a sí misma, `Db` es libre de acceder a su prototípico código basura. Yo evito usar `this` de la misma manera que evito usar un pañal sucio. Realmente no hay ninguna necesidad cuando se escribe código funcional. Sin embargo, al interactuar con otras bibliotecas, tendrás que aceptar el loco mundo que nos rodea.
135 |
136 | Hay quien argumentará que `this` es necesario para optimizar la velocidad. Si te va la micro-optimización, por favor cierra este libro. Si no puedes recuperar tu dinero, quizás puedas intercambiarlo por algo más complejo.
137 |
138 | Y con esto estamos listos para seguir adelante.
139 |
140 | [Capítulo 3: Pura Felicidad con Funciones Puras](ch03-es.md)
141 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [Chapter 01: What ever are we doing?](ch01.md)
4 | * [Introductions](ch01.md#introductions)
5 | * [A brief encounter](ch01.md#a-brief-encounter)
6 | * [Chapter 02: First Class Functions](ch02.md)
7 | * [A quick review](ch02.md#a-quick-review)
8 | * [Why favor first class?](ch02.md#why-favor-first-class)
9 | * [Chapter 03: Pure Happiness with Pure Functions](ch03.md)
10 | * [Oh to be pure again](ch03.md#oh-to-be-pure-again)
11 | * [Side effects may include...](ch03.md#side-effects-may-include)
12 | * [8th grade math](ch03.md#8th-grade-math)
13 | * [The case for purity](ch03.md#the-case-for-purity)
14 | * [In Summary](ch03.md#in-summary)
15 | * [Chapter 04: Currying](ch04.md)
16 | * [Can't live if livin' is without you](ch04.md#cant-live-if-livin-is-without-you)
17 | * [More than a pun / Special sauce](ch04.md#more-than-a-pun--special-sauce)
18 | * [In Summary](ch04.md#in-summary)
19 | * [Exercises](ch04.md#exercises)
20 | * [Chapter 05: Coding by Composing](ch05.md)
21 | * [Functional Husbandry](ch05.md#functional-husbandry)
22 | * [Pointfree](ch05.md#pointfree)
23 | * [Debugging](ch05.md#debugging)
24 | * [Category Theory](ch05.md#category-theory)
25 | * [In Summary](ch05.md#in-summary)
26 | * [Exercises](ch05.md#exercises)
27 | * [Chapter 06: Example Application](ch06.md)
28 | * [Declarative Coding](ch06.md#declarative-coding)
29 | * [A flickr of functional programming](ch06.md#a-flickr-of-functional-programming)
30 | * [A Principled Refactor](ch06.md#a-principled-refactor)
31 | * [In Summary](ch06.md#in-summary)
32 | * [Chapter 07: Hindley-Milner and Me](ch07.md)
33 | * [What's Your Type?](ch07.md#whats-your-type)
34 | * [Tales from the Cryptic](ch07.md#tales-from-the-cryptic)
35 | * [Narrowing the Possibility](ch07.md#narrowing-the-possibility)
36 | * [Free as in Theorem](ch07.md#free-as-in-theorem)
37 | * [Constraints](ch07.md#constraints)
38 | * [In Summary](ch07.md#in-summary)
39 | * [Chapter 08: Tupperware](ch08.md)
40 | * [The Mighty Container](ch08.md#the-mighty-container)
41 | * [My First Functor](ch08.md#my-first-functor)
42 | * [Schrödinger's Maybe](ch08.md#schrödingers-maybe)
43 | * [Use Cases](ch08.md#use-cases)
44 | * [Releasing the Value](ch08.md#releasing-the-value)
45 | * [Pure Error Handling](ch08.md#pure-error-handling)
46 | * [Old McDonald Had Effects...](ch08.md#old-mcdonald-had-effects)
47 | * [Asynchronous Tasks](ch08.md#asynchronous-tasks)
48 | * [A Spot of Theory](ch08.md#a-spot-of-theory)
49 | * [In Summary](ch08.md#in-summary)
50 | * [Exercises](ch08.md#exercises)
51 | * [Chapter 09: Monadic Onions](ch09.md)
52 | * [Pointy Functor Factory](ch09.md#pointy-functor-factory)
53 | * [Mixing Metaphors](ch09.md#mixing-metaphors)
54 | * [My Chain Hits My Chest](ch09.md#my-chain-hits-my-chest)
55 | * [Power Trip](ch09.md#power-trip)
56 | * [Theory](ch09.md#theory)
57 | * [In Summary](ch09.md#in-summary)
58 | * [Exercises](ch09.md#exercises)
59 | * [Chapter 10: Applicative Functors](ch10.md)
60 | * [Applying Applicatives](ch10.md#applying-applicatives)
61 | * [Ships in Bottles](ch10.md#ships-in-bottles)
62 | * [Coordination Motivation](ch10.md#coordination-motivation)
63 | * [Bro, Do You Even Lift?](ch10.md#bro-do-you-even-lift)
64 | * [Operators](ch10.md#operators)
65 | * [Free Can Openers](ch10.md#free-can-openers)
66 | * [Laws](ch10.md#laws)
67 | * [Identity](ch10.md#identity)
68 | * [Homomorphism](ch10.md#homomorphism)
69 | * [Interchange](ch10.md#interchange)
70 | * [Composition](ch10.md#composition)
71 | * [In Summary](ch10.md#in-summary)
72 | * [Exercises](ch10.md#exercises)
73 | * [Chapter 11: Transform Again, Naturally](ch11.md)
74 | * [Curse This Nest](ch11.md#curse-this-nest)
75 | * [A Situational Comedy](ch11.md#a-situational-comedy)
76 | * [All Natural](ch11.md#all-natural)
77 | * [Principled Type Conversions](ch11.md#principled-type-conversions)
78 | * [Feature Envy](ch11.md#feature-envy)
79 | * [Isomorphic JavaScript](ch11.md#isomorphic-javascript)
80 | * [A Broader Definition](ch11.md#a-broader-definition)
81 | * [One Nesting Solution](ch11.md#one-nesting-solution)
82 | * [In Summary](ch11.md#in-summary)
83 | * [Exercises](ch11.md#exercises)
84 | * [Chapter 12: Traversing the Stone](ch12.md)
85 | * [Types n' Types](ch12.md#types-n-types)
86 | * [Type Feng Shui](ch12.md#type-feng-shui)
87 | * [Effect Assortment](ch12.md#effect-assortment)
88 | * [Waltz of the Types](ch12.md#waltz-of-the-types)
89 | * [No Law and Order](ch12.md#no-law-and-order)
90 | * [In Summary](ch12.md#in-summary)
91 | * [Exercises](ch12.md#exercises)
92 | * [Chapter 13: Monoids bring it all together](ch13.md)
93 | * [Wild combination](ch13.md#wild-combination)
94 | * [Abstracting addition](ch13.md#abstracting-addition)
95 | * [All my favourite functors are semigroups.](ch13.md#all-my-favourite-functors-are-semigroups)
96 | * [Monoids for nothing](ch13.md#monoids-for-nothing)
97 | * [Folding down the house](ch13.md#folding-down-the-house)
98 | * [Not quite a monoid](ch13.md#not-quite-a-monoid)
99 | * [Grand unifying theory](ch13.md#grand-unifying-theory)
100 | * [Group theory or Category theory?](ch13.md#group-theory-or-category-theory)
101 | * [In summary](ch13.md#in-summary)
102 | * [Exercises](ch13.md#exercises)
103 | * [Appendix A: Essential Functions Support](appendix_a.md)
104 | * [always](appendix_a.md#always)
105 | * [compose](appendix_a.md#compose)
106 | * [curry](appendix_a.md#curry)
107 | * [either](appendix_a.md#either)
108 | * [identity](appendix_a.md#identity)
109 | * [inspect](appendix_a.md#inspect)
110 | * [left](appendix_a.md#left)
111 | * [liftA\*](appendix_a.md#lifta)
112 | * [maybe](appendix_a.md#maybe)
113 | * [nothing](appendix_a.md#nothing)
114 | * [reject](appendix_a.md#reject)
115 | * [Appendix B: Algebraic Structures Support](appendix_b.md)
116 | * [Compose](appendix_b.md#compose)
117 | * [Either](appendix_b.md#either)
118 | * [Identity](appendix_b.md#identity)
119 | * [IO](appendix_b.md#io)
120 | * [List](appendix_b.md#list)
121 | * [Map](appendix_b.md#map)
122 | * [Maybe](appendix_b.md#maybe)
123 | * [Task](appendix_b.md#task)
124 | * [Appendix C: Pointfree Utilities](appendix_c.md)
125 | * [add](appendix_c.md#add)
126 | * [append](appendix_c.md#append)
127 | * [chain](appendix_c.md#chain)
128 | * [concat](appendix_c.md#concat)
129 | * [eq](appendix_c.md#eq)
130 | * [filter](appendix_c.md#filter)
131 | * [flip](appendix_c.md#flip)
132 | * [forEach](appendix_c.md#foreach)
133 | * [head](appendix_c.md#head)
134 | * [intercalate](appendix_c.md#intercalate)
135 | * [join](appendix_c.md#join)
136 | * [last](appendix_c.md#last)
137 | * [map](appendix_c.md#map)
138 | * [match](appendix_c.md#match)
139 | * [prop](appendix_c.md#prop)
140 | * [reduce](appendix_c.md#reduce)
141 | * [replace](appendix_c.md#replace)
142 | * [safeHead](appendix_c.md#safehead)
143 | * [safeLast](appendix_c.md#safelast)
144 | * [safeProp](appendix_c.md#safeprop)
145 | * [sequence](appendix_c.md#sequence)
146 | * [sortBy](appendix_c.md#sortby)
147 | * [split](appendix_c.md#split)
148 | * [take](appendix_c.md#take)
149 | * [toLowerCase](appendix_c.md#tolowercase)
150 | * [toString](appendix_c.md#tostring)
151 | * [toUpperCase](appendix_c.md#touppercase)
152 | * [traverse](appendix_c.md#traverse)
153 | * [unsafePerformIO](appendix_c.md#unsafeperformio)
154 |
--------------------------------------------------------------------------------
/ch01.md:
--------------------------------------------------------------------------------
1 | # Chapter 01: What Ever Are We Doing?
2 |
3 | ## Introductions
4 |
5 | Hi there! I'm Professor Franklin Frisby. Pleased to make your acquaintance. We'll be spending some time together, as I'm supposed to teach you a bit about functional programming. But enough about me, what about you? I'm hoping that you're at least a bit familiar with the JavaScript language, have a teensy bit of Object-Oriented experience, and fancy yourself a working class programmer. You don't need to have a PhD in Entomology, you just need to know how to find and kill some bugs.
6 |
7 | I won't assume that you have any previous functional programming knowledge, because we both know what happens when you assume. I will, however, expect you to have run into some of the unfavorable situations that arise when working with mutable state, unrestricted side effects, and unprincipled design. Now that we've been properly introduced, let's get on with it.
8 |
9 | The purpose of this chapter is to give you a feel for what we're after when we write functional programs. In order to be able to understand the following chapters, we must have some idea about what makes a program *functional*. Otherwise we'll find ourselves scribbling aimlessly, avoiding objects at all costs - a clumsy endeavor indeed. We need a clear bullseye to hurl our code at, some celestial compass for when the waters get rough.
10 |
11 | Now, there are some general programming principles - various acronymic credos that guide us through the dark tunnels of any application: DRY (don't repeat yourself), YAGNI (ya ain't gonna need it), loose coupling high cohesion, the principle of least surprise, single responsibility, and so on.
12 |
13 | I won't belabor you by listing each and every guideline I've heard throughout the years... The point of the matter is that they hold up in a functional setting, although they're merely tangential to our ultimate goal. What I'd like you to get a feel for now, before we get any further, is our intention when we poke and prod at the keyboard; our functional Xanadu.
14 |
15 |
16 |
17 | ## A Brief Encounter
18 |
19 | Let's start with a touch of insanity. Here is a seagull application. When flocks conjoin they become a larger flock, and when they breed, they increase by the number of seagulls with whom they're breeding. Now, this is not intended to be good Object-Oriented code, mind you, it is here to highlight the perils of our modern, assignment based approach. Behold:
20 |
21 | ```js
22 | class Flock {
23 | constructor(n) {
24 | this.seagulls = n;
25 | }
26 |
27 | conjoin(other) {
28 | this.seagulls += other.seagulls;
29 | return this;
30 | }
31 |
32 | breed(other) {
33 | this.seagulls = this.seagulls * other.seagulls;
34 | return this;
35 | }
36 | }
37 |
38 | const flockA = new Flock(4);
39 | const flockB = new Flock(2);
40 | const flockC = new Flock(0);
41 | const result = flockA
42 | .conjoin(flockC)
43 | .breed(flockB)
44 | .conjoin(flockA.breed(flockB))
45 | .seagulls;
46 | // 32
47 | ```
48 |
49 | Who on earth would craft such a ghastly abomination? It is unreasonably difficult to keep track of the mutating internal state. And, good heavens, the answer is even incorrect! It should have been `16`, but `flockA` wound up permanently altered in the process. Poor `flockA`. This is anarchy in the I.T.! This is wild animal arithmetic!
50 |
51 | If you don't understand this program, it's okay, neither do I. The point to remember here is that state and mutable values are hard to follow, even in such a small example.
52 |
53 | Let's try again, this time using a more functional approach:
54 |
55 | ```js
56 | const conjoin = (flockX, flockY) => flockX + flockY;
57 | const breed = (flockX, flockY) => flockX * flockY;
58 |
59 | const flockA = 4;
60 | const flockB = 2;
61 | const flockC = 0;
62 | const result =
63 | conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB));
64 | // 16
65 | ```
66 |
67 | Well, this time we got the right answer. With much less code. The function nesting is a tad confusing... (we'll remedy this situation in ch5). It's better, but let's dig a little bit deeper. There are benefits to calling a spade a spade. Had we scrutinized our custom functions more closely, we would have discovered that we're just working with simple addition (`conjoin`) and multiplication (`breed`).
68 |
69 | There's really nothing special at all about these two functions other than their names. Let's rename our custom functions to `multiply` and `add` in order to reveal their true identities.
70 |
71 | ```js
72 | const add = (x, y) => x + y;
73 | const multiply = (x, y) => x * y;
74 |
75 | const flockA = 4;
76 | const flockB = 2;
77 | const flockC = 0;
78 | const result =
79 | add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
80 | // 16
81 | ```
82 | And with that, we gain the knowledge of the ancients:
83 |
84 | ```js
85 | // associative
86 | add(add(x, y), z) === add(x, add(y, z));
87 |
88 | // commutative
89 | add(x, y) === add(y, x);
90 |
91 | // identity
92 | add(x, 0) === x;
93 |
94 | // distributive
95 | multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));
96 | ```
97 |
98 | Ah yes, those old faithful mathematical properties should come in handy. Don't worry if you didn't know them right off the top of your head. For a lot of us, it's been a while since we learned about these laws of arithmetic. Let's see if we can use these properties to simplify our little seagull program.
99 |
100 | ```js
101 | // Original line
102 | add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
103 |
104 | // Apply the identity property to remove the extra add
105 | // (add(flockA, flockC) == flockA)
106 | add(multiply(flockB, flockA), multiply(flockA, flockB));
107 |
108 | // Apply distributive property to achieve our result
109 | multiply(flockB, add(flockA, flockA));
110 | ```
111 |
112 | Brilliant! We didn't have to write a lick of custom code other than our calling function. We include `add` and `multiply` definitions here for completeness, but there is really no need to write them - we surely have an `add` and `multiply` provided by some existing library.
113 |
114 | You may be thinking "how very strawman of you to put such a mathy example up front". Or "real programs are not this simple and cannot be reasoned about in such a way." I've chosen this example because most of us already know about addition and multiplication, so it's easy to see how math is very useful for us here.
115 |
116 | Don't despair - throughout this book, we'll sprinkle in some category theory, set theory, and lambda calculus and write real world examples that achieve the same elegant simplicity and results as our flock of seagulls example. You needn't be a mathematician either. It will feel natural and easy, just like you were using a "normal" framework or API.
117 |
118 | It may come as a surprise to hear that we can write full, everyday applications along the lines of the functional analog above. Programs that have sound properties. Programs that are terse, yet easy to reason about. Programs that don't reinvent the wheel at every turn. Lawlessness is good if you're a criminal, but in this book, we'll want to acknowledge and obey the laws of math.
119 |
120 | We'll want to use a theory where every piece tends to fit together so politely. We'll want to represent our specific problem in terms of generic, composable bits and then exploit their properties for our own selfish benefit. It will take a bit more discipline than the "anything goes" approach of imperative programming (we'll go over the precise definition of "imperative" later in the book, but for now consider it anything other than functional programming). The payoff of working within a principled, mathematical framework will truly astound you.
121 |
122 | We've seen a flicker of our functional northern star, but there are a few concrete concepts to grasp before we can really begin our journey.
123 |
124 | [Chapter 02: First Class Functions](ch02.md)
125 |
126 |
--------------------------------------------------------------------------------
/ch01-es.md:
--------------------------------------------------------------------------------
1 | # Capítulo 01: ¿Qué Estamos Haciendo?
2 |
3 | ## Presentaciones
4 |
5 | ¡Hola! Soy el Profesor Franklin Frisby, encantado de conocerte. Vamos a pasar algo de tiempo juntos, pues se supone que voy a enseñarte algo de programación funcional. Pero basta de hablar sobre mí, ¿qué hay de ti? Espero que el lenguaje JavaScript te sea por lo menos familiar, que tengas algo de experiencia en programación orientada a objetos, y que te apetezca convertirte en un programador de bandera. No necesitas tener un doctorado en entomología, solo necesitas saber cómo encontrar y matar algunos bugs [*bichos*].
6 |
7 | No asumo que tengas ningún conocimiento previo sobre programación funcional porque ya sabemos lo que sucede cuando uno presupone, pero espero que hayas encontrado problemas al trabajar con estados mutables, con efectos secundarios no restringidos, y con diseño sin principios. Ahora que ya nos hemos presentado, sigamos adelante.
8 |
9 | El propósito de este capítulo es darte una idea de lo que buscamos cuando escribimos programas funcionales. Para poder entender los próximos capítulos, hemos de tener una idea sobre qué hace que un programa sea *funcional*. De lo contrario, acabaremos garabateando sin rumbo, evitando objetos a toda costa; un esfuerzo sin sentido. Necesitamos una diana a la que lanzar nuestro código, una brújula celestial para cuando las aguas se agiten.
10 |
11 | Hay ciertos principios de programación, varios acrónimos, que nos guiarán a través de los túneles oscuros de cualquier aplicación: DRY (don't repeat yourself [*no te repitas*]), YAGNI (ya ain't gonna need it [*no lo vas a necesitar*]), alta cohesión bajo acoplamiento, principio de mínima sorpresa, responsabilidad única, etc.
12 |
13 | No voy a alargarme enumerando cada una de las guías que he escuchado a lo largo de los años... La cuestión es que siguen vigentes en un entorno funcional, aunque de forma meramente tangencial a nuestro objetivo final.
14 | Lo que me gustaría que entendieses por ahora, antes de seguir adelante, es cuál será nuestra intención cuando nos aferremos al teclado; nuestro Xanadú funcional.
15 |
16 |
17 |
18 | ## Un Breve Encuentro
19 |
20 | Vamos a empezar con un toque de locura. He aquí una aplicación de gaviotas [*seagulls*]. Cuando una bandada [*flock*] se junta con otra [*conjoin*], se convierten en una bandada más grande y cuando se reproducen [*breed*], aumentan por el número de gaviotas con las que se reproducen. Ahora bien, este no pretende ser un buen ejemplo de código orientado a objetos, ojo, este código está aquí para resaltar los peligros de nuestro moderno enfoque basado en asignación. Contempla:
21 |
22 | ```js
23 | class Flock {
24 | constructor(n) {
25 | this.seagulls = n;
26 | }
27 |
28 | conjoin(other) {
29 | this.seagulls += other.seagulls;
30 | return this;
31 | }
32 |
33 | breed(other) {
34 | this.seagulls = this.seagulls * other.seagulls;
35 | return this;
36 | }
37 | }
38 |
39 | const flockA = new Flock(4);
40 | const flockB = new Flock(2);
41 | const flockC = new Flock(0);
42 | const result = flockA
43 | .conjoin(flockC)
44 | .breed(flockB)
45 | .conjoin(flockA.breed(flockB))
46 | .seagulls;
47 | // 32
48 | ```
49 |
50 | ¿Quién en la faz de la tierra, sería capaz de crear tan espantosa abominación? Es irrazonablemente difícil seguir el rastro del estado interno mientras muta. Y, por si esto fuera poco, ¡la respuesta es incorrecta! Debería ser `16`, pero `flockA` ha sido alterado permanentemente durante el proceso. Pobre `flockA`. ¡Esto es anarquía en la informática! ¡Esto es aritmética de animales salvajes!
51 |
52 | Si no entiendes este programa, no pasa nada, yo tampoco lo entiendo. La cuestión es que el estado y los valores mutables son difíciles de seguir, incluso en un ejemplo tan pequeño.
53 |
54 | Vamos a intentarlo de nuevo, esta vez con un enfoque más funcional:
55 |
56 | ```js
57 | const conjoin = (flockX, flockY) => flockX + flockY;
58 | const breed = (flockX, flockY) => flockX * flockY;
59 |
60 | const flockA = 4;
61 | const flockB = 2;
62 | const flockC = 0;
63 | const result =
64 | conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB));
65 | // 16
66 | ```
67 |
68 | Bueno, esta vez la respuesta es correcta. Con mucho menos código. La anidación de funciones es algo confusa... (pondremos remedio a esto en el capítulo 5). Está mejor, pero profundicemos un poco más. Llamar a las cosas por su nombre tiene sus ventajas. Si hubiéramos examinado nuestras funciones más de cerca, habríamos descubierto que estamos utilizando simples sumas (`conjoin`) y multiplicaciones (`breed`).
69 |
70 | Realmente no hay nada especial en estas dos funciones aparte de sus nombres. Vamos a renombrarlas a `multiply` [*multiplicar*] y `add` [*añadir*] para revelar sus verdaderas identidades.
71 |
72 | ```js
73 | const add = (x, y) => x + y;
74 | const multiply = (x, y) => x * y;
75 |
76 | const flockA = 4;
77 | const flockB = 2;
78 | const flockC = 0;
79 | const result =
80 | add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
81 | // 16
82 | ```
83 | Y con esto obtenemos el conocimiento de los antiguos:
84 |
85 | ```js
86 | // asociativa
87 | add(add(x, y), z) === add(x, add(y, z));
88 |
89 | // conmutativa
90 | add(x, y) === add(y, x);
91 |
92 | // identidad
93 | add(x, 0) === x;
94 |
95 | // distributiva
96 | multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));
97 | ```
98 |
99 | Ah, sí, esas viejas y fieles propiedades matemáticas serán de ayuda. No te preocupes si no las sabes de memoria. Puede que haya pasado mucho tiempo desde que las estudiamos. Vamos a ver si podemos utilizar estas propiedades para simplificar nuestra pequeña aplicación de gaviotas.
100 |
101 | ```js
102 | // Línea original
103 | add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
104 |
105 | // Aplicamos la propiedad de identidad para eliminar la suma sobrante
106 | // (add(flockA, flockC) == flockA)
107 | add(multiply(flockB, flockA), multiply(flockA, flockB));
108 |
109 | // Aplicamos la propiedad distributiva para llegar a nuestro resultado
110 | multiply(flockB, add(flockA, flockA));
111 | ```
112 |
113 | ¡Brillante! No hemos tenido que escribir ni una pizca de código aparte de las llamadas a las funciones. Hemos incluído las implementaciones de `add` y `multiply` por completitud, pero en realidad no hacía falta escribirlas, puesto que seguro que ya existen en alguna librería.
114 |
115 | Seguramente estarás pensando "qué pícaro, al poner este ejemplo". O "en la realidad los programas no son tan simples y no se puede razonar sobre ellos de esta manera". He seleccionado este ejemplo porque la mayoría ya sabemos sumar y multiplicar, y así es fácil ver cómo las matemáticas pueden sernos útiles.
116 |
117 | No te desesperes, a lo largo de este libro hablaremos un poco sobre teoría de categorías, teoría de conjuntos, y cálculo lambda para escribir ejemplos de la vida real que consigan la misma elegante simplicidad y que resulten como nuestro ejemplo de la bandada de gaviotas. Tampoco necesitas tener un título en matemáticas, será sencillo y natural, como utilizar otro framework u otra API.
118 |
119 | Puede resultar sorprendente oír que se pueden escribir aplicaciones completas y reales utilizando programación funcional tal y como hemos mostrado en el ejemplo anterior. Programas con sólidas propiedades. Programas cortos sobre los que razonar fácilmente. Programas que no reinventan la rueda una y otra vez. La falta de leyes es buena si vas a cometer un crimen, pero en este libro, vamos a reconocer y a obedecer a las leyes de las matemáticas.
120 |
121 | Querremos utilizar una teoría en la que todas las piezas tiendan a encajar limpiamente. Querremos representar nuestro problema específico en términos de pequeñas piezas genéricas y combinables, para luego explotar sus propiedades en nuestro propio beneficio. Será necesaria un poco más de disciplina que en el enfoque del "todo vale" de la programación imperativa (más adelante definiremos más precisamente qué es la programación imperativa, pero por ahora considérala cualquier cosa que no sea programación funcional). La recompensa de trabajar dentro de un marco matemático basado en principios verdaderamente te asombrará.
122 |
123 | Hemos visto un destello de nuestra estrella del norte funcional, pero hay unos cuantos conceptos que necesitamos entender antes de realmente poder emprender nuestro viaje.
124 |
125 | [Capítulo 2: Funciones de Primera Clase](ch02-es.md)
126 |
--------------------------------------------------------------------------------
/ch04.md:
--------------------------------------------------------------------------------
1 | # Chapter 04: Currying
2 |
3 | ## Can't Live If Livin' Is without You
4 | My Dad once explained how there are certain things one can live without until one acquires them. A microwave is one such thing. Smart phones, another. The older folks among us will remember a fulfilling life sans internet. For me, currying is on this list.
5 |
6 | The concept is simple: You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments.
7 |
8 | You can choose to call it all at once or simply feed in each argument piecemeal.
9 |
10 | ```js
11 | const add = x => y => x + y;
12 | const increment = add(1);
13 | const addTen = add(10);
14 |
15 | increment(2); // 3
16 | addTen(2); // 12
17 | ```
18 |
19 | Here we've made a function `add` that takes one argument and returns a function. By calling it, the returned function remembers the first argument from then on via the closure. Calling it with both arguments all at once is a bit of a pain, however, so we can use a special helper function called `curry` to make defining and calling functions like this easier.
20 |
21 | Let's set up a few curried functions for our enjoyment. From now on, we'll summon our `curry`
22 | function defined in the [Appendix A - Essential Function Support](./appendix_a.md).
23 |
24 | ```js
25 | const match = curry((what, s) => s.match(what));
26 | const replace = curry((what, replacement, s) => s.replace(what, replacement));
27 | const filter = curry((f, xs) => xs.filter(f));
28 | const map = curry((f, xs) => xs.map(f));
29 | ```
30 |
31 | The pattern I've followed is a simple, but important one. I've strategically positioned the data we're operating on (String, Array) as the last argument. It will become clear as to why upon use.
32 |
33 | (The syntax `/r/g` is a regular expression that means _match every letter 'r'_. Read [more about regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) if you like.)
34 |
35 | ```js
36 | match(/r/g, 'hello world'); // [ 'r' ]
37 |
38 | const hasLetterR = match(/r/g); // x => x.match(/r/g)
39 | hasLetterR('hello world'); // [ 'r' ]
40 | hasLetterR('just j and s and t etc'); // null
41 |
42 | filter(hasLetterR, ['rock and roll', 'smooth jazz']); // ['rock and roll']
43 |
44 | const removeStringsWithoutRs = filter(hasLetterR); // xs => xs.filter(x => x.match(/r/g))
45 | removeStringsWithoutRs(['rock and roll', 'smooth jazz', 'drum circle']); // ['rock and roll', 'drum circle']
46 |
47 | const noVowels = replace(/[aeiou]/ig); // (r,x) => x.replace(/[aeiou]/ig, r)
48 | const censored = noVowels('*'); // x => x.replace(/[aeiou]/ig, '*')
49 | censored('Chocolate Rain'); // 'Ch*c*l*t* R**n'
50 | ```
51 |
52 | What's demonstrated here is the ability to "pre-load" a function with an argument or two in order to receive a new function that remembers those arguments.
53 |
54 | I encourage you to clone the Mostly Adequate repository (`git clone
55 | https://github.com/MostlyAdequate/mostly-adequate-guide.git`), copy the code above and have a
56 | go at it in the REPL. The curry function, as well as actually anything defined in the appendixes,
57 | are available in the `support/index.js` module.
58 |
59 | Alternatively, have a look at a published version on `npm`:
60 |
61 | ```
62 | npm install @mostly-adequate/support
63 | ```
64 |
65 | ## More Than a Pun / Special Sauce
66 |
67 | Currying is useful for many things. We can make new functions just by giving our base functions some arguments as seen in `hasLetterR`, `removeStringsWithoutRs`, and `censored`.
68 |
69 | We also have the ability to transform any function that works on single elements into a function that works on arrays simply by wrapping it with `map`:
70 |
71 | ```js
72 | const getChildren = x => x.childNodes;
73 | const allTheChildren = map(getChildren);
74 | ```
75 |
76 | Giving a function fewer arguments than it expects is typically called *partial application*. Partially applying a function can remove a lot of boiler plate code. Consider what the above `allTheChildren` function would be with the uncurried `map` from lodash (note the arguments are in a different order):
77 |
78 | ```js
79 | const allTheChildren = elements => map(elements, getChildren);
80 | ```
81 |
82 | We typically don't define functions that work on arrays, because we can just call `map(getChildren)` inline. Same with `sort`, `filter`, and other higher order functions (a *higher order function* is a function that takes or returns a function).
83 |
84 | When we spoke about *pure functions*, we said they take 1 input to 1 output. Currying does exactly this: each single argument returns a new function expecting the remaining arguments. That, old sport, is 1 input to 1 output.
85 |
86 | No matter if the output is another function - it qualifies as pure. We do allow more than one argument at a time, but this is seen as merely removing the extra `()`'s for convenience.
87 |
88 |
89 | ## In Summary
90 |
91 | Currying is handy and I very much enjoy working with curried functions on a daily basis. It is a tool for the belt that makes functional programming less verbose and tedious.
92 |
93 | We can make new, useful functions on the fly simply by passing in a few arguments and as a bonus, we've retained the mathematical function definition despite multiple arguments.
94 |
95 | Let's acquire another essential tool called `compose`.
96 |
97 | [Chapter 05: Coding by Composing](ch05.md)
98 |
99 | ## Exercises
100 |
101 | #### Note about Exercises
102 |
103 | Throughout the book, you might encounter an 'Exercises' section like this one. Exercises can be
104 | done directly in-browser provided you're reading from [gitbook](https://mostly-adequate.gitbooks.io/mostly-adequate-guide) (recommended).
105 |
106 | Note that, for all exercises of the book, you always have a handful of helper functions
107 | available in the global scope. Hence, anything that is defined in [Appendix A](./appendix_a.md),
108 | [Appendix B](./appendix_b.md) and [Appendix C](./appendix_c.md) is available for you! And, as
109 | if it wasn't enough, some exercises will also define functions specific to the problem
110 | they present; as a matter of fact, consider them available as well.
111 |
112 | > Hint: you can submit your solution by doing `Ctrl + Enter` in the embedded editor!
113 |
114 | #### Running Exercises on Your Machine (optional)
115 |
116 | Should you prefer to do exercises directly in files using your own editor:
117 |
118 | - clone the repository (`git clone git@github.com:MostlyAdequate/mostly-adequate-guide.git`)
119 | - go in the *exercises* section (`cd mostly-adequate-guide/exercises`)
120 | - install the necessary plumbing using [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) (`npm install`)
121 | - complete answers by modifying the files named *exercise\_\** in the corresponding chapter's folder
122 | - run the correction with npm (e.g. `npm run ch04`)
123 |
124 | Unit tests will run against your answers and provide hints in case of mistake. By the by, the
125 | answers to the exercises are available in files named *solution\_\**.
126 |
127 | #### Let's Practice!
128 |
129 | {% exercise %}
130 | Refactor to remove all arguments by partially applying the function.
131 |
132 | {% initial src="./exercises/ch04/exercise_a.js#L3;" %}
133 | ```js
134 | const words = str => split(' ', str);
135 | ```
136 |
137 | {% solution src="./exercises/ch04/solution_a.js" %}
138 | {% validation src="./exercises/ch04/validation_a.js" %}
139 | {% context src="./exercises/support.js" %}
140 | {% endexercise %}
141 |
142 |
143 | ---
144 |
145 |
146 | {% exercise %}
147 | Refactor to remove all arguments by partially applying the functions.
148 |
149 | {% initial src="./exercises/ch04/exercise_b.js#L3;" %}
150 | ```js
151 | const filterQs = xs => filter(x => match(/q/i, x), xs);
152 | ```
153 |
154 | {% solution src="./exercises/ch04/solution_b.js" %}
155 | {% validation src="./exercises/ch04/validation_b.js" %}
156 | {% context src="./exercises/support.js" %}
157 | {% endexercise %}
158 |
159 |
160 | ---
161 |
162 |
163 | Considering the following function:
164 |
165 | ```js
166 | const keepHighest = (x, y) => (x >= y ? x : y);
167 | ```
168 |
169 | {% exercise %}
170 | Refactor `max` to not reference any arguments using the helper function `keepHighest`.
171 |
172 | {% initial src="./exercises/ch04/exercise_c.js#L7;" %}
173 | ```js
174 | const max = xs => reduce((acc, x) => (x >= acc ? x : acc), -Infinity, xs);
175 | ```
176 |
177 | {% solution src="./exercises/ch04/solution_c.js" %}
178 | {% validation src="./exercises/ch04/validation_c.js" %}
179 | {% context src="./exercises/support.js" %}
180 | {% endexercise %}
181 |
--------------------------------------------------------------------------------