[006.1] Introducing Mori.js

Why and how to use Mori.js in your JavaScript code

Subscribe now

Introducing Mori.js [12.27.2017]

Core Functional Programming with Javascript

Intro

Welcome back to our tutorial on functional programming in Javascript with Mori.js. Yesterday, we covered the fundamentals of functional programming itself including how to think functionally. Today, we're we're going to focus on a less talked about topic in functional programming in the Javascript world and that is Immutable Data Structures.

So to understand Immutable Data Structures, it's key to know what kind of problem they solve in the first place. To do this, let's take a look at a quick example of how value changes are handled in the iterative manner versus what we have been doing in our functional manner.

// iterative value change
let x = [1, 2, 3, 4, 5]
console.log(x) // --> [1, 2, 3, 4, 5]
x.push(6)
console.log(x) // --> [1, 2, 3, 4, 5, 6]

// functional value change
let y = [1, 2, 3, 4, 5]
console.log(y) // [1, 2, 3, 4, 5]
let z = y.push(6) 
console.log(z) // [1, 2, 3, 4, 5, 6]

Notice any particular inefficiencies with the functional version? As you may have noticed, the functional version is going to be drastically heavier on your system. Why? You're making a literal full copy of the array y just to push one more value to the end of a data set. If you're performance conscious, you may be ready to jump back on the iterative band wagon but hang on for a moment because this is the exact type of problem that Immutable Data Structures are intended to solve.

So what are Immutable Data Structures?

This may be no surprise, but Immutable Data Structures are... well... immutable. No surprise there. So what is it that makes them unique? Let's start by adding a small Mori code example and then we will show you what is going on under the hood.

// immutable value change using Mori
const _ = require('mori')
const log = (...args) => console.log(...args.map(_.toJs))

let a = _.vector(1, 2, 3, 4, 5)
let b = _.conj(a, 6)
log(b)

Ok, so this appears to be doing nearly the same thing as before, what's different? Let's have a look under the hood.

First we're looking at the memory allocation that happens with the old way. We have our code example on the left and the memory allocation on the right. As you can see, we are indeed just copying the entire array simply for the one push operation. This is just unacceptable, especially in production code where there will be far more activity happening at any given point in time. Now let's take a look at the same operation but using an immutable data structure from Mori.

So what exactly is going on here? This time, instead of copying the whole list from y, z merely references any unchanged values that were already located in y. Thus, we're not having to use up our memory needlessly copying an entire list for a simple push operation.

This being said, what are some other benefits of working with Immutable Data Structures?

Persistence

Persistence is the ability to access any old versions of data whenever you need it. Because you're never actually modifying any data, you can always read or write data based on any version at any time.

Lazy Evaluation

Lazy Evaluation is one feature I would consider to be a major benefit of working with Immutable Data Structures and this does come as a feature within Mori. So what is a lazy evaluation? Let's take the following example.

const _ = require('mori')
const log = (...args) => console.log(...args.map(_.toJs))

const double = n => n * 2
const inc = n => n + 1

let a = _.vector(1, 2, 3, 4, 5)
let b = _.map(double, a)
let c = _.map(inc, b)
let d = _.filter(_.isOdd, c)

If you're accustomed to normal javascript, you would think values a, b, c, and d have already run but in reality, they haven't. Why? Because they haven't been called on to be used yet. In other words, lazy evaluations mean that an operation that has been defined will only run in the future when it is actually required. This is key because it helps to prevent computing unnecessary values and also opens the door to using infinite data structures such as in the following example.

const _ = require('mori')
const log = (...args) => console.log(...args.map(_.toJs))

const a = _.range()
const b = _.take(10, a)
const c = _.take(3, a)
const d = _.take(20, a)
log(b) // --> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
log(c) // --> [ 0, 1, 2 ]
log(d) // --> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]

Here, we have defined an infinite range. Without lazy evaluation, that would be a quick ticket to crashing our system. However, because of Mori and its lazy evaluation scheme, no such thing will happen. Instead we can use b, c, and d to extract what we need from the range without having it crash our system. The lazy evaluation meant that the infinite range only extended out to the amount we needed and nothing more.

Drawbacks

However, it is also important to note that like all things in life, Immutable Data Structures are not the end all be all solution to everything. There can be increased algorithmic complexity for certain operations being carried out. So the smart thing is to know what types of circumstances best warrant their use.

When evaluating if you should use immutable data structures, it's worth asking the following questions.

  1. Are you going to be doing lots of functional style data modification? i.e. Will you be needing to make copies of lots of data structures?
  2. Will you be relying on your data structures being persistent over time?

If you said yes to either of those questions, then an immutable data structure should be a good fit for your needs.

Javascript and Immutable Data Structures

As you likely have figured out by now, Javascript does not support Immutable Data Structures at the time of this tutorial. Instead, we need to rely on outside libraries to gain these features. Right now, there are 2 main players in this area. Immutable.js and Mori.js. Immutable is the more known option as it was created by Facebook and is a pure Javascript implementation of Immutable Data Structures. Then we have Mori which isn't as well known. Mori is unique in that it imports Clojure's data structures into Javascript. For the most part, it looks and operates much like Underscore or Lodash but with its own data structures built in.

So you may be wondering, why are we using Mori over the more popular Immutable.js? Because according to some recent testing, Mori was by far more performant over not just Immutable.js but over raw Javascript itself as seen here http://jsperf.com/plain-js-vs-immutable-vs-mori/6 .

Conclusion

So today we've made it through another theory heavy day. I know it's not as fun but the good news is that tomorrow, we will start diving heavily into some code. We'll spend the tomorrow getting familiar with Mori itself then we will get ready to start our small project.