Zachary W. Huang

Home Projects Blog Guides Resume

Functions are all you need*

October 15, 2021

Imagine if Javascript did not have objects. What if I told you that Javascript would be no less powerful? No class, no array, no conventional data structures at all. Other than functions.

Wait, functions?

Yes. And the reason is: closures, lexical scoping, and first-class functions.

Sidenote: functions are actually represented as Objects in both JS and Python, but that’s not relevant to the ideas presented in this post.

Take this example:

function f() {
    let x = 2;
    return x;
}

Nothing exciting, right? It’s just a function called f that returns 2 when called.

But what if we add another layer?

function f() { // outer function
    let x = 2;
    return () => { // inner function
        return x;
    }
}

Now, f is a function which returns another function that will return 2 when called.

But consider the scopes in which x is visible. x is defined within the scope of the inner function returned by f. Outside of f, however, the value of x is not visible (hmmm… encapsulation?). How can we stretch this to its limits?

Consider the following data structure:

let pair = make_pair(42, "asdf");
console.log(first(pair)); // 42
console.log(second(pair)); // "asdf"

Below is a trivial implementation using object.

function make_pair(a, b) {
    return { first: a, second: b };
}
function first(pair) {
    return pair.first;
}
function second(pair) {
    return pair.second;
}

But can these functions be implemented without the help of objects? The answer is yes, with a bit of cleverness.

function make_pair(a, b) {
    return (flag) => {
        return flag ? a : b;
    }
}
function first(pair) {
    return pair(true);
}
function second(pair) {
    return pair(false);
}

The idea is that we can actually store data in the environment available to make_pair, and we can access these values from within an internal function. You can imagine that the inner function has some sort of link to the environment of the outer function. The arguments a and b are defined when make_pair is called, and the inner function chooses which to return based on a boolean flag.

By calling the function make_pair multiple times, we are, in effect, creating multiple instances of a pair.

let x = make_pair("Hello", 456);
let y = make_pair(123, "world!");

console.log(first(x), second(y)); // Hello world!
console.log(first(y), second(x)); // 123 456

We can even put pairs inside of pairs, effectively creating a linked list in which each ‘second’ field holds the rest of the list.

let list = make_pair(1, make_pair(2, make_pair(3, null))); // [1, 2, 3]
console.log(first(list)); // 1
console.log(first(second(list))); // 2
console.log(first(second(second(list)))); // 3

We can implement object-oriented programming with mutable state using this function-based technique. We will use our own pair datastructure to hold function arguments.

function make_vector(x, y) {
    return (args) => {
        let flag = first(args);
        if (flag === "x") {
            return x;
        } else if (flag === "y") {
            return y;
        } else if (flag === "setX") {
            x = second(args);
        } else if (flag === "setY") {
            y = second(args);
        } else {
            console.log(flag, "has not been implemented.");
        }
    }
}

// A helper function to work with our objects
// This only supports one argument, but we can easily extend this using our pair-based linked lists
function call(obj, method, arg) {
    obj(make_pair(method, arg))
}

This lets us do the following:

let v = make_vector(4, 2);
console.log(call(v, "x")); // 4
console.log(call(v, "y")); // 2

call(v, "setX", 10);
call(v, "setY", 11);
console.log(call(v, "x")); // 10
console.log(call(v, "y")); // 11

Now, this code isn’t perfect - we can’t reference any semblance of this (its implementation is left as an exercise to the reader), and performance is likely not great at all - but it should demonstrate the power of functions in modern scripting languages. The basic concepts shown in this post are key to the programming language Lisp, which is known to be extremely expressive. If you’re interested, see the further reading below.

I hope you enjoyed this article!

References

  • Structure and Interpretation of Computer Programs
  • MDN Web Docs - Closures
RSS icon github logo linkedin logo

Zachary W. Huang © 2021-2024