Standard Library

cover

In this article, I will explore what I learned in Clojure’s standard library in comparison to JavaScript’s. Without trying to get the most precise definition of “standard library,” for the sake of this discussion, I mean things that you can do with the base distribution of the language. This article in particular will discuss basic stuff that should be applicable regardless of the environment or runtime. What I say about Clojure applies to ClojureScript, and what I say about JS applies to the browser and Node.

Without further ado, let’s get into it.

Why Care About Standard Libraries?

Relying more on a standard library means relying less on (external) dependencies. So, in a world where everyone just npm installs anything and everything, allow me to go a bit over why standard libraries matter. I wrote a full article, Dependencyphobia, going over why I have issues with dependencies, but I’ll give a quick summary of them here:

Simply, the fewer dependencies you have in your project, the more robust and future-proof your project becomes. To be able to achieve this, you need the support of a rich standard library that allows you to do things with ease so that you don’t need to reach for an external library.

Data Structures

On the surface, the data structures I was exposed to so far are very similar in spirit (more on that in a bit) to those commonly used in JS. You get primitive data types like numbers, strings, and booleans, but one special data structure is the keyword which is most commonly used for “keys” in key-value pairs. For collections, you get vectors which are like JS arrays in the sense that they are ordered collections and can hold data that have different types, i.e., heterogenous. There are also hash maps which are like JS objects in the sense that they store key-value pairs.

I said that they are similar “in spirit” because these Clojure data structures are persistent (immutable), and that is a totally different behavior from JS where objects are mutable even when defined with the const keyword. Immutable data structures are one of the core features of functional programming, and I wrote an article discussing some FP features in a bit more detail if you’re interested. So in that sense, the similarities lie mostly in their practical use and not the underlying data structure implementation.

On a final note, Clojure has more data structures I didn’t mention like lists, hash sets, sorted sets (which I never used so far), and lazy sequences. What I like about the language’s design here is that these data structures are categorized into abstractions like “sequences” and “collections,” and that determines which functions you can use with them. I discussed this in a little more detail in contrast to the typical OOP approach in the same article linked above.

Functions and Composition

The variety of functions provided by the core library (clojure.core) is the biggest reason for writing this article. A huge number of ways in which you would want to manipulate data are already implemented for you in standard library functions. This is great because most of your needs are already covered, and if not, they can be done by composing a small number of these functions. Note that I don’t mean composing strictly as in “function composition” but rather the general idea of combining functions together. The result is code that is much more declarative than JS, and it’s also more concise and clean (when you get over the parentheses).

Now let me share some examples of cool stuff I learned, I’ll share some functions that I’ve got from exercises I solved in 4clojure plus some functions and features I discovered outside of the context of a specific problem. They helped me discover and appreciate Clojure’s standard library and how I can compose functions together to write declarative and concise code.

Default Map

(defn default-map
  [val keys]
  (zipmap keys (repeat val)))

(default-map 0 [:a :b :c]) ; {:a 0, :b 0, :c 0}

The task here was to make a function that takes a value and a vector of keys as its arguments, and its job is to construct a map where each key maps to this “default” value. Here’s how the function works.

Pack a Sequence

(defn pack
  [seq]
  (partition-by identity seq))

(pack [1 1 1 2 3 3 4]) ; ((1 1 1) (2) (3 3) (4))

This code is meant to take a sequence and “pack” each set of consecutive duplicates into a separate sequence. Thankfully, there is a function called partition-by which, as the name indicates, partitions sequences by something. That “something” should be a function, and here we supply identity which means we partition by values whose identities (the values themselves) are equal to each other. There’s also another function called partition which splits a sequence based on a fixed number of elements.

Dot Product

(defn dot-product
  [c1 c2]
  (apply + (map * c1 c2)))

(dot-product [1 2 3] [0 10 100]) ; 320

As per Wikipedia, “Algebraically, the dot product is the sum of the products of the corresponding entries of the two sequences of numbers.” So the code works like this:

I have a lot more examples like these to share, but I don’t want to do too much cherry-picking of all the cool one-liners that I wrote or found online.

Other Conveniences

There are other functions and conveniences that I learned about and find to be quite nice to help perform common tasks with a small amount of code.

(defn default-map
  [val keys]
  (->> val
       repeat
       (zipmap keys)))

The language is full of cool stuff like this. So, there is so much you can do without having to reach out to a library when it comes to handling and manipulating data. In JavaScript, I would’ve had to make a custom function and pass it to array.map() or array.reduce(), and then things get too complex so I split it into multiple map/reduce calls and create some intermediate arrays. When you compare this approach to Clojure’s, it feels wasteful, especially when the code becomes more verbose and imperative and you have to re-read the whole thing if you want to check how that particular step works.

You may argue against Clojure because it’s too dense and you have function names in front of you that you don’t know what each one does. First, with practice, these little idioms become easy to understand - Difficulty is simply a matter of unfamiliarity in most situations. Also, and most importantly, Clojure is a very well-documented language. https://clojuredocs.org/ is a community-powered site with good examples and it’s my favorite source of documentation. These documentation pages are also accessible from my editor thanks to Clojure LSP, which is even more convenient.

interleave clojuredocs

interleave lsp

There’s More

There always is! This article was mainly concerned with a small subset of clojure.core functions whose main job is to manipulate data. There is a whole world of stuff related to concurrent programming and mutable state that I am still learning at the moment, and that should be interesting to compare and contrast with JavaScript’s concurrency model.