DEV Community

Cover image for A function by any other name would work as well (part one)
Tracy Gilmore
Tracy Gilmore

Posted on • Edited on

A function by any other name would work as well (part one)

Not quite the words of Shakespeare but better than my butchery of the words of Samuel Taylor Coleridge “Functions, functions everywhere, nor any call to async”. Ouch, sorry

In this two-part article I will be exploring the extensive world of functions. Many of the concepts discussed can be found in other languages but we will focus specifically on functions as stipulated by JavaScript, or ECMAScript ECMA-262 to be more exact. We will not be going deeply 'under the hood' but we will be exploring functions in as many forms as I can recall.

Overview

Many programming languages possess a construct to provide the basic facility of functions but JS functions go beyond what many programming languages offer.

The primary use cases for functions are:

  • Functions provide a way to break the solution (program) down into small pieces in a similar way to how we mentally break down the problem.
  • Functions also provide a way of capturing reusable functionality and therefore reduces code duplication. Before ES Modules can about, functions were a key component for sharing code in a controlled manner and an enabler for code abstraction.

Some history

Functions of course have their origins in mathematics but in imperative programming languages they were first seen in procedural languages such as (structured) BASIC, Pascal and C. In fact Pascal provides distinct keywords (procedure and function) for the purposes described above.

  • Procedures can be regarded as “sub-programs” as they provide a container for logically grouped instructions but do not return a value.
  • Functions on the other hand, provide a way to extend the programming language by creating new operations that return a value. They are intended to be more generic than procedures.

Before functions and procedures came about the nearest equivalent was the sub-routine as provided by the GOSUB command in structured BASIC. However, these used a line number instead of a meaningful name to reference the start of the code block. Subroutinrd also needed another keyword (RETURN) to provide an explicit exit and a way of restoring execution to the line after the line from where the sub-routine was called.

Programming languages prior to the "Procedural" group were largely without structure (such as assembly) and programs all too easily became convoluted and unmaintainable, and often acquired the term “spaghetti code” to describe the resultant mess.

To commence our investigation we will consider the anatomy of a “traditional” JS function and learn what makes JS different from other languages in regards to functions.

Anatomy of a function

As we shall see, there are many ways to create a function but the one most common amongst programming languages uses a keyword like “function”, “func”, “def”, etc. In order for a program to reuse the function code, we usually have to give it a name, but in JS we can create ‘anonymous’ functions (as we shall see).

// A traditional function declaration
function functionName( parameterOne, parameterN) {
    // Zero or more statements
    // Zero or more return statements[1]
}

// Calling
functionName(argumentOne, argumentN);
Enter fullscreen mode Exit fullscreen mode

After the optional function name we use parentheses to enclose a list of parameters, which are variables that can be supplied when the function is called. However, the values provided at the time of calling are usually called arguments. It is possible (especially in JS) to call the function with more or fewer arguments that parameters, which can lead to errors but JS is excessively forgiving in this regard. In JS as with many languages the alignment of arguments to parameters is by position but some languages employ a different strategy where the parameter names have to be provided with the value when the function is called.

The curly braces {} define the statement block for the function along with the scope of internal variables and functions, as will be described later.

[1] Coding standards often stipulate there should be no more than one return statement in a function but this can make the logic of some functions less readable than needs be. The ‘return’ statement signifies an exit point of a function similar to the Pascal return statement described above. However, in JS, unlike Pascal, a function does not have to formally return a value and if it does not; or the end of the function block is reached before ‘return’ is encountered, or 'return' is used without a value, the default return value from the function is ‘undefined’.

function sayHi() {
  console.log('Hello, World!');
}

sayHi();
// Output: "Hello, World!"
// Return value: undefined
Enter fullscreen mode Exit fullscreen mode

Wait, back-up, if a function has no name how can it be used (called)?

Unlike some languages, JS functions are ‘first-class’ objects and can be assigned as the value of a variable (using a function expression). They can be passed as arguments to another function, returned from a function and even created inside a function. Functions that expect a function as a parameter or returns a function have a special name "high-order" functions, which we will explore further later.

const varFn = function(param) { return param * 6; };

varFn(7); // 42;
Enter fullscreen mode Exit fullscreen mode

In the example above the function is created as an anonymous (it has no name) function expression (that returns a value) that is assigned to the variable varFn. In this form the function can be executed/called using varFn(); call and can be reassigned as follows.

const varFn2 = varFn;

varFn2(); // 42;
Enter fullscreen mode Exit fullscreen mode

Another way to execute anonymous functions is known as IIFE (Immediately Invoked Function Expression). IIFEs are used as a form of single-use procedures and are a rather niche use case these days following the introduction of ES (ECMAScript) modules.

(
function iife(parameter) {
    // statements
    // no return statement (most often)
}
)(argument);
Enter fullscreen mode Exit fullscreen mode

The above function 'iife' is being declared and executed at (nearly) the same time. Lines 2-4 define the function and its statements. The parens (parentheses) around the declaration define the function as an expression (value). The following parens including arguments result in execution of the function expression immediately. Even if you do not think you need to give a function a name it is often helpful when debugging issues as it makes each function more obvious in the call stack.

"Traditional" vs "Arrow" functions

