Errata: April 27, 2018

Thank you for purchasing Functional Programming in JavaScript. Please post any errors, other than those listed below, in the book's Author Online Forum. We'll compile a comprehensive list and publish it here for everyone's convenience. Thank you!


Page 7. Listing 1.2

Argument passed to repeat should be 2

var printMessage = run(console.log, repeat(2), h2, echo);
            

Page 11. Listing 1.3

The second line should be changed to:

let student = db.find(ssn)
            

Improvement: Page 12. Listing 1.4 and fix missing arrow

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;
});
            

Figure 1.2

Second column function call should read:

print(plus2(0));
            

Improvement: Page 15. Section 1.2.4 First code snippet

For consistency with ES6 syntax used throughout the book, the code should read:

var sortDesc = arr =>
    arr.sort(
       (a, b) => b - a
    );
            

Page 17. Typo

First full paragraph, second line

"In reality, run is an alias for one of the most important techniques: composition."

Improvement: Page 21. Listing 1.7

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}`);
  });
            

Page 25. Object Oriented JavaScript Sidebar

Typo detected: "oriented-oriented" should be "object-oriented"

Page 27. Figure 2.2

Please use the following figure:

Page 37. Code on lenses

const lastnameLens = R.lensProp('lastname');
            

Page 38. Third paragraph from the top

"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
            

Page 40. Missing comparison operator

people.sort(( p1, p2) => p1.getAge() - p2.getAge());
            

Page 43. Section 2.3.3 Typo

As you can see from these examples... on how the function is used (globally, as an object method, as a constructor, and so on)

Page 51. MyModule missing return statement

var MyModule = (function MyModule(export)
    let _myPrivateVar = ...;

    export.method1 = function () {
      // do work
    };

    export.method2 = function () {
     // do work
    };

    return export;
}(MyModule || {}));
            

Page 62.

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..."

Page 64. Listing 3.1

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;
}

Page 64. Second paragraph from the top

"...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:"

Page 65. Listing 3.2

Remove brackets from optional accumulator parameter. It's confusing

function reduce(arr, fn, accumulator) { 

Improvement: Page 66. Listing 3.3

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;
  }, {});
            

Improvement: Page 67

'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,{}); 

Page 68. First code snippet

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

Page 69. Last code snippet from the top

Fix result string to:

//-> 'John von Neumann and Alonzo Church'
            

Page 72. Typo in desired output

Change result array to:

['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']
            

Page 75. SQL-like FP

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}); 

Page 76. Listing 3.9

Rework this code to:

_.from(persons)
  .where(p => p.birthYear > 1903 && p.address.country !== 'US')
  .sortBy(['firstname'])
  .select(p => p.firstname)
  .value();
            

Page 77. Second paragraph from the top

In this section, I'll explain what recursion is and then work through an exercise that will teach you how to think recursively.

Improvement: Page 91. Tuple data type

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;
};
            

Page 93. Section 4.3 Curried function evaluation

Formal definition of currying should be:

curry(f) :: ((a,b,c) -> d) -> a -> b -> c -> d
            

Page 94. Second code sample

Rework code to ES6 syntax

const name = curry2((last,first) => new StringPair(last,first));
            

Pages 94, 95. Function checkType() at the bottom

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
            

Page 99. Listing 4.7

Variable declarations on the fifth line should be:

let position = 0, length = args.length;
            

Should be:

let position = 0, length = boundArgs.length;
            

Page 101. Fix result of the call to .parseUrl()

Code snippet at the top. At line 8, the result should be:

[ 'http://example.com', 'http', 'example', 'com' ]
            

Improvement: Page 103. Refactor to use ES6

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));
            

Page 105. Snippet of code after figure 4.11

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

Improvement: Page 109. Listing 4.11 Use string templates

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;
});
            

Improvement: Page 109. Listing 4.11 Use Destructuring in function csv()

To take advantage of ES6 destructuring, we change the csv() function to:

const csv = ({ssn, firstname, lastname}) =>
    `${ssn}, ${firstname}, ${lastname}`;
            

Page 109. Listing 4.11 Missing the Ramda object R in curry

Last function of listing 4.11 should read:

const append = R.curry((elementId, info) => {
    document.querySelector(elementId).innerHTML = info;
    return info;
});
            

Page 110. Extra parenthesis

Extra parenthesis at the end of first code snippet

const showStudent = R.compose(
  append('#student-info'),
  csv,
  findStudent,
  normalize,
  trim);
            

Page 120. Delete the word "non-" from first line

"Violate the principle of locality because..."

Page 121. Cleanup/shorten code to

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');
}
            

Page 122. Class Wrapper fix map method

Typo in "this._value" access in map() and toString()

map(f) { 
  return f(this._value);
};

toString() {
  return 'Wrapper (' + this._value + ')';
}
            

Page 123. First paragraph

"it can't be directly retrieved"

Page 123. Second code snippet

Instead of:

wrappedValue.map(log);

Change to:

wrappedValue.map(console.log);  

Page 124. Fix fmap() function's this reference

// fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B]
fmap (f) {
  return new Wrapper(f(this._value));  
}
            

Page 127. Section 5.3 Missing semicolon

Improvement: convert to ES6

const getAddress = student =>
  wrap(student.fmap(R.prop('address')));
            

Improvement: Page 128. Section 5.3.1. Convert Empty class to ES6

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();
            

Page 130 - 131. Listing 5.3 Wrapper Monad

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})`;
  }
}
            

