Functional Objects in JavaScript

Emil Broman
5 min readMar 31, 2017

--

The Functional and Object Oriented paradigms are often considered to be incompatible. “Sure, you can use higher order functions in JavaScript, but if you’re using classes, you’re not writing functional code”. I beg to differ.

Functional Programming is all about immutability. That means that data structures can never change. One characteristic of Objects in the OOP sense of the word is that each object holds its own internal mutative state, so instead of using objects you should just use functions, right? Well, not so fast. First, let’s just try to implement a stack without changing any data.

class Stack {
constructor () {
this._array = []
}
}

Wait a second! Is that an assignment? We’re mutating the object and so we’re not being immutable, right?

The only place where assignment is OK is within a constructor, because you’re initializing the object. Saying constructor assignments are not okay is like saying that let i = 123 is not okay.

We now have an object with an internal state, but we haven’t manipulated the data in any way yet. Let’s add a method.

class Stack {
constructor () {
this._array = []
}
get length () {
return this._array.length
}

}

No problem, right? We have a simple getter function that doesn’t change any data, it simply returns a value. However, how can we implement the mutative push method? Let’s look at the mutative example first.

class Stack {
constructor () {
this._array = []
}
get length () {
return this._array.length
}
push (value) {
this._array.push(value)
}

}

Here, we’re changing the internal variable, breaking immutability. The tell-tale sign is that we don’t have a return statement in the function. If the function doesn’t return anything, it must have a side effect or do nothing at all.

The first step in solving the issue is a simple one: moving the internal array to a constructor parameter:

class Stack {
constructor (array) {
this._array = array
}
get length () {
return this._array.length
}
push (value) {
???
}
}

Now, the push method can use its internal array, create a new array with the value appended, create a new Stack with the array, and return the result:

class Stack {
constructor (array) {
this._array = array
}
get length () {
return this._array.length
}
push (value) {
return new Stack(
[...this._array, value]
)
}

}

Look at that! No more mutation. A consumer of the class will use it like this:

const stack = new Stack([])const stackWithItem = stack.push(1)

Next, let’s implement pop:

class Stack {
constructor (array) {
this._array = array
}
get length () {
return this._array.length
}
push (value) {
return new Stack(
[...this._array, value]
)
}
pop () {
return new Stack(
this._array.slice(0, this._array.length - 1)
)
}

}

Okay, we now have a method which returns a new Stack with one less item. But pop is supposed to return the value that is being removed!

In situations like this, functional programming has the notion of tuples. A tuple is a simple list-like data structure with a fixed length, the values of which can have different types. By returning a tuple, we can return multiple values from the same function. Let’s try it for our pop.

class Stack {
constructor (array) {
this._array = array
}
get length () {
return this._array.length
}
push (value) {
return new Stack(
[...this._array, value]
)
}
pop () {
const item = this._array[this._array.length - 1]
const stack = new Stack(
this._array.slice(0, this._array.length - 1)
)
return [stack, item]
}
}

The consumer can then use the method like so:

const stack = new Stack([])const stackWithOne = stack.push(1)const [stackWithoutOne, one] = stackWithOne.pop()

Another solution is to simply force the consumer to fetch the top item in the stack beforehand:

class Stack {
constructor (array) {
this._array = array
}
get length () {
return this._array.length
}
push (value) {
return new Stack(
[...this._array, value]
)
}
get top () {
return this._array[this._array.length - 1]
}
pop () {
return new Stack(
this._array.slice(0, this._array.length - 1)
)

}
}

And then it’s up to the consumer:

const stack = new Stack([])const stackWithOne = stack.push(1)const stackWithoutOne = stackWithOne.pop()
const one = stackWithOne.top

Notice in the example above, that it doesn’t matter that we access the top object after we have popped the stack, because the original stack is never changed, so the value is still there!

Finally, I like to make a static getter that initializes the empty object just like the original constructor did:

class Stack {
constructor (array) {
this._array = array
}
static get empty () {
return new Stack([])
}
get length () {
return this._array.length
}
push (value) {
return new Stack(
[...this._array, value]
)
}
get top () {
return this._array[this._array.length - 1]
}
pop () {
return new Stack(
this._array.slice(0, this._array.length - 1)
)
}
}

Ultimately, I think we’ve achieved a pretty nice API:

return Stack.empty
.push(1)
.push(2)
.pop()
.push(3)
.push(4)
.pop()
// Stack { _array: [1, 3] }

This can be done with any object, and if you force yourself to adhere to the principle of immutability, you end up with a system that is well defined, stable, and that’s easy to debug. You rarely need a debugger in a stateless program.

The implications of objects like this sort of propagate. For example, how would you loop over an array and push each item to the stack?

const stack = Stack.emptyfor (const item of [1, 2, 3]) {
stack.push(item)
}

This won’t work, because the original Stack is never changed. So we’ll have to keep track of the object:

const stack = Stack.emptyfor (const item [1, 2, 3]) {
stack = stack.push(item) // Error!
}

We can’t do this, because we’ll have to change the variable into a mutable let which is against our immutable rules.

One way to solve it is through recursion:

const source = [1, 2, 3]
const target = Stack.empty
const result = (function f (stack, index) {
if (index >= target.length) {
return stack
}
const item = source[index]
const newStack = stack.push(item)
return f(newStack, index + 1)
})(target, 0)

However, this gets rather muddy, and probably not very performant in the long run without tail call optimization yet implemented in most JavaScript runtimes.

So the nicest way to solve it in my opinion, is using the reduceRight method:

const source = [1, 2, 3]
const target = Stack.empty
const result = source.reduceRight(
(stack, item) => stack.push(item),
target
)

If you’re unfamiliar, this will work pretty much exactly like the recursive version, but in a very optimized fashion, since this is a built-in method.

The second argument (target) will get passed into the function, together with the first item in the array. The return value (i.e. a Stack with [1]) will be passed back into the function along with the second item, yielding a Stack of [1, 2] and so forth. No mutation.

This mindset of functional and object oriented paradigms combined, is very prominent in my UI library TweedJS. Check it out, why don’tcha!

--

--