Higher Order Functions (Continued)

CSCI-UA.0480-008

Abstraction

What's abstraction again?

pollock

Abstraction is the process of hiding away necessary, yet immaterial details so that the programmer can focus on solving the actual higher-level problem…

What are some features in JavaScript that facilitate abstraction?

  • (obvs) functions
  • functions as first-class objects
  • higher order functions

Higher Order Functions

Greeeaaaat. What's a higher order function, though?

A higher order function is a function that does at least one of the following things: →

  • accepts a function or functions as a parameter
  • returns a function

Array Methods

We learned about (and even re-implemented) four array methods that accepted functions as arguments (specifically as callback functions). What were these array methods, and what do they do?

  • forEach - calls callback on each array element
  • filter - returns a new filtered array based on test/callback
  • map - returns a new transformed array based on callback
  • reduce - reduces original array to single value based on callback

forEach

An array's forEach method executes a callback on every array element.

  • 1 parameter - a callback function that is executed with the current element, index and original array
  • returns undefined
  • example

// log double the value of every element in the array
const numbers = [3, 4, 5];
numbers.forEach(function(element) {
	console.log(element);
});

Also, our homegrown implementation as a standalone function.


function forEach(arr, action) {
	for (let i = 0; i < arr.length; i++) {
		action(arr[i]); 
	}
}

filter

An array's filter method returns a new array of elements that each pass some test/callback.

  • 1 parameter a function, test, that is executed with the current element, index and original array… and returns either true or false
  • returns a new filtered array
  • example (note… indexOf returns the index of a substring in a string… and it returns -1 if the substring is not found)

// only give back the strings that start with 'ba'

const words = ['foo', 'bar', 'baz', 'qux'];
const filtered_words = words.filter(function(word) {
	return word.indexOf('ba') !== -1;
});
console.log(filtered_words);

A DIY Filter Function

We implemented filter as a standalone function as well. The algorithm was fairly compact; what was it?

  1. create a new array to hold the filtered elements
  2. go through every element in the original array
  3. check to see if it passes the test (execute the callback on it)
  4. if it passes, add it to the new array


And the implementation…


function filter(arr, test) {
	const filtered = [];
	arr.forEach(function(element) {
		if(test(element)) {
			filtered.push(element)
		}
	});
	return filtered;
}

Now let's check out map and reduce.

Let's build our own version of each first …

Transforming with Map

Create a function called map that creates a new array based on calling a function on each element of an array passed in:

  • 2 parameters - an array to base new array off of and a function to transform each element
  • returns a new array with each element of original transformed by callback function
  • test it by creating an array of words and transforming that array into an array of words each with two exclamation points at the end


What would the algorithm be?

  1. create a new array to hold the transformed elements
  2. go over every element in the original array
  3. call the function on each element
  4. add the result of calling the function to the other array

Map Continued

Here's a potential implementation, along with example usage, of our own map implementation.


function map(arr, transform) {
	const transformed = [];
	arr.forEach(function(element) {
		transformed.push(transform(element));
	});
	return transformed;
}
const result = map(['hello', 'hey', 'hi'], function(greeting) {return greeting + '!!'});
console.log(result);

Using an Array's Map Method

Again, JavaScript arrays already have a map method

  • 1 parameter - a callback (the function that transforms each element)
  • the callback is executed with the current value, index and original array
  • the callback returns a new value/object to be added
  • map returns a new array with every element transformed


Try using it to change every word in the list ['hey','yo','sup'] to uppercase with an exclamation point.


words = ['hey', 'yo', 'sup']
const shoutedWords = words.map(function(word) {
	return word.toUpperCase() + '!';
});
console.log(shoutedWords);

Reducing an Array to a Single Value

Create a function called reduce… that repeatedly calls a function to reduce an array to a single value.

  • 3 parameters
    • the original array
    • a callback function to perform the reduction
    • a start value to initialize the variable that will hold the single value to be returned
  • the callback should
    • take both the current accumulated value, and the current element
    • return the new accumulated value
  • an example in the next slide… →

Reduce Continued


// result is 20
console.log(reduce([4, 12, 5], function(accum, ele) {
  return accum + ele;  
}, 0));

