Chapter 02 – First-Class, Higher-Order, and Lambda Functions

Chapter 02 - First-Class, Higher-Order, and Lambda Functions

Introduction

This chapter introduces first-class, higher-order, and lambda functions. In a few ways, these types of functions are all somewhat related and they are often used together sometimes interchangeably. All of these function types are derived from functional languages like Haskell and it’s quite nice to be able to use these in a more common language like JS. However, JS is far more flexible of a language and with most flexible languages, that leaves room for sloppy code. 

First-Class Functions

First-class functions are pretty easy to grasp:

A programming language is said to have First-class functions when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.

https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function

This means that functions in JS can be:

  • Stored in a variable.
  • Passed into and out of functions.
  • Used as property values in objects and arrays.

For instance, you can define a function in JS a few ways. As a function expression, the function is just assigned to a variable:

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

add(5, 5); // 10 

A function expression creates an anonymous function even though that function is assigned to a variable and can be referenced as such.

 And of course, there is the more traditional function declaration:

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

add(5, 5); // 10 

The difference here may be subtle but one of the most important differences, especially if you are coding functionally, is that function declarations are hoisted to the top of your code before execution whereas function expressions are only loaded when the interpreter reaches them. Technically, the variable declaration is hoisted but the initialization of the variable to the function is not, as that happens in place. This also gives function expressions the interesting side effect of maintaining the scope where they are defined.

Regardless of the way you define the function, it is still treated like a first-class citizen in JS.

let runFunc = function(fn, a, b) {
  return fn(a, b);
}

runFunc(add, 5, 5); // 10 

Higher-Order Functions

Higher-order functions (HOFs) sound kind of complicated, like there’s some mysterious higher power they have. In a way they…they kind of do but it’s not quite that mysterious. A higher-order function is simply a function that calls another function. That’s it. 

evan almighty

In our example above, we had the “runFunc()” call the passed-in “add()” function so we would say that “runFunc()” is a higher-order function as its “higher purpose” is to call the function that does the work. In JS (as with other languages like Haskell), some more popular examples of HOFs can be found in the “Array.prototype” object and include “map()”, “filter()”, “every()”, and a bunch more! Let’s take a few of these:

const vec1 = [1, 2, 3, 4];
const vec2_squared = vec1.map(square);

function square(n) {
    return n*n;
}

// vec2_squared -> [1, 4, 9, 16] 
const allResults = [65, 33, 97, 40, 1, 77];
const belowFifty = allResults.filter(lowerThan50);

function lowerThan50(n) {
  return n < 50;
}

// belowFifty -> [33, 40, 1] 

The passed in function that a higher-order might call is typically called a “callback” in the JS world. This is a very common pattern in the olden days before async (actually, it still is pretty common). Since JS is an asynchronous language, certain requests that might take an arbitrary amount of time to complete (web requests, user input, etc.) can’t block the execution of the rest of the JS program (imagine locking a user’s browser waiting for an AJAX request that times out after 10s). Thus, functions like these can be written to accept a function in their parameters that is to be executed after the asynchronous request completes. A typical example is the controller actions in a NodeJS API:

// The traditional way to use JS callbacks.
module.exports = {
  // Find a record and return it.
  find(req, res){
    // Grab the ID from parameters
    const id = req.param('id');
    
    // Do a DB query to find the record and pass in a callback lambda function as the 2nd param to execute when done.
    DB.findOne(id, (err, record)=>{
      // Typically, the first parameter of a callback is an error.
      if(err){
          return res.serverError(err);
      }
      
      // Return the found record.
      return res.json(record);
    });
  }
} 

However, with ES6 and the inclusion of async in core JS, we can make things function more like a synchronous program:

// The new way...no callbacks
module.exports = {
  // Find a record and return it.
  async find(req, res){
    // Grab the ID from parameters
    const id = req.param('id');
    
    // Do an async DB query to find the record.
    const record = await DB.findOne(id).catch(res.serverError);
    
    // Return the found record.
    return res.json(record);
  }
} 

Higher-order functions don’t seem all that special but keep in mind that they are just one piece of the functional programming puzzle. They make a pretty heavy appearance in the following sections and chapters and are key to other fancier operations.

Lambda Functions

Well, now we’re getting serious. Lambda sounds Greek…this must be complicated. Nope, not even close. A lambda function is simply an anonymous function expression and anonymous in this case is exactly what it sounds like: it has no name. Lambda functions are meant to be somewhat ephemeral and they are typically used as shorthand for function expressions above. Additionally, they are generally used directly as parameters passed into HOFs. 

For instance, if we re-write the HOF examples above using lambda functions, we get a much cleaner syntax:

const vec1 = [1, 2, 3, 4];
const vec2_squared = vec1.map(n => n*n);

// vec2_squared = [1, 4, 9, 16] 
const allResults = [65, 33, 97, 40, 1, 77];
const belowFifty = allResults.filter(n => n<50);

// belowFifty = [33, 40, 1] 

As you can see, lambda functions in JS are written using the “=>” or fat arrow symbol. These examples show only one parameter but they can of course accept multiple parameters by using ( ):

const results = [1, 2, 3, 4, 5];
const addResult = results.reduce((n, t) => { return n + t}, 0);

// addResult -> 15 

Now, in the context of JS, we have to remember that lambda functions don’t behave like normal functions concerning their bindings. 

1. They do not bind a “this” and they use the enclosing lexical scope of “this” which means that accessing “this” within a lambda function gets you the value of “this” of the function scope enclosing the lambda function. 

2. Because they are anonymous, they can also be harder to debug while checking a stack trace as there will be no function name present. 

3. Finally, you should not use them as object methods:

let cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
} 

Since lambda functions don’t bind to “this” the lives decrement operator won’t work. This also applies to instances where you need your function to have a dynamic context…inside of an event handler for instance.

Conclusion

This was a pretty short and sweet chapter as the topics covered were fairly straight forward. Of course, each function type is a bit more nuanced than we covered here and their applications are quite varied but in the context of  laying the functional programming groundwork, what we covered should suffice! The key takeaways are there are multiple ways to write functions in JS and each has its own quirks about how they function and where and when they should be used.