Higher Order Functions Continued (array methods, function methods)

Higher Order Functions

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

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

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

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


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, ...argsWhat 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!
 

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

HintHow 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
    const 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 decorators!
 
 
posted @ 2023-02-02 11:45  M1stF0rest  阅读(15)  评论(0编辑  收藏  举报