So far I have used the term “traditional” to describe the function construct that has existed in JS from its inception and follows a pattern consistent with many other languages. Since ECMAScipt 6 (2015) another form of function has been available known as the ‘arrow’ functions (or ‘fat arrow’ because of the syntax used =>).

const arrow = a => a > 0;

arrow(-1); // false: a < 0;
arrow(0); // false: a = 0;
arrow(1); // true: a > 0;
Enter fullscreen mode Exit fullscreen mode

The origins of the Arrow function can be traced back to mathematics via the Functional Programming languages such as Lisp, where they also have the name “lambda functions/expressions”. The format/syntax will be quite familiar to those who use spreadsheets like MS Excel when calculating a formula.

Although related, this should not be confused with AWS Lambda functions. They have been demonstrated to be so valuable to programming they can now be found in many popular languages.

;; Lisp
(lambda (a) (> a 0))

// C++
[](int a) { return a > 0; }

# Python
lambda a: a > 0

// C#
a => a > 0

// Java
a -> a > 0
Enter fullscreen mode Exit fullscreen mode

Scope and Closure

As stated above, within the braces of a function declaration we define the statements to be performed when the function is called. The statements have visibility of all the variables and functions defined outside the function (including those in the global scope) although position is important. Attempting to use some variables (those declared using let or const) can result in error as they cannot be used before they are declared (unlike vars). During execution variables declared within the 'scope' of the function are private and generally not visible outside the function.

const outerVariable = 6;

function mult() {
  const innerVariable = 7;

  console.log(outerVariable * innerVariable);
}

mult(); // 42 is shown in the console.

console.log(outerVariable * innerVariable);
// Error: innerVariable is not defined.
Enter fullscreen mode Exit fullscreen mode

Once complete the scope of most functions (in JS there are exceptions) is destroyed and lost.

Closures

For a formal definition of Closures please consult MDN, but here is a less formal definition. In JS a closure is formed when the internal scope of a function is preserved beyond its execution by returning an internal reference, usually in form of another function. For example:

function createClosure() {
  let closureState = 1;

  return function(stateChange) {
    closureState = stateChange(closureState);
    console.log(`New state: ${closureState}`);
  };
}

const closure = createClosure();

closure(s => s + 13);  // Console output `New state: 14`
closure(s => s * 3);  // Console output `New state: 42`
Enter fullscreen mode Exit fullscreen mode

Notice how createClosure is called just once and holds the value closureState, which is not exposed directly. However, the function that is returned from the call, which is assigned to the constant closure, is called twice. Each call is made with a different argument (callback function) that is used to update the state (variable) inside the closure before it is logged to the console.

According to your point of view, it is either the execution of createClosure or its subsequent returning of the anonymous function that forms the closure.

Not quite 57 varieties

Heinz 57 Varieties logo

JS supports a wide variety of functions types, the basic forms we have covered above. Before going really exotic let's investigate some others:

Methods

Methods are functions associated with and/or operate in the context of an object. The specific instance of an object can be referenced within a method using the this keyword, which is a little more dynamic in JS than other OOP languages and can be the source of some confusion.

const instance = {
  message: 'Hello, World!',
  method() {
    console.log(this.message);
  }
};

instance.method();
// Output: 'Hello, World!'
Enter fullscreen mode Exit fullscreen mode

As a simple "rule of thumb" for finding the value of this, locate when the function was called. For a method this will be of the form object.method(), so this is the object to the left of the dot.

When classes are used there is another form of method called a class/static method, as opposed to an object/instance method.

class ExampleClass {
  static staticMethod() {
    return 'Hello, World!';
  }
}

console.log(ExampleClass.staticMethod()); // 'Hello, World!'
Enter fullscreen mode Exit fullscreen mode

Static methods do not need to be called off an instantiated object but need to reference the class itself.

Getters and Setters (aka accessor methods)

There are also some special instance methods to control access to private (now we have the # syntax to support them) properties.

class ExampleClass {
  #privateArray = ['a', 'b', 'c'];

  get latest() {
    return this.#privateArray.at(-1);
  }

  set latest(last) {
    this.#privateArray.push(last);
  }

  arrayContent() {
    return this.#privateArray.join();
  }
}

const exampleInstance = new ExampleClass();
console.log(exampleInstance.arrayContent()); // Output: "a,b,c"
console.log(exampleInstance.latest); // Output: "c"

exampleInstance.latest = 'd';
console.log(exampleInstance.arrayContent()); // Output: "a,b,c,d"
console.log(exampleInstance.latest); // Output: "d"
Enter fullscreen mode Exit fullscreen mode

Finally on the topic of classes, there is another type of function called a constructor. If defined the constructor is always called when a new object is created (instantiated).

class ExampleClass {
  constructor() {
    console.log('Hello, World!');
  }
}

const newInstance = new ExampleClass(); // 'Hello, World!'
Enter fullscreen mode Exit fullscreen mode

Wrapping up part one

In this first article we discovered the origins of functions in imperative programming languages. We decomposed the structure and investigated the roles of functions before exploring some of the varieties used/provided in JS.

In part-two we will continue by discovering how JS supports asynchronous operations and enables a functional style of programming. We will also briefly investigate some of the newer, more exotic forms of function in JS.

Top comments (0)