What do you think the algorithm for reduce would look like?

  1. create a variable, an accumulator, that will be returned
  2. initialize it to start
  3. for every element in the original array…
  4. apply the callback…
  5. set the accumulator to the result of the callback
  6. return the accumulator

Our Version of Reduce


function reduce(arr, combine, start) {
  const accum = start;
  arr.forEach(function(ele){
    accum = combine(accum, ele); 
  }); 
  return accum;
}

console.log(reduce([4, 12, 5], function(accum, ele) {
  return accum + ele;  
}, 0));

Reduce Continued

Here's an example of finding the minimum (uses first element as initial min) with reduce:


const numbers = [-5, -2, -1, -10, -3];

console.log(reduce(numbers, function(accum, ele) {
  if(accum < ele) {
    return accum;
  } else {
    return ele; 
  }
}, numbers[0]));

Using an Array's Reduce Method

JavaScript arrays have a built-in reduce method. (Note that in other functional languages, reduce is sometimes called fold.)

  • 2 parameters a callback function (the function that reduces the array) and the optional start value of the accumulator (if the start doesn't exist, it uses the first element of the array that it was called on)
  • callback is executed with accumulator, element value, element index and original array object
  • callback returns a value (the new value for the internal accumulator)
  • reduce returns a single value (that value can be an Array, Object, etc.)


Try using it to calculate the product of all of the elements in [2, 5, 4, 3,].


[2, 5, 4, 3,].reduce(function(product, currentNumber ){
	return product * currentNumber;
}, 1);

Aaaaand Freestyle

Using forEach, filter, map, and/or reduce, can you try to count all of the face cards in the following array?


const cards = [{'suit':'♦', 'face':'4'},
             {'suit':'♠', 'face':'J'},
             {'suit':'♠', 'face':'Q'},
             {'suit':'♣', 'face':'Q'},
             {'suit':'♠', 'face':'2'},
             {'suit':'♦', 'face':'7'},
             {'suit':'♥', 'face':'K'}];

jack

Counting Face Cards

Let's try the most convential way… together.


let count = 0;
cards.forEach(function(card) {
	if (['K', 'Q', 'J'].indexOf(card.face) !== -1) {
		count += 1;
	} 
});
console.log(count);

Now write your own versions using the following two methods…

  1. with an Array's filter method
  2. …and an Array's reduce method

Composability / Chaining

Try using all 3 methods to run an analysis on some stats from game 5 of the 2014 NBA finals.

  • print out the average shooting percentage for each team, the Spurs and the Heat
  • use the object below as the data set
  • each player is associated with either team through the "team" property
  • shooting percentage is field goals made ("FGM") divided by field goals attempted ("FGA")

const players = [
{"lastName":"Duncan", "team":"Spurs", "FGM":5, "FGA":10},
{"lastName":"Parker", "team":"Spurs", "FGM":7, "FGA":18},
{"lastName":"Ginobili", "team":"Spurs", "FGM":6, "FGA":11},
{"lastName":"James", "team":"Heat", "FGM":10, "FGA":21},
{"lastName":"Wade", "team":"Heat", "FGM":4, "FGA":12},
{"lastName":"Bosh", "team":"Heat", "FGM":6, "FGA":14}
];

Shooting Percentages for The Spurs and Heat

Note that you can continue to chain calls to map, filter, and reduce as long as you get an array back.

Here's a potential solution. There are a few utility functions declared for clarity and readability.


function heat(player) { return player.team === 'Heat'; }
function spurs(player) { return player.team === 'Spurs'; }
function shootingPercentage(player) { return player.FGM / player.FGA; }
function sum(curTotal, num) { return curTotal + num}
function average(arr) { return arr.reduce(sum, 0) / arr.length; }

console.log(average(players.filter(heat).map(shootingPercentage)).toFixed(2));
console.log(average(players.filter(spurs).map(shootingPercentage)).toFixed(2));

Seems to map more closely to what we're actually trying to do rather than creating a bunch of for loops.

Two Separate Functions for Filtering?

Maybe having a function that checks against a hardcoded string doesn't sit well with you. Admittedly, heat(player) and spurs(player) do seem like throw away functions.

Is there a way that we can replace both of those functions with one?


function inTeam(teamName) {
  return function(player) { return player.team === teamName; }
}
console.log(average(players.filter(inTeam('Heat')).map(shootingPercentage)).toFixed(2));
console.log(average(players.filter(inTeam('Spurs')).map(shootingPercentage)).toFixed(2));

GINOBILI!!!

A quick side note: that guy Ginobili is an Argentinian basketball player that once hit a flying bat out of the air during a game.

ginobili

(BTW, I don't condone hurting animals, but I do love animated gifs a lot)

A Quick Summary of the Methods we Just Saw

  • forEach - calls callback on each array element
  • filter - returns a new filtered array based on test/callback
  • map - returns a new transformed array based on callback
  • reduce - reduces original array to single value based on callback

Performance

We're gaining readability, expressiveness, etc. … at the cost of performance

We make that trade-off all the time, which is why we're not writing with super-fast machine code, but with slower high-level languages, like JavaScript or Python.

  • a for loop will likely outperform forEach, map, filter and reduce
  • some of it may stem from the overhead of repeatedly executing functions
  • but… for general cases, modern-day computers are fast enough where it's not really a perceptible difference (depending on the data set and number of iterations, of course)

An Aside on Arrow Function Usage

For now, we'll use arrow functions as:

  • a quick way of creating anonymous callback functions…
  • for example, if we need to pass a one-time use function as an argument to a higher order function (like map):
    const nums = [1, 2, 3, 4, 5];
    console.log(nums.filter(x => x % 2 === 0));
    
  • or… occasionally, we can use them to define regular functions as well:
    
    const isEven = (x) => {return x % 2 === 0;};
    
  • we'll see later that arrow functions are sometimes useful because the this value within its body is the same as this in the scope that it was created in (this will make more sense when we discuss this!)

Functions as Objects

Continuing on with the notion that functions are just values or objects… do you think functions also have properties and methods?

Why yes - functions have both properties and methods! For example:


const f = function(x, y, z) {
	return x - y + z;
}
console.log(f.length);

Let's check out some methods that you can call on function objects:

  • bind
  • apply
  • call

Methods on Function Objects

So, what do these methods do?

  • call - calls a function with given this and individual arguments
  • apply - calls a function with given this and array as arguments
  • bind - creates a new function with given this, and optionally with set argument values


(we'll talk about this in a moment)

Call and Apply

Both call and apply immediately execute the function that they're called on.

  • they differ in the way that arguments are passed to the original function object (call passes each argument individually, while apply passes an array)
  • the example below sets this to null - we'll see more about this later

function areaTriangle(base, height) {
	return (1 / 2 ) * base * height;
}

const result1 = areaTriangle.call(null, 10, 20);
console.log(result1);
const result2 = areaTriangle.apply(null, [10, 20]);
console.log(result2);

Bind

Unlike the previous methods, bind doesn't execute the function that it's called on. Instead, bind:

  • takes a this argument
  • and an optional set of fixed parameters
  • returns a new function


It's actually an implementation of partial application

  • partial application - fixing a number of arguments to a function, producing another function of smaller arity
  • arity - the number of arguments or operands that a function or operation accepts

Bind Example

Fixing the first parameter, called base, of our function.


const areaTriangleBase100 = areaTriangle.bind(null, 100);

// call with only one argument now
console.log(areaTriangleBase100(3));


Note that you'll also see bind used to fix/set a function or method's this.

ES6 Spread and Rest

Hey… so remember the rest operator, ...args? What was it?

  • if the last named argument of a function has ... before it (the rest operator)
  • then the arguments passed in at that position are condensed into a single Array
  • for example:
    
    function f(a, b, ...args) { console.log(args); }
    f('foo', 'bar', 'baz', 'qux'); // prints out ['baz', 'qux']
    
  • notice that every value after and including the 3rd argument are collected into an Array
  • again, this allows for an arbitrary number of trailing arguments to be passed in to a function
  • (this is called a variadic function, a function that can have and indefinite number of arguments / arity!)

ES6 Spread and Rest Continued

An operator that shares the same syntax but does the opposite of the rest operator is the spread operator.

  • the spread operator takes an Array and breaks it up into parts!
  • this can be used in function calls: f(...someArray)
  • as well as in Array literals: [1, 2, ...someArray]

Spread Operator in Function Calls

The parameters for res are value and radix… and in this case, we're able to expand the incoming Array to fit the arguments by using the spread operator:


const stuff = ['101', 2];
const res = parseInt(...stuff);
console.log(res);
  • the first element of stuff becomes the first argument to parseInt, the second becomes the last argument
  • if there are too few or too many elements in the Array, JavaScript will behave as if there were too few or too many arguments
  • which is to say… fine - excess arguments are ignored and arguments not given a value are undefined…

Spread Operator in Function Calls Continued

What does the following code print out?


const words = ['foo', 'bar'];
function logThreeThings(a, b, c) {
    console.log(a, b, c); 
}
logThreeThings(...words); 

foo bar undefined

Spread Operator in Array Literals

The spread operator can also be used to expand Arrays within Array literals:


const words = ['foo', 'bar', 'baz'];
const moreWords = ['start', ...words, 'end']
console.log(moreWords);
// [ 'start', 'foo', 'bar', 'baz', 'end']

You can also use the spread operator to make a shallow copy of an Array:


const arrs = [[1, 2], [3, 4]];
const newArrs = [...arrs];
console.log(newArrs); // yay copied!

arrs[0].push('surprise!');
console.log(newArrs); // beware, shallow!

Now Back to Higher Order Functions; We Can Make Our Own!

(As we saw with our own implementations of some array methods)

  • create functions that return entirely new functions (we've done this)
  • create functions that wrap or decorate other functions

Let's Try Creating a Wrapping Function

How about creating a function that logs how much time a function takes to execute?

  • create a function, logExecutionTime, that takes a function, f, as an argument
  • it returns a new version of f that logs how much time it took to execute (in addition to doing whatever it usually does as a function, of course!)
  • we can use console.time and console.timeEnd to take the timings

Function Timing Implementation

Here's one possible way to do it.

  • create a function that just calls the function passed into it
  • and gives back whatever that function returns
  • the only extra thing that it will do is take the time before and after the function call

function logExecutionTime(f) {
	return function(arg) {
		console.time('function timing');
		const val = f(arg);
		console.timeEnd('function timing');
		return val; 
	};
}

function wasteTime(limit) { for(let i=0;i < limit; i++) { }}
wasteTime = logExecutionTime(wasteTime);
wasteTime(5000000);

Another Look at Function Timing

Hm. So… there's a limitation with regards to the kinds of functions that we can time. Can you spot it?


Hint: How would it handle the following function?


function wasteTime2(start, limit) { 
	for(let i = start; i < limit; i++) { } 
}


What if the timed function needs more than one argument?


function logExecutionTime(f) {
	return function(...args) {
		console.time('function timing');
        // use spread and rest
        val = f(...args);

        // old way with es5, with apply
		// const val = f.apply(null, arguments);
		console.timeEnd('function timing');
		return val; 
	};
}
wasteTime = logExecutionTime(wasteTime);
wasteTime(-5000000, 5000000);

Decorators

A function that accepts another function as a parameter, wraps its call in another function, and returns that wrapper as a new function… is called a function decorator.

Why might function decorators be a useful? When would it make sense to implement a decorator rather than to modify an actual function?

  • they allow us to modify how a function runs, even one that we did not write!
  • decorators can be reused! for example, you can make any function a timed function, but should you need to change the implementation of timing, you only change it in one place
  • there's potential for combining / chaining decoratiors!

Practical Applications

Decorators might come in handy whenever you want to do something before or after a function runs.

  • as we saw, function timing makes a lot of sense
  • perhaps caching
  • permissions checking (before a function runs)

Review

We just saw a bunch of higher order functions in JavaScript… and we even created our own.

  • Array Methods
    • forEach
    • map
    • filter
    • reduce
  • Function Methods
    • call
    • apply
    • bind