[006.2] Mori.js: hands on

Main functions and data structures

Subscribe now

Mori.js: hands on [12.28.2017]

Hello and welcome back to our tutorial on functional programming in Javascript with Mori.js. So far, we have spent a couple of days learning about core functional programming and the role of immutable data structures in our programs. Now we're going to be diving head first into working with our library of choice called Mori.js. Once we have learned the basics of Mori, we will put everything into practice by building out a small project that brings together all the theory we have learned.

Getting Started

To get started, let's open our terminal and handle a few tasks.

cd /your/project/directory
mkdir fun-with-mori
cd fun-with-mori
touch app.js
npm init
npm install --save mori

Good, now let's open app.js in your text editor.

Excellent, now let's get familiar with some of Mori's data structures.

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

const list = _.list(1, 2, 3, 4)
const vector = _.vector(1, 2, 3, 4)
const hashMap = _.hashMap('name', 'jon', 'age', 29, 'favorite numbers', vector)
const sortedMap = _.sorted_map(1, 'a value', 2, 'another value', 3, 'one more value')
const set = _.set(['dog', 'cat', 'micro pig', 'parrot'])
const sortedSet = _.sortedSet(['lion', 'tiger', 'bobcat'])
const range = _.range(10)
const queue = _.queue(1, 2, 3)

log(list) // --> (1 2 3 4)
log(vector) // --> [1, 2, 3, 4]
log(hashMap) // --> {"name" "jon", "age" 29, "favorite numbers", [1, 2, 3, 4]}
log(sortedMap) // --> {1 'a value', 2 'another value', 3 'one more value'}
log(set) // --> #{"dog" "cat" "micro pig" "parrog"}
log(sortedSet) // --> #{"lion" "tiger" "bobcat"}
log(range) // --> (0 1 2 3 4 5 6 7 8 9)
log(queue) // --> #queue [1 2 3]

Great, let's turn our attention to the list data structure first. Much like the name implies, this is a simple immutable list that happens to be really fast at adding values to the head of the list. Admittedly, I haven't found a use case for these yet but it's good to know they are there.

Next we have the vector. This operates in many ways like the Javascript array and is particularly good at adding values to the end of the list along with being fast for random access of values anywhere along the list. Because of this factor, I tend to use this the most out of any array like structure.

Now we have the hashMap which is similar in many ways to the Javascript object. However, unlike a Javascript object, hashMaps support complex keys. If you check out sorted_map, it's practically the same thing but now the keys will be kept ordered.

Next we have sets. This operates as an immutable list, however all the items must be unique and sequencable in some way. In other words you really only want to populate this list with numbers or strings. However, because of this attribute, sets have some operations that are unique to them that you don't get on the other data structures. And of course a sortedSet is just a set where all the items will be kept ordered.

Moving on, we have the range. This creates a potentially infinite range of values (generally numbers). If written with no parameters, then for all intents and purposes it is an infinite range that can be used in interesting ways thanks to the lazy evaluation we learned about yesterday. However, you can use parameters to limit both the length and scope of the range you will generate. In this case, we limit the range to 10 values.

Lastly we have the queue which is mainly good for being fast at adding values to the end or beginning. Not good for much else.

So now that we have had a look at Mori's data structures, let's now write out some code that uses some of the most common operations I personally have depended on. This isn't a comprehensive list of everything Mori offers, for that you will want to check out their documentation which will be linked at the end. Let's switch back to our text editor and write this code out.

const conj = _.conj(vector, 5, 6) // --> [1, 2, 3, 4, 5, 6]
const nth = _.nth(vector, 3) // --> 4
const popVector = _.pop(vector) // --> [1, 2, 3]
const popList = _.pop(list) // --> (2 3 4)
const empty = _.empty(vector) // --> []
const each = _.each(i => log(i), vector)
const map = _.map(i => i * 2, list, vector)
const filter = _.filter(_.isEven, list) // --> returns even numbers only
const remove = _.remove(_.isEven, list) // --> removes all even numbers
const reduce = _.reduce(_.sum, list)

const f = (acc, key, val) => acc + "(" + key + ":" + val + ")"
const reduceKV = _.reduceKV(f, "", hashMap)
const take = _.take(5, range) // --> (0 1 2 3 4)
const drop = _.drop(2, list) // (3 4)

Great, first let's look at conj. This is a simple operation which adds values to the end of a list. Nothing crazy here. Next we have nth, this allows us to look up a value at any index within a list. popis interesting because its behavior changes if it's used on a list or other data structure. On a list, pop returns a list with the first element removed. On a vector, it returns a list with the 2nd item removed. Next we have empty which simply returns an empty version of whatever data structure you started with.

Moving on, we have each which allows you to iterate over a collection for side effects. Next we have the incredibly important map operation. It will apply whatever function you pass to each value in whatever collections you pass to it. Yes, you can pass multiple collections into map. Next you have the filter operation, you can use this to apply a function which will test for truthiness to any all values in a collection then return only the values which were truthy. Then you get remove which is the exact opposite of filter. In other words, it returns a list of only the values that were falsy in our function test. You can get a pretty good idea of the behavior here. Next we have reduce which accumulates a collection into a single value. In this case we're returning the sum of all the values in our list. reduceKV is essentially the same thing but works on hashMaps. take allows you to take a specified number of elements in a collection, in this case 5. Lastly we have drop which operates as the exact opposite of take.

Now, before we wrap things up, we should go through a few unique helper functions that Mori provides. Let's write some code.

const identity = _.identity(3) // --> 3
const isEven = _.isEven(2) // --> true
const isOdd = _.isOdd(2)// --> false

const juxt = _.juxt(_.first, _.last)
juxt(vector) // --> [1, 4]

const pipeline = _.pipeline(vector, filter, i => i * 2) // --> [4, 8] 

const curry = _.curry(_.conj, 5)
curry(vector)

const toClj = _.toClj([1, 2, 3, 4]) // --> converts JS array to _.vector
const toJsVector = _.toJs(vector) // --> [1, 2, 3, 4]
const toJsHashMap = _.toJs(hashMap) // --> {name: 'jon', age: 29, numbers: [1, 2, 3, 4]}

Great, so we have a number of interesting functions. Some obvious, some not so obvious. First we have identity which simply returns the value of what ever is passed to it. Next we have isEven and isOdd which test to see if a value is, you guessed it, even or odd. Next we have juxt which is interesting. What it does is it creates a function that is a juxtaposition of 2 or more functions. If that doesn't make any sense, then take a look at the example. Here we pass 2 Mori helper functions as parameters called first and last which return the first and last elements of whatever collection they are passed. In this case, we use juxt to create a function which will return the first and last elements of a collection. Simple as that, but it gives an idea of what could be possible with juxt. Next we have pipeline which we saw on day 1. We use it to thread a starting collection through a number of functions to return a final value or collection. Next we have curry which, if you are familiar with currying, allows you to curry arguments to a function. Lastly we have toClj and toJs which we can use to convert values from Javascript to Clojure and vice versa. This can be very helpful at times.

Conclusion

So we just covered a fair number of Mori's functions and data structures. It's quite a lot so if you are a little lost it's perfectly okay. We have the docs linked and starting tomorrow, we will begin designing our small project. Thank you and see you tomorrow.