- What is functional programming and what are its benefits?
- Explain what it means that functions are first-class citizens in JavaScript.
- What are higher-order functions and how does this result from the concept of first-class functions?
- Thus far, how have we abstracted over data? How have we abstracted over actions (particularly, actions performed on data)?
- How do JavaScript's higher-order array methods allow us to further leverage abstraction?
- abstraction
- first-class functions
- higher-order functions
- callback function
- functional programming
- programming paradigm
- pure function
- side effects
- composability
- (im)mutability
Array.prototype.forEach()
Array.prototype.map()
Array.prototype.filter()
Array.prototype.reduce()
Array.prototype.sort()
Array.prototype.some()
Array.prototype.every()
function print(x) {
console.log(x);
}
print("hello")
- What do you expect to be logged to the console?
- What happens if I change hello to "Hey"? Will we still see "hello"?
Functions take in dynamic data. They take in some argument whose value we don't know and they do something with it.
- In JavaScript, function's are first-class citizens. What does it mean for a value to be a first-class citizen?
let foo = { name: "ben" }
print(foo);
- Basically, we can use function's like any other data value.
Referencing a funtion (not invoking it) returns a function object
- What if we created a function and passed it to
print
?
function foo() {
return "foo"
}
print(foo);
- We see the function definition printed to the console. What happens if I do this?
function fizzbuzz() {
return "fizzbuzz"
}
print(fizzBuzz)
- Adding in a
debugger
:
function print(x) {
debugger;
console.log(x);
}
function fizzbuzz() {
return "fizzbuzz";
}
debugger;
print(fizzbuzz);
print(fizzbuzz());
- What is the difference between passing in
fizzbuzz
andfizzbuzz()
? - What is the value of
x
in each case?
A higher order function is a function that takes in another function as an argument.
- If we pass in a function to
print
, thenx
is the function object. - What happens if instead of printing
x
, we invoke it?
function execute(x) {
debugger;
x();
}
function sayHello() {
console.log("hello world!");
}
execute(sayHello);
// execute(sayHello());
- Why do we pass data to a function?
- transform dynamic inputs => outputs
function print3Times(data) {
let timesExecuted = 0;
while (timesExecuted < 3) {
console.log(data);
timesExecuted++;
}
}
let greeting = "hello world!";
print3Times(greeting);
- Why would we ever want to pass a function to a function?
- transform dynamic actions => outputs
function repeat3Times(action) {
let timesExecuted = 0;
while (timesExecuted < 3) {
action(); // execute the parameter now that its a function
timesExecuted++;
}
}
function greet() {
console.log("hello world!");
}
function bye() {
console.log("goodbye!")
}
repeat3Times(sayHello);
repeat3Times(sayBye);
We can also add in arguments that our action
function should be invoked with.
function repeat3Times(action, data) {
debugger;
let timesExecuted = 0;
while (timesExecuted < 3) {
action(data); // execute the parameter now that its a function
timesExecuted++;
}
}
function sayHello(name) {
console.log("hello " + name);
}
function bye(name) {
console.log("goodbye " + name)
}
repeat3Times(sayHello, "ben");
repeat3Times(sayGoodbye, "carmen");
Write a function called forEach
that takes in an array
and an action
(a function).
The function should invoke action
once per value in the array
with three arguments:
- the current element of the
array
- the index of the current element
- the entire
array
itself
By passing in all three of these values to the action
function, the action
function has full access to all of the data you would normally have access to in a for
loop iteration.
solution
function forEach(array, action) {
for (let i = 0; i < array.length; i++) {
// pass in the element, its index, and the entire array
action(array[i], i, array);
}
}
let names = ['ben', 'carmen', 'motun']
function sayHello(name){
console.log(`Hello ${name}!`);
}
// For each element in names, invoke sayHello()
forEach(names, sayHello);
// For each element in names, invoke the arrow function
forEach(names, name => {
console.log(`Goodbye ${name}`);
});
Abstractions hide details so we can think at a higher level
What does this code do?
console.log(Math.ceil(Math.random() * 6));
console.log(Math.ceil(Math.random() * 6));
console.log(Math.ceil(Math.random() * 6));
We can abstract this by writing a function rollDice()
function rollDice(sides) {
console.log(Math.ceil(Math.random() * sides));
}
rollDice(6);
rollDice(12);
rollDice(20);
**Q: So, how do we abstract over data?**
We write functions! Parameters let us work with abstract data values.
function add(a, b) {
let sum = a + b;
return sum;
}
const add = function(a, b) {
let sum = a + b;
return sum;
}
const add = (a, b) => {
let sum = a + b;
return sum;
}
If the arrow function has only 1 parameter, the parentheses around the parameter can be left out.
const double = num => {
let twice = num * 2;
return twice;
}
If the arrow function's body only has one line, the {}
can be left out. The arrow function will return whatever the expression evaluates to (the return
keyword can also be left out).
const double = num => num * 2;
Vocab
- callback function — A function provided to a higher order function to be executed by the higher order function.
MDN:
The forEach()
method executes a provided function once for each array element.
Note: Every array higher-order method will invoke the callback function with three arguments:
element, index, array
.
-
Example 1: Print
Hello [Name]!
for each value in an array of namesconst names = ['ben', 'carmen', 'motun']; // Print a greeting using a for loop for (let i = 0; i < names.length; i++) { let name = names[i]; console.log(`hello ${name}`); } // Print a greeting using forEach and a function // Note: the function will be given each element, its index, and the array, // but it doesn't have to use all of those values. Often, only the element // is needed and the other parameters can be left out. const sayHello = (name, i, array) => console.log(`hello ${name}`); names.forEach(sayHello); // Or just pass the arrow function directly to forEach names.forEach( name => console.log(`hello ${name}`) )
-
Example 2: Double the values in an array and put them into another array
// Double the values in numbers const numbers = [10, 20, 30]; const doubleArray = []; numbers.forEach( number => doubleArray.push(number * 2) ); console.log(doubleArray); // [20, 40, 60]
Note:
Array.prototype.forEach
returnsundefined
MDN:
The map()
method creates a new array populated with the results of calling a provided function on every element in the calling array.
-
Example 1: Double the values in an array and put them into another array
// Double the values in numbers const numbers = [10, 20, 30]; function transform(num, i, array) { return num * 2; } console.log(numbers.map(transform)); // => [20, 40, 60] console.log(numbers.map(num => num * 2)); // => [20, 40, 60]
-
Example 2: Get an array containing the length of every word in the array:
const names = ['ben', 'carmen', 'motun']; const nameLengths = names.map(word => word.length); console.log(nameLengths); // => [3, 6, 5]
-
Example 3: Extract the
email
property from an array of objectsconst users = [ { name: "Ben", email: "ben@marcylabschool.org" }, { name: "Carmen", email: "carmen@marcylabschool.org" }, { name: "Motun", email: "motun@marcylabschool.org" }, ]; const emails = users.map(user => user.email); console.log(emails); // [ // "ben@marcylabschool.org", // "carmen@marcylabschool.org", // "motun@marcylabschool.org", // ]
**Q: How can we implement the `map` function? It should take in an `array` and a `callback`**
function map(arr, callback) {
const toReturn = [];
for (let i = 0; i < arr.length; i++) {
const result = callback(arr[i]);
toReturn.push(result);
}
return toReturn;
}
const nums = [1,2,3];
const doubled = map(nums, num => num * 2);
MDN:
The filter()
method creates a shallow copy of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function.
-
Example 1: Keep only the even values
let numbers = [1,2,3,4,5,6]; let evens = numbers.filter(num => num % 2 === 0); console.log(evens); // => [2, 4, 6]
-
Example 2: Keep only users who are NOT admins and who are at least 18
const users = [ { username: "soccerKid123", isAdmin: false, age: 20 }, { username: "iHeartPonies", isAdmin: false, age: 16 }, { username: "skaterboi666", isAdmin: false, age: 19 }, { username: "mrRobot", isAdmin: true, age: 25 }, ]; const selectedUsers = users.filter(user => !user.isAdmin && user.age >= 18) console.log(selectedUsers); // [ // { username: "soccerKid123", isAdmin: false, age: 20 }, // { username: "skaterboi666", isAdmin: false, age: 19 } // ]
**Q: How can we implement the `filter` function? It should take in an `array` and a `test` callback**
function map(arr, test) {
const toReturn = [];
for (let i = 0; i < arr.length; i++) {
const result = test(arr[i]);
if (result) {
toReturn.push(result);
}
}
return toReturn;
}
const nums = [1,2,3,4];
const evens = map(nums, num => num % 2 === 0);
The functions map()
and filter()
both return arrays. As a result, we can chain them together to compose elegant one-line code.
-
Example 1: Double the even values in an array
const nums = [1,2,3,4,5]; // one at a time const evens = nums.filter(num => num % 2 === 0); // [2, 4] const doubledEvens = evens.map(num => num * 2); // [4, 8] // in one line const doubledEvens = nums.filter(num => num % 2 === 0).map(num => num * 2); // [4, 8]
-
Example 2: Get the usernames of users who are admins
const users = [ { username: "soccerKid123", isAdmin: false, age: 20 }, { username: "iHeartPonies", isAdmin: false, age: 16 }, { username: "skaterboi666", isAdmin: true, age: 19 }, { username: "mrRobot", isAdmin: true, age: 25 }, ]; // one at a time const admins = users.filter(user => user.isAdmin) const adminUsernames = admins.map(user => user.username); // in one line const adminUsernames = users.filter(u => u.isAdmin).map(u => u.username); console.log(adminUsernames); // => ["skaterboi666", "mrRobot"]
**Q: So, how do we abstract over actions?**
We write higher-order functions! Callback functions as parameters let us work with abstract actions.