Mastering JavaScript Functional Programming
上QQ阅读APP看书,第一时间看更新

Of lambdas and functions

In lambda calculus terms, a function can look like λx.2*x. The understanding is that the variable after the λ character is the parameter for the function, and the expression after the dot is where you would replace whatever value is passed as an argument.

If you sometimes wonder about the difference between arguments and parameters, a mnemonic with some alliteration may help: Parameters are Potential, Arguments are Actual. Parameters are placeholders for potential values that will be passed, and arguments are the actual values passed to the function.

Applying a function means that you provide an actual argument to it, and that is written in the usual way, by using parentheses. For example,  (λx.2*x)(3) would be calculated as 6. What's the equivalent of these lambda functions in JS? That's an interesting question! There are several ways of defining functions, and not all have the same meaning.

A good article showing the many ways of defining functions, methods, and more, is The Many Faces of Functions in JavaScript, by Leo Balter and Rick Waldron, at https://bocoup.com/blog/the-many-faces-of-functions-in-javascript--give it a look!

In how many ways can you define a function in JS? The answer is, probably in more ways than you thought! At the very least, you could write:

  • a named function declaration: function first(...) {...};
  • an anonymous function expression: var second = function(...) {...};
  • a named function expression: var third = function someName(...) {...};
  • an immediately-invoked expression: var fourth = (function() { ...; return function(...) {...}; })(); 
  • a function constructor: var fifth = new Function(...);
  • an arrow function: var sixth = (...) => {...};

And, if you wanted, you could add object method declarations, since they actually imply functions as well, but that's enough.

JS also allows defining generator functions as in function*(...) {...} that actually return a Generator object and async functions that really are a mix of generators and promises. We won't be using these kinds of functions, but read more about them at https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/function* and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function--they can be useful in other contexts.

What's the difference between all these ways of defining functions, and why should we care? Let's go over them, one by one:

  • The first definition, a standalone declaration starting with the function keyword, is probably the most used in JS and defines a function named first (that is, first.name=="first"). Due to hoisting, this function will be accessible everywhere in the scope where it's defined.

Read more about hoisting at https://developer.mozilla.org/en-US/docs/Glossary/Hoisting and keep in mind that it applies only to declarations, but not to initializations.

  • The second definition, assigning a function to a variable, also produces a function, but an anonymous (that is, no name) one. However, many JS engines are capable of deducing what the name should be, and set second.name=="second" (check the following code, which shows a case where the anonymous function gets no name assigned). Since the assignment isn't hoisted, the function will only be accessible after the assignment has been executed. Also, you'd probably prefer defining the variable with const rather than var because you wouldn't (shouldn't) be changing the function:
var second = function() {};
console.log(second.name);
// "second"

var myArray = new Array(3);
myArray[1] = function() {};
console.log(myArray[1].name);
// ""
  • The third definition is the same as the second, except that the function now has its own name: third.name === "someName".

The name of a function is relevant when you want to call it, and also if you plan to do recursive calls; we'll come back to this in Chapter 9, Designing Functions - Recursion. If you just want a function for, say, a callback, you can do without a name. However, note that named functions are more easily recognized in an error traceback.

  • The fourth definition, with an immediately-invoked expression, lets you use a closure. An inner function can use variables or other functions, defined in its outer function, in a totally private, encapsulated, way. Going back to the counter making function that we saw in the Closures section of Chapter 1, Becoming Functional - Several Questions, we could write something like the following:
var myCounter = (function(initialValue = 0) {
let count = initialValue;
return function() {
count++;
return count;
};
})(77);

myCounter(); // 78
myCounter(); // 79
myCounter(); // 80

Study the code carefully: the outer function receives an argument (77, in this case) that is used as count's initial value (if no initial value is provided, we start at zero). The inner function can access count (because of the closure), but the variable cannot be accessed anywhere else. In all aspects, the returned function is a common function; the only difference is its access to private elements. This is also the base of the module pattern.

  • The fifth definition isn't safe, and you shouldn't use it! You pass the arguments names, and then the actual function body as a string in the last argument -- and the equivalent of eval() is used to create the function, which could allow for many dangerous hacks, so don't do this! Just to whet your curiosity, let's see an example, rewriting the very simple  sum3() function we saw back in the Spread  section of Chapter 1, Becoming Functional - Several Questions:
var sum3 = new Function("x", "y", "z", "var t = x+y+z; return t;");
sum3(4, 6, 7); // 17

This sort of definition is not only unsafe, but has some other quirks, such as not creating closures with their creation contexts, and always being global instead. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function for more on this, but remember that using this way of creating functions isn't a good idea!

  • Finally, the last definition using an arrow => definition is the most compact way to define a function and the one we'll try to use whenever possible. We'll get into more detail in the next section.