Improvement: Page 131. Listing 5.4. Use ES6 syntax

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')));
            

Improvement: Page 133. Listing 5.5

Use qualified static calls in fromNullable() method

static fromNullable(a) {
  return a !== null ? Maybe.just(a) : Maybe.nothing();
}
            

Page 133. Listing 5.5 Fix this._value access in Just class

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})`;
  }
}
            

Page 133. Listing 5.5 Class Nothing

Correct quotes use in error string

get value() {
    throw new TypeError("Can't extract the
      value of a Nothing.");  
}
            

Page 134. Code block at the top. Fix the this._value

Fix this._value in filter() method and replace isNothing() with chain()

filter(f) {
  return this._value;  
}

chain(f) {
  return this;
}
            

Improvement: Page 134. Convert safeFindObject to ES6

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)));
            

Pages 136 and 137. Side bar titled "Function Lifting"

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);
            

Improvement. Page 137. Enhance the annotation #A in listing 5.6

"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."

Pages 138, 139. Spot fixes to the Either type

Class Either

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})`;
}
            

Page 139. Typo and ES6 syntax

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}`);
});
            

Page 140. Last code block annotation

The annotation references the wrong section. It should read:

"This function was created in section 4.4.1"

Improvement: Page 142. Refactor functions read() and write() at the top

Simplify the selector string

const read = (document, selector) => document.querySelector(selector).innerHTML;

const write = (document, selector, val) => {
  document.querySelector(selector).innerHTML = val;
  return val;
};
            

Improvement: Page 142. Changes to Listing 5.7 for ES6 syntax

Modify method map to use lambda expression

  map(fn) {
    let self = this;
    return new IO(() => fn(self.effect()));
  }
            

Improvement: Page 143. Refactor functions read() and write()

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;
  };
};
            

Page 143. Refactor last code snippet

Code should change to:

const changeToStartCase =
    IO.from(readDom('#student-name'))
    .map(_.startCase)  
    .map(writeDom('#student-name'));
            

Page 145. Listing 5.8

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}`);
});
            

Improvement: Page 147. Listing 5.10. Refactor map() and chain()

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));
            

Page 147. Listing 5.11 Run code

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)
            

Page 148. Figure 5.13

Please use the following figure:

Fix code listing 5.12. Page 149.

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)
);
            

Improvement: Page 160. Listing 6.1

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';
};
            

Page 162. Repetition of previous code

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)
);
            

Page 163. Listing 6.2 small code changes

On the 4th line. It should be:

expect(input.length);
            

Remove trailing comma on the last line

assert.equal(csv(['Alonzo', '', 'Church']), 'Alonzo,,Church');
            

Page 164. Section 6.3.4

Rename variable

studentDb 

to:

studentStore 

Page 165. Listing 6.3

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); 
});
            

Page 165. Listing 6.3

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);  
});
            

Page 170. Listing 6.5. Missing "fat" arrow in lambda expression

Third line

JSC.on_report((report) => trace('Report'+ str));
            

Page 176. Figure 6.13

Please use the following figure:

Page 179. Summary. Typo

"...reduces the complexity of your program as a whole..."

Page 182. Second paragraph

"...require 8 bytes each..."

Page 185. Figure 7.4

Please use the following figure:

Page 185. Second paragraph

"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."

Page 193 - 194. Listing 7.2

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]);
            

Page 194. Minor simplification towards the end inside the Function.prototype.memoize function

Instead of: if (fn.length === 0 || fn.length > 1)

Use: if (fn.length !== 1)

Page 195. Minor grammar

"If you closer at the code..."

Instead use: "If you look closer at the code..."

Page 197. Update recalled 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)
            

Page 202. Second snippet for recursive addition

function sum(arr, acc = 0) {
  if(_.isEmpty(arr)) {
    return acc;
  }
  return sum(_.rest(arr), acc + _.first(arr));
}
            

Page 211. Listing 8.4

Change:

selector('#search-button').addEventListener('click', handleMouseMovement);

to:

selector('#search-button').addEventListener('click', handleClickEvent);

Page 212.

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...

Page 219. Listing 8.6

Rename:

forkJoin()

to:

fork()

Page 220 Listing 8.7

Rename:

forkJoin()

to:

fork()