Argument passed to repeat should be 2
var printMessage = run(console.log, repeat(2), h2, echo);
The second line should be changed to:
let student = db.find(ssn)
For consistency with ES6 syntax used throughout the book, the code should read:
const find = curry((db, id) => { let obj = db.find(id); if(obj === null) { throw new Error('Object not found!'); } return obj; }); const csv = student => `${student.ssn}, ${student.firstname}, ${student.lastname}`; const append = curry((selector, info) => { document.querySelector(selector).innerHTML = info; });
Second column function call should read:
print(plus2(0));
For consistency with ES6 syntax used throughout the book, the code should read:
var sortDesc = arr => arr.sort( (a, b) => b - a );
First full paragraph, second line
"In reality, run
is an alias for one of the most important techniques: composition."
This has a small bug, and can be simplified with the following:
Rx.Observable.fromEvent( document.querySelector('#student-ssn'), 'keyup') .pluck('srcElement', 'value') .map(ssn => ssn.replace(/^\s*|\s*$|\-/g, '')) .filter(ssn => ssn !== null && ssn.length === 9) .subscribe(validSsn => { console.log(`Valid SSN ${validSsn}`); });
Typo detected: "oriented-oriented" should be "object-oriented"
Please use the following figure:
This correction hasn't been made in any version.
All versions contain an extraneous e in R.lenseProp
:
const lastnameLens = R.lensProp('lastname');
"Because lenses implement immutable setters, you can change the nested object and still return a new Person object:"
should be changed to:
"Because lenses implement immutable setters, you can change the nested object and return a new object:"
Rework the code under this statement to:
var newPerson = R.set(zipLens, zipCode('90210', '5678'), person); var newZip = R.view(zipLens, newPerson); //-> zipCode('90210', '5678') var originalZip = R.view(zipLens, person); //-> zipCode('08544', '1234') newZip.toString() !== originalZip.toString(); //-> true
people.sort(( p1, p2) => p1.getAge() - p2.getAge());
As you can see from these examples... on how the function is used (globally, as an object method, as a constructor, and so on)
var MyModule = (function MyModule(export) let _myPrivateVar = ...; export.method1 = function () { // do work }; export.method2 = function () { // do work }; return export; }(MyModule || {}));
In the sidebar titled "The underscore in Lodash". There is a typo in the line "as a fork of the famous and widely used Underscore.js project..."
Bugs in code
function map(arr, fn) { const len = arr.length, result = new Array(len); for (let idx = 0; idx < len; ++idx) { result[idx] = fn(arr[idx], idx, arr); } return result; }
"...map
is exclusively a left-to-right operation; for a right-to-left sweep, you must first
reverse the array. JavaScript's Array.reverse()
operation won't work, because it
mutates the original array in place, but a functional equivalent of reverse
can be connected
with map in a single statement:"
Should be changed to:
"...map
is exclusively a left-to-right operation; for a right-to-left sweep, you must first
reverse the array. For consistency, Lodash's _.reverse()
matches JavaScript's Array.reverse()
,
which means it mutates the original array in place; it's important for you to be aware
of when functions have side effects:"
Remove brackets from optional accumulator parameter. It's confusing
function reduce(arr, fn, accumulator) {
For consistency with ES6 syntax used throughout the book, the code should read:
_(persons).reduce((stat, person) => { const country = person.address.country; stat[country] = _.isUndefined(stat[country]) ? 1 : stat[country] + 1; return stat; }, {});
'city' is an undefined field. The code should read:
const countryPath = ['address','country']; const countryLens = R.lens(R.path(countryPath), R.assocPath(countryPath));
then
_(persons).map(R.view(countryLens)).reduce(gatherStats,{});
For readability, remove additional parenthesis in:
const notAllValid = args => _(args).some(isNotValid);
Fix run code:
validate(['string', 0, null, undefined]); //-> false validate(['string', 0, {}]);//-> true
Should be changed to:
notAllValid(['string', 0, null, undefined]); //-> true notAllValid(['string', 0, {}]); //-> false
Fix result string to:
//-> 'John von Neumann and Alonzo Church'
Change result array to:
['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']
Rework the SQL query as such:
SELECT p.firstname FROM Person p WHERE p.birthYear > 1903 and p.country IS NOT 'US' ORDER BY p.firstname
Rework the mixing as such:
_.mixin({'select': _.map, 'from': _.chain, 'where': _.filter, 'sortBy': _.sortByOrder});
Rework this code to:
_.from(persons) .where(p => p.birthYear > 1903 && p.address.country !== 'US') .sortBy(['firstname']) .select(p => p.firstname) .value();
In this section, I'll explain what recursion is and then work through an exercise that will teach you how to think recursively.
For consistency with ES6 syntax used throughout the book, the code should read:
const Tuple = function( /* types */ ) { const typeInfo = Array.prototype.slice.call(arguments, 0); const _T = function( /* values */ ) { const values = Array.prototype.slice.call(arguments, 0); if(values.some( val => val === null || val === undefined)) { throw new ReferenceError('Tuples may not have any null values'); } if(values.length !== typeInfo.length) { throw new TypeError('Tuple arity does not match its prototype'); } values.map((val, index) => { this['_' + (index + 1)] = checkType(typeInfo[index])(val); }, this); Object.freeze(this); }; _T.prototype.values = () => { return Object.keys(this) .map(k => this[k], this); }; return _T; };
Formal definition of currying should be:
curry(f) :: ((a,b,c) -> d) -> a -> b -> c -> d
Rework code to ES6 syntax
const name = curry2((last,first) => new StringPair(last,first));
Rework code to read (break points remain the same):
// checkType :: Type -> Object -> Object const checkType = R.curry((typeDef, obj) => { if(!R.is(typeDef, obj)) { let type = typeof obj; throw new TypeError(`Type mismatch. Expected [${typeDef}] but found [${type}]`); } return obj; }); checkType(String)('Curry'); //->'Curry' checkType(Number)(3); //-> 3 checkType(Number)(3.5); //-> 3.5 let now = new Date(); checkType(Date)(now); //-> now checkType(Object)({}); //-> {} checkType(String)(42); //-> TypeError
Variable declarations on the fifth line should be:
let position = 0, length = args.length;
Should be:
let position = 0, length = boundArgs.length;
Code snippet at the top. At line 8, the result should be:
[ 'http://example.com', 'http', 'example', 'com' ]
For consistency with ES6 syntax used throughout the book, the code should change from:
const element = R.curry((val, tuple) => new Node(val, tuple));
to:
const element = R.curry((val, tuple) => new Node(val, tuple));
Formal definition of composition should reflect implementation:
Change from:
f ● g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)
to:
f ● g = f(g) = compose :: ((B -> C), (A -> B)) -> (A -> C)
The parenthesis help to clearly define both functions being composed together
First function of findObject() should read:
const findObject = R.curry((db, id) => { const obj = find(db, id); if(obj === null) { throw new Error(`Object with ID [${id}] not found`); } return obj; });
To take advantage of ES6 destructuring, we change the csv() function to:
const csv = ({ssn, firstname, lastname}) => `${ssn}, ${firstname}, ${lastname}`;
Last function of listing 4.11 should read:
const append = R.curry((elementId, info) => { document.querySelector(elementId).innerHTML = info; return info; });
Extra parenthesis at the end of first code snippet
const showStudent = R.compose( append('#student-info'), csv, findStudent, normalize, trim);
"Violate the principle of locality because..."
function getCountry(student) { let school = student.getSchool(); if(school !== null) { let addr = school.getAddress(); if(addr !== null) { return addr.getCountry(); } return null; } throw new Error('Error extracting country info'); }
Typo in "this._value" access in map() and toString()
map(f) { return f(this._value); }; toString() { return 'Wrapper (' + this._value + ')'; }
"it can't be directly retrieved"
Instead of:
wrappedValue.map(log);
Change to:
wrappedValue.map(console.log);
// fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B] fmap (f) { return new Wrapper(f(this._value)); }
Improvement: convert to ES6
const getAddress = student => wrap(student.fmap(R.prop('address')));
The Empty class is currently using ES5 syntax, we can convert to ES6 to match the rest of the book. We remove the first annotation
class Empty map(f) { return this; } // fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B] fmap (_) { return new Empty(); } toString() { return 'Empty ()'; } }; const empty = () => new Empty();
Problem with this._value reference
class Wrapper { constructor(value) { this._value = value; } static of(a) { return new Wrapper(a); } map(f) { return Wrapper.of(f(this._value)); } join() { if(!(this._value instanceof Wrapper)) { return this; } return this._value.join(); } get() { return this._value; } toString() { return `Wrapper (${this._value})`; } }
Rework functions findObject() and getAddress()
// findObject :: DB -> String -> Wrapper const findObject = R.curry((db, id) => Wrapper.of(find(db, id))); // getAddress :: Student -> Wrapper const getAddress = student => Wrapper.of(student.map(R.prop('address')));
Use qualified static calls in fromNullable() method
static fromNullable(a) { return a !== null ? Maybe.just(a) : Maybe.nothing(); }
Fix typo in methods map, filter(f), and toString(). Substitute chain() method in place of isJust()
class Just extends Maybe { constructor(value) { super(); this._value = value; } get value() { return this._value; } map(f) { return Maybe.fromNullable(f(this._value)); } getOrElse() { return this._value; } filter(f) { return Maybe.fromNullable(f(this._value) ? this._value : null); } chain(f) { return f(this._value); } toString () { return `Maybe.Just(${this._value})`; } }
Correct quotes use in error string
get value() { throw new TypeError("Can't extract the value of a Nothing."); }
Fix this._value in filter() method and replace isNothing() with chain()
filter(f) { return this._value; } chain(f) { return this; }
Use ES6 syntax to be consistent with rest of the book
// safeFindObject :: DB, string -> Maybe const safeFindObject = R.curry((db, id) => Maybe.fromNullable(find(db, id)));
lift() is missing the curried function argument f. Converting safeFindObject to ES6
Improve safeFindObject
const safeFindObject = R.curry((db, id) => Maybe.fromNullable(find(db, id)));
Improvement: Function lift()
const lift = R.curry((f, value) => Maybe.fromNullable(value).map(f));
Improvement: findObject() to ES6
const findObject = R.curry((db, id) => find(db, id));
Second instance of safeFindObject() changes to:
const safeFindObject = R.compose(lift(console.log), findObject);
"Constructor function for Either. This can hold an exception or a successful value (right bias). For brevity, I'll skip boolean properties isRight and isLeft in this class structure since their implementations are trivial. You can find Either fully implemented in the code repository."
static fromNullable(val) { return val !== null && val !== undefined ? Either.right(val) : Either.left(val); } static of(a) { return Either.right(a); }
Class Left
get value() { throw new TypeError("Can't extract the value of a Left(a)."); } orElse(f) { return f(this._value); } toString() { return `Either.Left(${this._value})`; }
Class Right
map(f) { return Either.of(f(this._value)); } getOrElse(other) { return this._value; } chain(f) { return f(this._value); } filter(f) { return Either.fromNullable(f(this._value) ? this._value : null); } toString() { return `Either.Right(${this._value})`; }
Lowercase Either.left() in safeFindObject and convert to ES6 (improvement)
const safeFindObject = R.curry((db, id) => { const obj = find(db, id); if(obj) { return Either.of(obj); } return Either.left(`Object not found with ID: ${id}`); });
The annotation references the wrong section. It should read:
"This function was created in section 4.4.1"
Simplify the selector string
const read = (document, selector) => document.querySelector(selector).innerHTML; const write = (document, selector, val) => { document.querySelector(selector).innerHTML = val; return val; };
Modify method map to use lambda expression
map(fn) { let self = this; return new IO(() => fn(self.effect())); }
Simplify the selector string
const read = (document, selector) => () => document.querySelector(selector).innerHTML; const write = (document, selector) => { return (val) => { document.querySelector(selector).innerHTML = val; return val; }; };
Code should change to:
const changeToStartCase = IO.from(readDom('#student-name')) .map(_.startCase) .map(writeDom('#student-name'));
Refactor functions checkLengthSsn() and safeFindObject() and convert to ES6 (improvement)
// checkLengthSsn :: String -> Either(String) const checkLengthSsn = ssn => validLength(9,ssn) ? Either.right(ssn): Either.left('Invalid SSN'); // safeFindObject :: Store, string -> Either(Object) const safeFindObject = R.curry((db, id) => { const val = find(db, id); return val ? Either.right(val) : Either.left(`Object not found with ID: ${id}`); });
Use ES6 syntax
// map :: (ObjectA -> ObjectB), Monad -> Monad[ObjectB] const map = R.curry((f, container) => container.map(f)); // chain :: (ObjectA -> ObjectB), Monad -> ObjectB const chain = R.curry((f, container) => container.chain(f));
The logged output doesn't match the code. It should be changed to:
Monad Example [TRACE] Input was valid:Either.Right(444444444) Monad Example [TRACE] Record fetched successfully!: Either.Right(Person [firstname: Alonzo| lastname: Church]) Monad Example [TRACE] Student converted to CSV: Either.Right(444-44-4444, Alonzo, Church) Monad Example [TRACE] Student added to HTML page: Either.Right(1)
Please use the following figure:
Extract data before appending.
const getOrElse = R.curry((message, container) => container.getOrElse(message)); const showStudent = R.compose( map(append('#student-info')), liftIO, getOrElse('Unable to find student'), map(csv), map(R.props(['ssn', 'firstname', 'lastname'])), chain(findStudent), chain(checkLengthSsn), lift(cleanInput) );
Refactor to use ES6 style
const fork = (join, func1, func2) => (val) => join(func1(val), func2(val)); const toLetterGrade = (grade) => { if (grade >= 90) return 'A'; if (grade >= 80) return 'B'; if (grade >= 70) return 'C'; if (grade >= 60) return 'D'; return 'F'; };
On this page I revisit some previous code that changed
const showStudent = R.compose( map(append('#student-info')), liftIO, getOrElse('Unable to find student'), map(csv), map(R.props(['ssn', 'firstname', 'lastname'])), chain(findStudent), chain(checkLengthSsn), lift(cleanInput) );
On the 4th line. It should be:
expect(input.length);
Remove trailing comma on the last line
assert.equal(csv(['Alonzo', '', 'Church']), 'Alonzo,,Church');
Rename variable
studentDb
to:
studentStore
Typo. Missing begining single quote in test case
QUnit.test('showStudent: findStudent returning null', function (assert) { mockContext.expects('get').once().returns(null); const findStudent = safeFindObject(studentStore); assert.ok(findStudent('xxx-xx-xxxx').isLeft); });
Last QUnit test. Change 'get' to 'find'
QUnit.test('showStudent: findStudent returning valid object', function (assert) { mockContext.expects('find').once().returns ( new Student('Alonzo', 'Church', 'Princeton') .setSsn('444-44-4444')); ); const findStudent = safeFindObject(studentStore); assert.ok(findStudent('444-44-4444').isRight); });
Third line
JSC.on_report((report) => trace('Report'+ str));
Please use the following figure:
"...reduces the complexity of your program as a whole..."
"...require 8 bytes each..."
Please use the following figure:
"Currying all functions might seem like a good idea, but overdoing it can lead to programs that take up larger chunks of memory and run significantly slower."
Fix code to:
const start = () => now(); const runs = []; const end = (start) => { let end = now(); let result = (end - start).toFixed(3); runs.push(result); return result; }; const test = (fn, input) => () => fn(input); const testRot13 = IO.from(start) .map(R.tap(test(rot13, 'functional_js_50_off'))) .map(end); testRot13.run(); // 0.733 m (times may vary) testRot13.run(); // second time: 0.021 ms assert.ok(runs[0] >= runs[1]);
Instead of:
if (fn.length === 0 || fn.length > 1)
Use:
if (fn.length !== 1)
"If you closer at the code..."
Instead use: "If you look closer at the code..."
const m_cleanInput = cleanInput.memoize(); const m_checkLengthSsn = checkLengthSsn.memoize(); const m_findStudent = findStudent.memoize(); const showStudent = R.compose( map(append('#student-info')), liftIO, getOrElse('unable to find student'), map(csv), map(R.props(['ssn', 'firstname', 'lastname'])), chain(m_findStudent), chain(m_checkLengthSsn), lift(m_cleanInput) ); showStudent('444-44-4444').run(); //-> 9.2 ms on average (no memoization) showStudent('444-44-4444').run(); //-> 2.5 ms on average (with memoization)
function sum(arr, acc = 0) { if(_.isEmpty(arr)) { return acc; } return sum(_.rest(arr), acc + _.first(arr)); }
Change:
selector('#search-button').addEventListener('click', handleMouseMovement);
to:
selector('#search-button').addEventListener('click', handleClickEvent);
Code at the beginning (inside the loop) should read:
var student = students[i];
Instead of
let student = students[i];
Then, correct this line:
Regardless of using the block-scoped keyword let
, all the inner calls...
to:
By using the function-scoped keyword var
, all the inner call...
Rename:
forkJoin()
to:
fork()
Rename:
forkJoin()
to:
fork()