Skip to content

Latest commit

 

History

History
506 lines (379 loc) · 12.8 KB

File metadata and controls

506 lines (379 loc) · 12.8 KB

Functional Programming Day 1

Essential Questions

  • 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?

Vocabulary

  • abstraction
  • first-class functions
  • higher-order functions
  • callback function
  • functional programming
  • programming paradigm
  • pure function
  • side effects
  • composability
  • (im)mutability

Key Methods

  • Array.prototype.forEach()
  • Array.prototype.map()
  • Array.prototype.filter()
  • Array.prototype.reduce()
  • Array.prototype.sort()
  • Array.prototype.some()
  • Array.prototype.every()

Review functions (5 minutes)

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.

First Class Citizens (15 minutes)

  • 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 and fizzbuzz()?
  • What is the value of x in each case?

Higher Order Functions (10 minutes)

A higher order function is a function that takes in another function as an argument.

  • If we pass in a function to print, then x 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 Pass Functions to Functions? (15 minutes)

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

Challenge (15 minutes)

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

Functional Programming Day 2

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.

Arrow Functions

Function Definitions/Declarations

function add(a, b) {
    let sum = a + b;
    return sum;
}

Function Expression

const add = function(a, b) {
    let sum = a + b;
    return sum;
}

Arrow Function

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;

Higher-Order Array Methods

Vocab

  • callback function — A function provided to a higher order function to be executed by the higher order function.

For Each

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 names

    const 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 returns undefined

Map

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 objects

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

Filter

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

Other higher order methods

Composability

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.