$ cat /etc/passwd | cut -f1 -d: | xargs -I{} sh -c "echo {} | grep -i -o '[aeiou]' | sort | uniq | wc -l" | sort -n | tail -1
How many different vowels has the username that has more different vowels?
function getMaxVowels() {
var fs = require('fs');
var contents = fs.readFileSync('/etc/passwd', 'utf8');
var userPattern = /^[^:]+/gm;
var userNames = contents.match(userPattern);
var maxVowels = 0;
for (var i = 0; i < userNames.length; i++) {
var userName = userNames[i].toLowerCase();
var countVowels = 0;
var vowels = ['a', 'e', 'i', 'o', 'u'];
for (var j = 0; j < vowels.length; j++) {
if (userName.indexOf(vowels[j]) !== -1) {
countVowels++;
}
}
if (maxVowels < countVowels) {
maxVowels = countVowels;
}
}
return maxVowels;
}
function readFile(fileName) {
var fs = require('fs');
return fs.readFileSync(fileName, 'utf8');
}
function getUserNames() {
var contents = readFile('/etc/passwd');
var userNamePattern = /^(\w|[-])+:/gm;
return contents.match(userNamePattern).map(function(value) {
return value.replace(/:$/, '');
});
}
function getVowels(string) {
return string.split('').filter(isVowel);
}
function isVowel(char) {
return /[aeoiuAEIOU]/.test(char[0]);
}
function uniq(array) {
return array.reduce(function(result, value) {
if (result.indexOf(value) === -1) {
result.push(value);
}
return result;
}, []);
}
function compareNumbers(numberA, numberB) {
return numberA - numberB;
}
function last(array) {
return array[array.length - 1];
}
function count(array) {
return array.length;
}
function maxVowels(array) {
var vowelsCount = array.map(getVowels).map(uniq).map(count).sort(compareNumbers);
return last(vowelsCount);
}
maxVowels(getUserNames());
function greet() {
return 'Hi!';
}
function greet(name) {
return 'Hi ' + name + '!';
}
function greet(greeting, name) {
return greeting + ' ' + name + '!';
}
function greet() {
return arguments[0] + ' ' + arguments[1] + '!';
}
//Function declaration
function greet() {
return 'Hi!';
}
typeof greet === 'function'; //true
var greet2 = greet;
greet2 === greet; //true
//Function expression
var greet3 = function() {
return 'Hi!';
}
greet2 === greet3; //false
greet2() === greet3(); //true
//functions as parameters
function map(array, fn) {
var i;
var result = [];
for (i = 0; i < array.length; i++) {
result.push(fn(array[i]));
}
return result;
}
function double(x) {
return x * 2;
}
map([1, 2, 3], double); //[2, 4, 6];
//returned functions
function greet(greeting) {
return function(name) {
return greeting + ' ' + name + '!';
};
}
var sayHello = greet('Hi');
sayHello('Peter'); //"Hi Peter!"
sayHello('Anna'); //"Hi Anna!"
var firstName = 'Peter';
//global scope <- bad
'firstName' in window; //true
firstName; //"Peter"
window.firstName; //"Peter"
function greet() {
return 'Hi!';
}
//global scope <- bad
'greet' in window; //true
greet(); //"Hi!"
window.greet(); //"Hi!"
function greet() {
//missing var
greeting = 'Hi!';
}
'greeting' in window; //false
greet();
'greeting' in window; //true
function greet() {
"use strict";
greeting = 'Hi!';
}
'greeting' in window; //false
try {
greet();
} catch(e) {
console.log('Error: ' + e);
}
'greeting' in window; //false
//JavaScript has function scope
function greet(firstName) {
var greeting = 'Hi';
//parametrs and inner variables are visible inside the function
console.log(greeting + ' ' + firstName + '!');
}
//Here greeting and firstName are not accesible
console.log(firstName); //error firstName is not declared
greeting = 'Bye'; //Create a global variable
"greeting" in window; //true
var greeting = 'Bye';
//Inner variables hide outer variables
function greet() {
var greeting = 'Hi';
console.log(greeting); //"Hi"
}
greet(); //"Hi"
console.log(greeting); //"Bye"
//javascript has no block scope
function sum(from, to) {
var result = 0;
for (var i = from ; i <= to; i++) {
result += i;
}
//console.log(i) -> to + 1 -> i variable is accessible outside the loop
return result;
}
for (var i = 0; i < 10; i++) {
....
}
for (var i = 0; i < 10; i++) { //bad: No complaints but it is a variable redeclaration
....
}
function greet() {
console.log(greeting); //undefined -> Declarations are hoisted
var greeting = 'Hi';
console.log(greeting); //Hi
}
function greet() {
//Internally this is what is happening
var greeting;
console.log(greeting);
greeting = 'Hi';
console.log(greeting); //Hi
}
function greet(isHello) {
if (isHello) {
function g(name) {
return 'Hi ' + name + '!';
}
} else {
function g(name) { //functions declarations are hoisted too -> Bad: redefinition
return 'Bye ' + name + '!';
}
}
return g;
}
greet(true)('Peter'); //"Bye Peter!" <- unexpected?
//Fixed
function greet(isHello) {
var g;
if (isHello) {
g = function(name) {
return 'Hi ' + name + '!';
};
} else {
g = function(name) {
return 'Bye ' + name + '!';
};
}
return g;
}
//Better
function greet(isHello) {
if (isHello) {
return function(name) {
return 'Hi ' + name + '!';
};
} else {
return function(name) {
return 'Bye ' + name + '!';
};
}
}
function greet(greeting) {
return function(firstName) {
//inner functions can access outer variables -> Closure
return greeting + ' ' + firstName + '!';
};
//outer functions can not access inner variables -> Variables have function scope
}
var sayHello = greet('Hello');
sayHello('Peter'); //"Hello Peter" -> Variables survive the execution of the function.
//External variables are accessible even when the function has returned.
function greet() {
var i = 1;
for(; i <= 10; i++) {
setTimeout(function() {
console.log('Hi ' + i);
}, i * 1000);
}
}
greet(); //Hi 11 Hi 11 Hi 11 Hi 11 ...
//why? Closures access the current value of the variables ->
//Access is by reference even in primitive types
//Solution 2: Using IIFE
function greet() {
var i = 1;
for(; i <= 10; i++) {
(function(i) {
setTimeout(function() {
console.log('Hi ' + i);
}, i * 1000);
}(i));
}
}
greet(); //Hi 1 Hi 2 Hi 3 Hi 4 ...
//Why does this work? Now i inner variable is a copy of the outer i variable ->
//Primitive variables are passed by value
//Solution 1: Extracting the internal function of the loop
function show(msg) {
return function() {
console.log(msg);
};
}
function greet() {
var i = 1;
for(; i <= 10; i++) {
setTimeout(show('Hi ' + i), i * 1000);
}
}
greet(); //Hi 1 Hi 2 Hi 3 Hi 4 ...
//Why does this work? show(msg) is executed synchronously ->
//i has the correct value ->
//setTimeout executes the inner function returned by show(msg)
function multiply(x, y) {
return x * y;
}
multiply(2, 5); //10
//code reuse
function double(x) {
return multiply(2, x);
}
double(5); //10
//another cool way -> Partial application
var double = multiply.bind(null, 2);
double(5); //10
//Private static variables
var count = (function() {
var counter = 0;
return function() {
return ++counter;
};
}());
count(); //1
count(); //2
count(); //3
function double(x) {
return 2 * x;
}
function addThree(x) {
return 3 + x;
}
//composition
double(addThree(5)); //16
//Generic composition: compose two functions with one argument
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
var addThreeThenDouble = compose(double, addThree);
addThreeThenDouble(5); //16
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
//Composite is great but difficult to read in JavaScript
multiply(add(multiply(2, 4), 5), 3); //39
//chainable functions
function chain(fns) {
var _fns = {};
var result;
fns.forEach(function(fn) {
_fns[fn.name] = function() {
var args = Array.prototype.slice.call(arguments);
if (result !== undefined) {
args = [result].concat(args);
}
result = fn.apply(null, args);
return _fns;
};
});
_fns.value = function() {
var _result = result;
result = undefined;
return _result;
};
return _fns;
}
var c = chain([add, multiply]);
//Fluent style
c.multiply(2, 4).add(5).multiply(3).value(); //39
//Lazy chainable version
function chain(fns) {
var _fns = {};
var _chain = [];
fns.forEach(function(fn) {
_fns[fn.name] = function() {
var args = Array.prototype.slice.call(arguments);
_chain.push(function() {
return fn.apply(null, Array.prototype.slice.call(arguments).concat(args));
});
return _fns;
};
});
_fns.execute = function() {
if (_chain.length === 0) {
return;
}
var result = _chain.slice(1).reduce(function(result, fn) {
return fn(result);
}, _chain[0]());
_chain = [];
return result;
};
return _fns;
}
//classical way
function double(arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(arr[i] * 2);
}
return result;
}
double([1, 2, 3, 4]); //[2, 4, 6, 8];
function isEven(arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(arr[i] % 2 === 0);
}
return result;
}
//Bad code reuse -> same structure, different function -> Break the single responsibility principle
isEven([1, 2, 3, 4]); //[false, true, false, true]
function double(x) {
return 2 * x;
}
function isEven(x) {
return x % 2 === 0;
}
//creates a new array with the results of calling a provided function on every element in the array
function map(arr, fn) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(fn(arr[i]));
}
return result;
}
map([1, 2, 3, 4], double); //[2, 4, 6, 8]
map([1, 2, 3, 4], isEven); //[false, true, false, true]
//native JavaScript map
[1, 2, 3, 4].map(isEven); //[false, true, false, true]
//Intrusive and no reusable solution
function sum(arr) {
var result = 0;
var i;
for (i = 0; i < arr.length; i++) {
result += arr[i];
}
return result;
}
sum([1, 2, 3, 4]); //10
function sum(x, y) {
return x + y;
}
function reduce(arr, fn, initValue){
var i = 0;
var result = initValue !== undefined ? initValue : arr[i++];
for(; i < arr.length; i++){
result = fn(result, arr[i]);
}
return result;
}
reduce([1, 2, 3, 4], sum, 0); //10
[1, 2, 3, 4].reduce(sum, 0); //10
//Bad -> intrusive
function evens(arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
result.push(arr[i]);
}
}
return result;
}
evens([1, 2, 3, 4]); //[2, 4]
function isEven(x) {
return x % 2 === 0;
}
function filter(fn, arr) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
if (fn(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
filter(isEven, [1, 2, 3, 4]); //[2, 4]
var evens = filter.bind(null, isEven);
evens([1, 2, 3, 4]); //[2, 4]
[1, 2, 3, 4].filter(isEven); //[2, 4]
//Why use loops at all?
function filter(fn, arr) {
var result = [];
arr.forEach(function(value) {
if (fn(value)) {
result.push(value);
}
});
return result;
}
//forEach does not return a value -> This is not very FP approach
function filter(fn, arr) {
return arr.reduce(function(result, value) {
return fn(value) ? result.concat(value) : result;
}, []);
}
//Classical way
function diagonalSum(matrix) {
var i;
var sum = 0;
for (i = 0; i < matrix.length; i++) {
sum += matrix[i][i];
}
return sum;
}
diagonalSum([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); // 1 + 5 + 9 = 15
function zipWith(fn, array1, array2) {
var result = [];
var i = 0;
var size = Math.min(array1.length, array2.length);
while (i < size) {
result.push(fn(array1[i], array2[i]));
i++;
}
return result;
}
function getArrayValue(arr, index) {
return arr[index];
}
//other way: Object.keys(arr)
function indexes(arr) {
return arr.map(function(value, index) {
return index;
});
}
function sum(x, y) {
return x + y;
}
function diagonalSum(matrix) {
return zipWith(getArrayValue, matrix, indexes(matrix)).
reduce(sum);
}
//Classical way
function reverse(arr) {
var result = [];
var i = arr.length - 1;
for (; i >= 0; i--) {
result.push(arr[i]);
}
return result;
}
reverse([1, 2, 3, 4]); //[4, 3, 2, 1]
//Using reduce function
function reverse(arr) {
return arr.reduce(function(result, value) {
return [value].concat(result);
}, []);
}
//Recursive way
function reverse(arr) {
return arr.length === 0 ? arr : reverse(arr.slice(1)).concat(arr[0]);
}
//Reverse native function
[1, 2, 3, 4].reverse(); //Warning: Array.prototype.reverse produce a state mutation
arr.reduce(callback[, initialValue]);
where callback receives: callback(previousValue, currentValue, index, array);
Implement the diagonalSum() function using arr.reduce() method
Â
Help: This is the declaration of reduce() method:
You can see a solution here
function head(arr) {
return arr[0];
}
function tail(arr) {
return arr.slice(1);
}
function last(arr) {
return arr[arr.length - 1];
}
function first(arr) {
return arr.slice(0, arr.length - 1);
}
Implement a pure functional version of the zipWith() function.
Â
Help: You can use some of these helper functions:
You can see a solution here
function map(arr, fn) {
var i;
var result = [];
for (i= 0; i < arr.length; i++) {
result.push(fn(arr[i]));
}
return result;
}
Implement map() function using reduce() function
Â
As a remainder this is our actual implementation of the map() function:
You can see a solution here
function reduce(arr, fn, initValue){
var i = 0;
var result = initValue !== undefined ? initValue : arr[i++];
for(; i < arr.length; i++){
result = fn(result, arr[i]);
}
return result;
}
Implement a pure functional version of the reduce() function
Â
As a remainder this is our actual implementation of the reduce() function:
Note: For a cleaner implementation, suppose that initValue param is always passed.
You can see a solution here
frequency(['Peter', 'Anna', 'Rose', 'Peter', 'Peter', 'Anna']); //[["Anna", 2], ["Peter", 3], ["Rose", 1]]
frequency([1, 10, 12, 2, 1, 10, 2, 2]); //[[1, 2], [2, 3], [10, 2], [12, 1]]
Implement the frequency(arr, options) function.
The function takes an array and calculates the frequency of each value of the array sorted naturally. For example:
Optionally, the function can take sorting and grouping criterias. See CodeWars kata
//SELECT * FROM numbers
var numbers = [1, 2, 3, 4, 5, 6];
query().select().from(numbers).where(isEven).execute(); //[2, 4, 6]
function isEven(number) {
return number % 2 === 0;
}
If you have completed the previous exercise, this is the continuation. Â Resolve Functional SQL kata:
//objects are maps of (key, value) pairs
var obj = {
x: 1,
y: 2
};
//keys are always strings
var obj = {
1: 1000, //The key is "1" not 1. Non string keys are converted to strings
2: -200
};
//quotes are optional
var obj = {
"1": 1000,
"2": -200
};
//values can be of any type
var obj = {
foo: 1000,
bar: 'Peter',
baz: function() {},
zot: [1, 2, 3],
foobar: /^a/
};
var obj = {
x: 1,
y: 2
};
//You can use dot notation...
obj.x; //1
//...or array notation
obj["x"]; //1
//array notation is useful with expressions
var key = 'x';
obj[key]; //1
var obj = {
x: 1,
y: 2
};
//You can use for..in loop...
for (var key in obj) {
obj[key];
}
//...but FP style is preferred
Object.keys(obj).forEach(function(key) {
obj[key];
});
//Important: key iteration order is not guaranteed.
var obj = {
x: 1,
y: 2
};
"x" in obj; //true
"z" in obj; //false
//keys can be added...
obj.z = 23;
"z" in obj; //true
//...or removed
delete obj.z;
"z" in obj; //false
//Common error: This do not remove the key
obj.x = undefined;
"x" in obj; //true
//Rather, it assigns the undefined value
obj.x === undefined; //true
//You can not define a key in this way
var key = "x" + "y"
var obj = {
key : 3 //the key is "key" and not "xy";
};
//..neither this way is valid
var obj = {
"x" + "y": 3 //expressions are not allowed here
};
//But you can do this
var obj = {};
obj["x" + "y"] = 3;
//Attention
var obj = {};
var arr = [1, 2];
obj[arr] = 4;
"1,2" in obj; //true
var obj2 = {};
obj[obj2] = 3;
"[object Object]" in obj; //true
//Remember: keys are converted to strings
//Arrays are a special type of objects
typeof [] === 'object'; //true
//Arrays can have holes
var arr = [1,,,2];
//indexes are keys
"0" in arr; //true
//holes are not keys
"1" in arr; //false
//holes are equal to undefined
arr[1] === undefined; //true
//but careful: It is not the same be undefined than it be equal to undefined
arr[1] = undefined; //this is not a hole
arr[1] === undefined; //true
"1" in arr; //true <- this is the different
//Arrays are dynamic
var arr = [1, 2, 3];
arr[500] = 4; //from index 3 to index 499 there are holes
//The length property is not what it seems
arr.length; //501
//You should not use for..in loop to iterate through arrays...
//because the iteration order is not guaranteed
//There are plenty of methods to iterate over arrays:
//forEach, map, reduce, filter, some, every, ...
//Careful: delete comand make holes, it does not remove the element
var arr = [1, 2, 3];
delete arr[3];
arr.length; //3
//To remove properly, you should use splice method instead
arr.splice(2, 1);
arr.length; //2
//As in Java, JavaScript difference between objects and primitive types.
//Primitive types are compared/passed by value
"Peter" === "Peter"; //true
1 === 1; //true
false === false; //true
//Objects types are compared/passed by reference
{} === {}; //false
[1] === [1]; //false
function(){} = function(){}; //false
//You can use primitive as objects (I do not think this is helpful at all)
new String('Peter') === new String('Peter'); //false
//You can extract the primitive of an object
new String('Peter').valueOf() === new String('Peter').valueOf(); //true
//Or convert to a primitive
String(1) === String(1); //true
//Primitive types have no methods but are converted to objects when used as such
"Peter".toUpperCase(); //PETER
//The wrapper object is deleted when the method returns
//Therefore the primitive may not have keys
//as they are added to the discarted wrapper object
var person = "Peter";
person.age = 3;
person.age; //undefined
"age" in person; //error <- person is primitive
//However, this can be done with objects
var person = new String('Peter');
person.age = 3;
"age" in person; //true
// == do coercion over primitives
// All primitives are truthy except: 0 NaN '' false null undefined
0 == false; //true
null == undefined; //true
false == false; //true
Boolean(1) == Boolean(0); //false
NaN == NaN; //false <- wierd
[1] == 1; // true
!!0 == !!''; //true
//Objects are never coerced
new Boolean(false) == new Boolean(false); //false
//Rule of thumb: Always use ===
//Be careful with this:
function fn(x) {
if (x) {...}
}
fn(false); //Probably this work as you are expecting, but ...
fn(0); //What about this? Are you sure 0 is false?
//Better
if (x !== false)
//Same considerations can be done with
if (!x) {...}
//Better
if (x === false) {...}
//To check nullity, do not use
if (!x) {...}
//Instead use
if (x === undefined || x === null) {...}
//Rule of thumb: prefer undefined over null. Then you can use
if (x === undefined) {...}
//Be careful with this
fn(isEven) {
isEven = isEven || true; //bad: there is no way to assign false
}
fn(false); //isEven will be true
fn('Peter'); //isEven will be "Peter"
//Corrected and more secure
fn(isEven) {
isEven = !!isEven || false; //truthy values will be true and falsy values will be false
}
//Note that we are using the object 'this'. We will talk more about 'this' later. By now you avoid making too many assumptions about it and its behavior, because it is quite different from other languages
var person1 = {
name: 'peter',
greet: function() {
return 'Hi ' + this.name + '!';
}
};
This approach has some drawbacks:
var person2 = {
name: 'Anna',
greet: function() {
return 'Hi ' + this.name + '!';
}
};
person1.name = 'Michael';
person1.lastName = 'Smith'; //bad, but possible
delete person1.name; //worse
function createPerson(spec) {
"use strict";
var name = spec.name;
return Object.freeze({
greet: function() {
return 'Hi ' + name + '!';
}
}); // Object.freeze() makes public interface immutable
}
var person1 = createPerson({name: 'Peter'});
var person2 = createPerson({name: 'Anna'});
person1.greet(); //"Hi Peter!"
person1.name; //undefined
delete person1.greet;
person1.greet(); //"Hi Peter!"
person1.lastName = 'Smith';
person1.lastName; //undefined
var person1 = createPerson({name: 'Peter'});
var person2 = createPerson({name: 'Anna'});
person1.greet === person2.greet; //false
person1 instanceof Person; //Error Person is not defined
function Person(name) {
this.name = name;
this.greet = function() {
return 'Hi ' + this.name + '!';
};
}
var person1 = new Person('Peter');
person1.greet(); //"Hi Peter!"
//This solution exposes the name variable
person1.name; //"Peter"
//But we can avoid this easily
function Person(name) {
var _name = name;
this.greet = function() {
return 'Hi ' + _name + '!';
};
//Object.freeze(this); //To add immutability
}
In JavaScript, any function can be used to construct objects if it is called with the 'new' operator
It is generally accepted that the name of a constructor function begin with a capital letter
Constructor functions are similar to constructors in classical OOP languages
var person1 = new Person('Peter');
person1 instanceof Person; //true
person1 instanceof Object; //true
typeof person1; //"object"
var person1 = new Person('Peter');
var person2 = new Person('Anna');
person1.greet === person2.greet; //false
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return 'Hi ' + this.name + '!';
};
var person1 = new Person('Peter');
person1.greet(); //"Hi Peter!"
JavaScript can emulate the class concept through prototypes
var person1 = new Person('Peter');
person1.greet === person2.greet; //true
person1 instanceof Person; //true
person1 instanceof Object; //true
typeof person1; //"object"
var person1 = new Person('Peter');
person1.name = 'Anna';
//Prototype does not change. Instead, a public `greet` variable is added to `this` object <- danger
person1.greet = function() {
return 'Bye!';
}
function createPerson(name) {
var personPrototype = {
greet: function() {
return 'Hi ' + this.name + '!';
}
};
return Object.create(personPrototype, {
name: {
value: name,
writable: true
}
});
}
var person1 = new createPerson('Peter');
person1.greet(); //"Hi Peter!"
var person1 = new Person('Peter');
person1.greet === person2.greet; //true
person1 instanceof Person; //true
person1 instanceof Object; //true
typeof person1; //"object"
var person1 = new Person('Peter');
person1.name = 'Anna';
//Prototype does not change. Instead, a public `greet` variable is added to `this` object <- danger
person1.greet = function() {
return 'Bye!';
}
function greet(firstName) {
return 'Hi ' + first.name + '!';
}
greet instanceof Function; //true
greet.name; //"greet"
greet.length; //1 <- named parameters
//Other attributes
greet.prototype
greet.call(...);
greet.apply(...);
greet.bind(...);
//adding attributes
greet.greeting = 'Hi';
greet.greeting; //"Hi"
function Person(firstName) {
this.firstName = firstName;
this.greet = function(greeting) {
return greeting + ' ' + this.firstName + '!';
};
}
var person1 = new Person('Peter');
//Seems like Java
person1.firstName; //"Peter"
//But something weird happens if we forget new operator
var person2 = Person('Anna'); //No complainst
//Person is called as a regular function that returns ....
person2; //undefined
//firstName is attached to the global object
"firstName" in window; //true -> bad
function Person(firstName) {
"use strict";
this.firstName = firstName;
...
}
var person2 = Person('Anna'); //"TypeError: Cannot set property 'firstName' of undefined"
Fix one:
function Person(firstName) {
if (this instanceof Person) {
this.firstName = firstName;
} else {
return new Person(firstName);
}
}
var person2 = Person('Anna');
person2.firstName; //"Anna"
Fix two:
function Person(firstName) {
//Avoid using this object
var _firstName = firstName;
return {
getFirstName: function() {
return _firstName;
}
};
}
//We have seem this before:...
//rename Person to createPerson
var person2 = Person('Anna');
person2.greet('Hi'); //"Hi Anna!"
Fix three:
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.greet = function(greeting) {
return return greeting + ' ' + this.firstName + '!';;
};
var person1 = new Person('Peter');
//Seems it works
person1.greet(); //"Hi Peter!"
//But it does not
var greet = Person.prototype.greet;
greet('Hi'); //"Hi undefined!"
//Why?
//greet is a global function
//when greet is executed, this becomes the global object <- weird
var person1 = new Person('Peter');
var greet = Person.prototype.greet;
//When greet function is called, it sets the this object to person1 passing 'Hi' as argument
greet.call(person1, 'Hi'); //"Hi Peter!"
Fix one:
//apply is like call but the parameters are passed inside an array -> Useful in some situations
greet.apply(person1, ['Hi']); //"Hi Peter!"
Fix two:
//bind returns a function with the 'this' object applied to the first param for later execution
greetPeter = greet.bind(person1);
greetPeter('Hi'); //"Hi Peter!"
greetPeter('Bye'); //"Bye Peter"
Fix three:
//bind allows partial application parameters
greetPeter = greet.bind(person1, 'Hi');
//greeting parameter has been fixed previously
greetPeter(); //"Hi Peter!"
Fix four:
function Person(firstName) {
this.firstName = firstName;
this.greets = {
sayHello: function() {
return 'Hello ' + this.firstName + '!';
},
sayBye: function() {
return 'Bye ' + this.firstName + '!';
}
};
}
var person1 = new Person('Peter');
//In sayHello function, this is bound to the global object
person1.greets.sayHello('Hi'); //"Hello undefined!"
function Person(firstName) {
this.firstName = firstName;
this.greets = {
sayHello: function() {
return 'Hello ' + this.firstName + '!';
}.bind(this)
...
};
}
Fix one:
function Person(firstName) {
this.firstName = firstName;
var self = this;
this.greets = {
sayHello: function() {
return 'Hello ' + self.firstName + '!';
},
...
};
}
Fix two:
person1.greets.sayHello.bind(person1)('Hi');
Fix three:
person1.greets.sayHello.call(person1, 'Hi');
Fix four:
function Person(firstName) {
this.firstName = firstName;
this.timedGreet = function(greet, milliseconds) {
setTimeout(function() {
console.log(greet + ' ' + this.firstName + '!');
}, milliseconds);
};
}
var person1 = new Person('Peter');
person1.timedGreet('Hi', 1000); //"Hi undefined"
function Person(firstName) {
this.firstName = firstName;
this.timedGreet = function(greet, milliseconds) {
setTimeout(function() {
console.log(greet + ' ' + this.firstName + '!');
}.bind(this), milliseconds);
};
}
Fix one:
function Person(firstName) {
this.firstName = firstName;
var self = this;
this.timedGreet = function(greet, milliseconds) {
setTimeout(function() {
console.log(greet + ' ' + self.firstName + '!');
}, milliseconds);
};
}
Fix two:
function Person(firstName) {
this.firstName = firstName;
this.timedGreet = function(greet, milliseconds, self) {
setTimeout(function() {
console.log(greet + ' ' + self.firstName + '!');
}, milliseconds);
};
}
var person1 = new Person('Peter');
person1.timedGreet('Hi', 1000, person1);
Fix three:
But seriously, think twice before doing this.
function Person(firstName) {
this.firstName = firstName;
this.getName = function() {
return this.firstName;
};
}
Person.prototype.greet = function(greeting) {
return greeting + ' ' + this.firstName + '!';
};
function Student(firstName, grade) {
//Questions?
//1. How could Student inherit this.getName method from Person?
//2. How could Student.prototype inherit from Person.prototype?
}
function Student(firstName, grade) {
Person.call(this, firstName);
this.grade = grade;
}
var student1 = new Student('Peter');
student1.firstName; //"Peter"
student1.getName(); //"Peter"
Student.prototype = Person.prototype;
Student.prototype.getGrade = function() {
return this.grade;
};
var person1 = new Person('Peter');
"getGrade" in person1; //true <- Student.prototype and Person.prototype are the same object
Bad solution one:
Student.prototype = new Person();
Student.prototype.getGrade = function() {
return this.grade;
};
var person1 = new Person('Peter');
"getGrade" in person1; //false
var student1 = new Student('Anna', 'middle school');
student1.getGrade(); //"middle school"
student1.getName(); //"Anna"
student1.greet('Hi'); //"Hi Anna!"
//Seems all is ok, but...
"firstName" in Student.prototype; //true
Bad solution two (unfortunately widely used):
function Person(firstName) {
this.firstName = firstName;
this.getName = function() {
return this.firstName;
};
}
Person.prototype.greet = function(greeting) {
return greeting + ' ' + this.firstName + '!';
};
function Student(firstName, grade) {
Person.call(this, firstName);
this.grade = grade;
}
function F() {}
F.prototype = Person.prototype;
Student.prototype = new F();
Student.prototype.getGrade = function() {
return this.grade;
};
var person1 = new Person('Peter');
"getGrade" in person1; //false
var student1 = new Student('Anna', 'middle school');
student1.getGrade(); //"middle school"
student1.getName(); //"Anna"
student1.greet('Hi'); //"Hi Anna!"
"firstName" in Student.prototype; //false
Good solution (mix the two bad solutions):
function inherits(Child, Parent) {
function F() {}
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Student(firstName, grade) {
Person.call(this, firstName);
this.grade = grade;
}
inherits(Student, Person);
Student.prototype.getGrade = function() {
return this.grade;
};
var student1 = new Student('Anna', 'middle school');
student1.getGrade(); //"middle school"
student1.getName(); //"Anna"
student1.greet('Hi'); //"Hi Anna!"
"firstName" in Student.prototype; //false
student1 instanceof Student; //true
student1 instanceof Person; //true
Generalizing the problem:
personPrototype = {
greet: function(greeting) {
return greeting + ' ' + this.firstName + '!';
}
};
var studentPrototype = Object.create(personPrototype, {
getGrade: {
value: function() {
return this.grade;
}
}
});
function createStudent(firstName, grade) {
return Object.create(studentPrototype, {
firstName: {
value: firstName
},
grade: {
value: grade
}
});
}
var student1 = createStudent('Anna', 'Middle school');
student1.getGrade(); //"middle school"
student1.greet('Hi'); //"Hi Anna!"
Better approach (using Object.create):
function greet(person, greeting) {
return greeting + ' ' + person.firstName + '!';
}
var createStudent = function(firstName, grade) {
var self = {
firstName: firstName,
grade: grade
};
return Object.freeze({
getGrade: function() {
return self.grade;
},
greet: greet.bind(null, self)
});
};
var student1 = createStudent('Anna', 'Middle school');
student1.getGrade(); //"middle school"
student1.greet('Hi'); //"Hi Anna!"
//Memory waste
var student2 = createStudent('Peter', 'High school');
student1.greet === student2.greet; //false
student1.getGrade === student2.getGrade; //false
Why mimic class-based languages if JavaScript is classless?
function greet(greeting) {
return greeting + ' ' + this.firstName + '!';
}
var createStudent = (function() {
var studentPrototype = {
greet: greet,
getGrade: function() {
return this.grade;
}
};
return function(firstName, grade) {
return Object.create(studentPrototype, {
firstName: {
value: firstName
},
grade: {
value: grade
}
});
};
}());
var student1 = createStudent('Anna', 'Middle school');
student1.getGrade(); //"middle school"
student1.greet('Hi'); //"Hi Anna!"
var student2 = createStudent('Peter', 'High school');
student1.greet === student2.greet; //true
student1.getGrade === student2.getGrade; //true
//public access
"firstName" in student1; //true
"grade" in student1; //true
Prototype solution:
var persons = {
0: 'Peter',
1: 'Anna',
2: 'Michael',
length: 3
};
Array.prototype.map.call(persons, function(name) {
return name.toUpperCase();
}); //["PETER", "ANNA", "MICHAEL"]
We can benefit from the array methods in objects that seem arrays
function add(x, y) {
return x + Number(y);
}
var number = "1234";
Array.prototype.reduce.call(number, add, 0); //10
Strings are array-like objects
function add(x, y) {
return x + y;
}
function sum() {
return Array.prototype.reduce.call(arguments, add, 0);
}
sum(1, 2, 3, 4); //10
arguments is an array-like object
Array.prototype.forEach.call(document.querySelectorAll('div'), addClickEvent);
function addClickEvent(div) {
div.addEventListener('click', function(event) {
event.target.style.backgroundColor = 'Green';
});
}
DOM Collections are array-like objects
function min() {
return Math.min.apply(null, arguments);
}
min(1, 2, 3); //1
We can use Function.prototype.apply with arguments
In JavaScript there are different ways to perform pseudo-concurrency:
Callbacks allow us to decide the continuation function when the asynchronous operation is complete.Â
setTimeout(function() {
console.log('Done!');
}, 1000);
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
$.ajax(url, {
success: function(...) { ... },
error: function(...) { ... }
});
Callbacks are easy but they have some drawbacks:
One solution: Async.js
async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.series([
function(){ ... },
function(){ ... }
]);
Promises seem synchronous code
checkUser(credentials).then(loginSuccess, loginError).then(gotoMainPage);
$.ajax(url).done(function() {...}).fail(function() {...});
Q: a promises implementation
Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
// Do something with value4
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
Example of naive promise implementation
function Promise() {
this._dones = [];
this._errors = [];
}
Promise.prototype.done = function (onSuccess) {
this._dones.push(onSuccess);
return this;
};
Promise.prototype.error = function (onError) {
this._errors.push(onError);
return this;
};
Promise.prototype.resolve = function (result) {
this._complete('resolve', result);
};
Promise.prototype.reject = function (error) {
this._complete('reject', error);
};
Promise.prototype._complete = function (action, data) {
// disallow multiple calls to resolve or reject
this.resolve = this.reject = function () {
throw new Error('Promise already completed.');
};
var functions = action === 'resolve' ? this._dones : this._errors;
functions.forEach(function(fn) {
fn(data);
});
this._dones = [];
this._errors = [];
};
Example of use naive promise implementation
function doWork(time, callback) {
if (typeof time !== 'number' || time <= 0) {
setTimeout(function() {
callback('Bad format', null);
}, 200); //Simulates a failure
} else {
setTimeout(function() {
callback(null, 'Done!');
}, time); //Simulates a long work
}
}
function work(time) {
var promise = new Promise();
doWork(time, function(error, result) {
if(error) {
promise.reject(error);
} else {
promise.resolve(result);
}
});
return promise;
}
work(2000).done(function(result) {
console.log(result);
}).error(function(error) {
console.log(error);
});
work(4000).done(function(result) {
console.log(result);
}).done(function(result) {
console.log("In second success handler");
}).error(function(error) {
console.log(error);
});
work(-2000).done(function(result) {
console.log(result);
}).error(function(e) {
console.log("In first error handler");
}).error(function(e) {
console.log(e);
});
Why promises are so cool?
Events are great when there are multiple receivers (listeners) interested  on the response:
document.addEventListener('click', function(event) {....});
$('#table').on( "click", function() {
...
});
var server = http.createServer(function (req, res) {
...
req.on('data', function (chunk) {
...
});
req.on('end', function () {
...
});
...
});
Example of naive events implementation
function EventEmitter() {
var events = {};
this.addListener = function (event, listener) {
if (!events[event]) {
events[event] = [];
}
events[event].push(listener);
};
this.emitEvent = function (event) {
if (events[event]) {
events[event].forEach(function(listener) {
listener(event);
});
}
};
}
Example of use naive events implementation
var ee = new EventEmitter();
function listener1(event) {
console.log('The ' + event + ' event has been recived by listener1.');
}
function listener2(event) {
console.log('The ' + event + ' event has been recived by listener2.');
}
ee.addListener('foo', listener1); //Receiver
ee.addListener('foo', listener2); //Receiver
ee.addListener('bar', listener1); //Receiver
ee.emitEvent('foo'); //Emitter
ee.emitEvent('bar'); //Emitter
Benefits of using events
Cons of using events
New player in ECMAScript 6
function* foo(x) {
yield x + 1;
var y = yield null;
return x + y;
}
var gen = foo(5);
gen.next(); // { value: 6, done: false }
gen.next(); // { value: null, done: false }
gen.send(8); // { value: 13, done: true }
Co: a generators implementation
co(function *(){
try {
var res = yield get('http://badhost.invalid');
console.log(res);
} catch(e) {
console.log(e.code) // ENOTFOUND
}
})()
Pros
Cons
var findLargest = require('./findLargest');
findLargest('./path/to/dir', function (er, filename) {
if (er) return console.error(er);
console.log('largest file was:', filename);
});
Nested callback approach:
var fs = require('fs');
var path = require('path');
module.exports = function (dir, cb) {
fs.readdir(dir, function (err, files) {
if (err) return cb(err);
var counter = files.length;
var errored = false;
var stats = [];
files.forEach(function (file, index) {
fs.stat(path.join(dir, file), function (er, stat) { // <- parallel process
if (errored) return;
if (er) {
errored = true;
return cb(er);
}
stats[index] = stat;
if (--counter == 0) {
var largest = stats
.filter(function (stat) { return stat.isFile(); })
.reduce(function (prev, next) {
return prev.size > next.size ?
prev :
next;
}, 0);
cb(null, files[stats.indexOf(largest)]);
}
});
});
});
};
Modular callback approach:
var fs = require('fs');
var path = require('path');
module.exports = function (dir, cb) {
fs.readdir(dir, function (er, files) {
if (er) return cb(er);
var paths = files.map(function (file) {
return path.join(dir,file);
});
getStats(paths, function (er, stats) {
if (er) return cb(er);
var largestFile = getLargestFile(files, stats);
cb(null, largestFile);
});
});
};
function getStats (paths, cb) {
var counter = paths.length;
var errored = false;
var stats = [];
paths.forEach(function (path, index) {
fs.stat(path, function (er, stat) {
if (errored) return;
if (er) {
errored = true;
return cb(er);
}
stats[index] = stat;
if (--counter == 0) cb(null, stats);
});
});
}
function getLargestFile (files, stats) {
var largest = stats
.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
return files[stats.indexOf(largest)];
}
function max(prop, x, y) {
return x[prop] > y [prop] ?
x :
y;
}
function isFile(stat) {
return stat.isFile();
}
Async library approach:
var fs = require('fs');
var async = require('async');
var path = require('path');
module.exports = function (dir, cb) {
async.waterfall([
function (next) {
fs.readdir(dir, next)
},
function (files, next) {
var paths = files.map(function (file) { return path.join(dir, file) });
async.map(paths, fs.stat, function (er, stats) {
next(er, files, stats);
});
},
function (files, stats, next) {
var largest = stats
.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
next(null, files[stats.indexOf(largest)]);
}
], cb);
}
function max(prop, x, y) {
return x[prop] > y [prop] ?
x :
y;
}
function isFile(stat) {
return stat.isFile();
}
Q promises library approach:
var fs = require('fs');
var path = require('path');
var Q = require('q');
var fs_readdir = Q.denodeify(fs.readdir);
var fs_stat = Q.denodeify(fs.stat);
module.exports = function (dir) {
return fs_readdir(dir)
.then(function (files) {
var promises = files.map(function (file) {
return fs_stat(path.join(dir,file));
});
return Q.all(promises).then(function (stats) {
return [files, stats];
});
})
.then(function (data) {
var files = data[0]
var stats = data[1]
var largest = stats.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
return files[stats.indexOf(largest)];
});
};
function max(prop, x, y) {
return x[prop] > y [prop] ? x : y;
}
function isFile(stat) {
return stat.isFile();
}
//use
var findLargest = require('./findLargest')
findLargest('./path/to/dir')
.then(function (filename) {
console.log('largest file was:', filename);
})
.catch(console.error);
Co generator library approach:
var co = require('co');
var thunkify = require('thunkify');
var fs = require('fs');
var path = require('path');
var readdir = thunkify(fs.readdir);
var stat = thunkify(fs.stat);
module.exports = co(function* (dir) {
var files = yield readdir(dir);
var stats = yield files.map(function (file) {
return stat(path.join(dir,file))
});
var largest = stats
.filter(isFile)
.reduce(max.bind(null, 'size'), 0);
return files[stats.indexOf(largest)];
});
function max(prop, x, y) {
return x[prop] > y [prop] ?
x :
y;
}
function isFile(stat) {
return stat.isFile();
}
$ sudo npm install --global traceur
$ traceur --out build.js --script my_source_file.js
$ node build.js
var users = [
{ name: 'Jack', age: 21 },
{ name: 'Ben', age: 23 },
{ name: 'Adam', age: 22 }
];
console.log(users.map(function(user) { return user.age; }));// [21, 23, 22]
var ages = users.map(user => user.age);
var sum = ages.reduce((a, b) => a + b);
console.log(sum); // 66
var agesDoubled = users.map(user => {
var age = user.age;
return age * 2;
});
console.log(agesDoubled); // [42, 46, 44]
//Lexical this binding
var person = {
name: 'Peter',
getName: function() {
return this.name;
}
};
console.log(person.getName()); // "Peter"
var getPeterName = person.getName;
console.log(getPeterName()); // "undefined" [in Node]
var person = {
name: 'Peter',
getName: () => {
return this.name;
}
};
console.log(person.getName()); // "Peter"
var getPeterName = person.getName;
console.log(getPeterName()); // "Peter"
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// app.js
module math from "lib/math";
console.log("2Ï€ = " + math.sum(math.pi, math.pi));
// otherApp.js
import {sum, pi} from "lib/math";
alert("2Ï€ = " + sum(pi, pi));
// lib/mathplusplus.js
export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
// app.js
module math from "lib/mathplusplus";
import exp from "lib/mathplusplus";
alert("2Ï€ = " + exp(math.pi, math.e));
// Dynamic loading – ‘System’ is default loader
System.import('lib/math').then(function(m) {
alert("2Ï€ = " + m.sum(m.pi, m.pi));
});
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
var map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original"); //key must be a non primitive, value can be of any type
// later
var value = map.get(element);
console.log(value); // "Original"
// later still - remove reference
element.parentNode.removeChild(element);
element = null;
value = map.get(element);
console.log(value); // undefined <- avoids memory leaks
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
for (var i = 0; i < 10; i++);
console.log(i); // 10
for (let j = 0; j < 10; j++);
console.log(j); // Error: j is not defined
const x = 4;
x = 9; // Error: x is read-only
class SkinnedMesh extends THREE.Mesh {
constructor(geometry, materials) {
super(geometry, materials);
this.idMatrix = SkinnedMesh.defaultMatrix();
this.bones = [];
this.boneMatrices = [];
//...
}
update(camera) {
//...
super.update();
}
static defaultMatrix() {
return new THREE.Matrix4();
}
}
// Pseudo-code of Array
class Array {
constructor(...args) { /* ... */ }
static [Symbol.create]() {
// Install special [[DefineOwnProperty]]
// to magically update 'length'
}
}
// User code of Array subclass
class MyArray extends Array {
constructor(...args) { super(...args); }
}
// Two-phase 'new':
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
var arr = new MyArray();
arr[1] = 12;
arr.length == 2
var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return "d " + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};
// Basic literal string creation
`In JavaScript '\n' is a line-feed.`
// Multiline strings
`In JavaScript this is
not legal.`
// Construct a DOM query
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
// list matching
var [a, , b] = [1,2,3];
// object matching
var { op: a, lhs: { op: b }, rhs: c }
= getASTNode()
// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode()
// Can be used in parameter position
function g({name: x}) {
console.log(x);
}
g({name: 5})
// Fail-soft destructuring
var [a] = [];
a === undefined;
// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1;
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3); // 15
function f(x, ...y) {
// y is an Array
return x * y.length;
}
f(3, "hello", true); // 6
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]); //6
//Classical way
function idMaker(){
var index = 0;
return {
next: function(){
return {value: index++, done: false};
}
}
}
var it = idMaker();
console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...
//New way
function* idMaker(){
var index = 0;
while(true)
yield index++;
}
var gen = idMaker();
console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...
function* fibonacci() { // a generator function
let [prev, curr] = [0, 1];
for (;;) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
// truncate the sequence at 1000
if (n > 1000)
break;
print(n);
}
let fibonacci = {
[Symbol.iterator]() {
let pre = 0, cur = 1;
return {
next() {
[pre, cur] = [cur, pre + cur];
return { done: false, value: cur }
}
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
print(n);
}
let articleParagraphs = document.querySelectorAll("article > p");
for (let paragraph of articleParagraphs) {
paragraph.classList.add("read");
}
[for (i of [ 1, 2, 3 ]) i*i ]; // [ 1, 4, 9 ]
var abc = [ "A", "B", "C" ];
[for (letters of abc) letters.toLowerCase()]; // [ "a", "b", "c" ]
var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ];
[for (year of years) if (year > 2000) year]; // [ 2006, 2010, 2014 ]
var numbers = [ 1, 2, 3 ];
var letters = [ "a", "b", "c" ];
var cross = [for (i of numbers) for (j of letters) i+j];
// [ "1a", "1b", "1c", "2a", "2b", "2c", "3a", "3b", "3c" ]
// Proxying a normal object
var target = {};
var handler = {
get: function (receiver, name) {
return `Hello, ${name}!`;
}
};
var p = new Proxy(target, handler);
p.world === 'Hello, world!';
/ Proxying a function object
var target = function () { return 'I am the target'; };
var handler = {
apply: function (receiver, ...args) {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p() === 'I am the proxy';
var firstName = Symbol("first name property");
var person = {};
person[firstName] = "Nicholas";
console.log(person[firstName]); // "Nicholas"
//You need the symbol to access the property
console.log(Symbol("first name property")); //undefined
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll('*')) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1,2,3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0,0) })
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration);
})
}
var p = timeout(1000).then(() => {
return timeout(2000);
}).then(() => {
throw new Error("hmm");
}).catch(err => {
return Promise.all([timeout(100), timeout(200)]);
});
function factorial(n, acc = 1) {
'use strict';
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
// Stack overflow in most implementations today,
// but safe on arbitrary inputs in eS6
factorial(100000);
Better than more theory, we are going to practice TDD with an exercise. You can find the complete solution in here.
This is the description:
Algebraic Data Types (not to be confused with Abstract Data Types, ADT) are composed types (normally recursively) and for those who have defined a serie of operations.
For example in Haskell, natural numbers can be defined in this way:
data Nat = Zero | Succ Nat
So:
zero = Zero
one = Succ Zero
two = Succ (Succ Zero)
three = Succ (Succ (Succ Zero))
...
The objetive of this kata is to define an algebra for the Nat data type
.
In JavaScript
we are going to use the next definition for natural numbers:
function zero(){}
function succ(nat) {
return function() {
return nat;
};
}
zero
and succ()
are constructor functions of the Nat
data type. A nat number could be the zero
function (although you will never really call this function) or a function obtained by calling the function succ()
with the previous natural number. This definition corresponds roughly to the definition of Church numbers
Â
zero nat number corresponds to zero
function
one nat number is obtained by calling succ(zero)
two nat number is obtained by calling succ(succ(zero))
... and so on
Or more precisely:
var one = succ(zero);
var two = succ(one) = succ(succ(zero));
...
The algebra are composed by the next operations:
natToInt(nat) -> int // obtains the integer value of the nat number. For example natToInt(succ(succ(zero)) returns 2
intToNat(int) -> nat // converts an integer to a natural value. For example intToNat(2) does the same that succ(succ(zero))
toString(nat) -> string // returns a string representation of the natural value (see examples below)
add(nat1, nat2) -> nat // given two natual numbers, returns the natural sum of them
mul(nat1, nat2) -> nat // multiplies two naturals
compareTo(nat1, nat2) -> int // returns a number less than zero when nat1 < nat2; greater than zero when nat1 > nat2; and 0 when nat1 === nat2
Some examples:
natToInt(zero); // 0
natToInt(succ(zero)); // 1
natToInt(succ(succ(zero))); // 2
intToNat(0) === zero; // true
intToNat(1); // is the same than succ(zero)
intToNat(2); // is the same than succ(succ(zero))
natToInt(intToNat(10)); // 10
toString(zero); // "zero"
toString(succ(zero)); // "succ(zero)"
toString(succ(succ(zero))); // "succ(succ(zero))"
add(zero, zero) === zero; // true
add(succ(zero), zero); // is the same that succ(zero)
add(succ(zero), succ(zero)); // is the same that succ(succ(zero))
add(succ(zero), succ(succ(zero))); // is the same that succ(succ(succ(zero)))
natToInt(add(zero, succ(zero))); // 1
natToInt(add(intToNat(10), intToNat(15))); // 25
mul(zero, zero) === zero; // true
mul(suc(zero), zero) === zero; // true
natToInt(mul(intToNat(10), intToNat(15))); // 150
compareTo(zero, zero); // 0
compareTo(zero, succ(zero)) < 0; // true
compareTo(succ(zero), 0) > 0; // true
compareTo(suc(zero), succ(zero)); // 0
natToInt(compareTo(intToNat(10), intToNat(15))) < 0; // true
Additional notes:
while
or for
keywords in your solution.add()
operation could be:function add(nat1, nat2) {
return intToNat(natToInt(nat1) + natToInt(nat2));
}
But you must not use arithmetic operators (+, - , *, /, %, ...) in implementing the functions add()
and mul()
.
Let's start with the first function: natToInt()
This could be the very first dummy test:
var assert = require("assert");
var zero = function(){};
function succ(nat) {
return function() {
return nat;
};
}
describe('Nat numbers tests', function(){
describe('natToInt() tests', function(){
it('should return 0 for the zero function', function(){
assert.equal(natToInt(zero), 0);
});
});
});
$ mocha test.js
Nat numbers tests
natToInt() tests
1) should return 0 for the zero function
0 passing (6ms)
1 failing
1) Nat numbers tests natToInt() tests should return 0 for the zero function:
ReferenceError: natToInt is not defined
at Context.<anonymous> (/home/mint/test.js:14:20)
Now, we can write a simple implementation to pass the test:
...
function natToInt(nat) {
return 0;
}
describe('Nat numbers tests', function(){
describe('natToInt() tests', function(){
it('should return 0 for the zero function', function(){
assert.equal(natToInt(zero), 0, 'natToInt(zero) should return 0');
});
});
});
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
1 passing (8ms)
This implementation is not very useful since it only applies to the number zero. So we go back to cause a failure by adding a test case.
...
var one = succ(zero);
describe('Nat numbers tests', function(){
describe('natToInt() tests', function(){
it('should return 0 for the zero function', function(){
assert.equal(natToInt(zero), 0, 'natToInt(zero) should return 0');
});
it('should return 1 for the one nat number', function(){
assert.equal(natToInt(one), 1, 'natToInt(one) should return 1');
});
});
});
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
1) should return 1 for the one nat number
1 passing (7ms)
1 failing
1) Nat numbers tests natToInt() tests should return 1 for the one nat number:
AssertionError: natToInt(one) should return 1
at Context.<anonymous> (/home/mint/test.js:24:14)
We can fix this by changing the implementation:
...
function natToInt(nat) {
if (nat === zero) return 0;
else return 1;
}
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
✓ should return 1 for the one nat number
2 passing (8ms)
Easily we can break the implementation by adding a new test:
...
var one = succ(zero);
var two = succ(one);
...
it('should return 2 for the two nat number', function(){
assert.equal(natToInt(two), 2, 'natToInt(two) should return 2');
});
This procedure seems pointless. But it is just the opposite, because it helps us reason about the correct solution.
Moreover, the solution is not as simple as it seems:
...
function natToInt(nat) {
if (nat === zero) return 0;
if (nat === one) return 1;
else return 2;
}
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
1) should return 1 for the one nat number
✓ should return 2 for the one nat number
2 passing (8ms)
1 failing
1) Nat numbers tests natToInt() tests should return 1 for the one nat number:
AssertionError: natToInt(one) should return 1
Why? Very easy, check out the code:
var zero = function(){};
var one = succ(zero);
var two = succ(one);
function succ(nat) {
return function() {
return nat;
};
}
function natToInt(nat) {
if (nat === zero) return 0;
if (nat === one) return 1;
else return 2;
}
succ(zero) === succ(zero); //false
zero === zero; //true
So let's make a first attempt to seriously implement the natToInt() function:
function natToInt(nat) {
var result = 0;
while (nat !== zero) {
nat = nat();
result++;
}
return result;
}
This is not a very FP approach, but it is working.
However, it violates one of the requirements: "iterative implementations are not allowed. Do not use while
or for
keywords in your solution"
So we'll add a test to invalidate the solution:
it('should not use loops', function(){
assert.ok(/for|while/.test(natToInt.toString()) === false, '"for" or "while" keywords are not allowed');
});
$ mocha test.js
Nat numbers tests
natToInt() tests
✓ should return 0 for the zero function
✓ should return 1 for the one nat number
✓ should return 2 for the one nat number
1) should ot use loops
3 passing (7ms)
1 failing
1) Nat numbers tests natToInt() tests should ot use loops:
AssertionError: "for" or "while" keywords are not allowed
A recursive implementation for pass the tests would be :
function natToInt(nat) {
return nat === zero ? 0 : 1 + natToInt(nat());
}
It is time that you are going to implement the rest of the functions.
Now we have enough confidence that the natToInt() function behaves correctly.
You can use this function in the implementation or testing the rest of the exercise as we know it will not fail (really you will use it only in tests).
We can also make changes in the implementation safely