Destructuring Clojure with Javascript
Clojure is a language I am learning as I work on awesome stuff at Swym Corporation. When I came on board last year, I had no clue what it meant unless it was the thing that causes problems in Javascript for loops. Turns out it is pretty close to Javascript.
Coming to Clojure. It is a beautiful language. Weird at first, but it grows on you. Functional languages somehow tend to be. In this post, I am going to talk about a feature shared by many languages, but with specific details in Clojure and Javascript with their joys and pains. Destructuring. Why Clojure? — that’s a post for another time.
Destructuring makes assignments easier, which could instead take multiple lines of code.
Simple destructuring,
To put it simply, load variables from vectors and arrays quickly without repeating a word more or less.
//JS// Old way
var list = [1,2,3];
var x = list[0], y = list[1], z = list[2];
console.log(x, y, z);// Destructured way
var list = [1,2,3];
var [x, y, z, a] = list;
console.log(x, y, z, a);
// Note - Any extra parameter not accounted for becomes undefined
One stark between Clojure and JS, is the immutatablity of data-structures. Like variable assignment is available, but not necessarily good to use. This plays heavily when understanding Clojure, I thought of it as a handicap initially. But as time went by, the intuitiveness showed itself.
;; Clojure;; Using let definition, same can be done with a def for the list
(let [list [1 2 3]
[x y z] list]
(println x y z));; Better one, list ref created within the destructuring
(let [[x y z a :as list] [1 2 3]]
(println x y z a list))
;; Note - Extra parameter unaccounted for becomes nil
Going a step deeper into vectors, especially useful when you don’t want to list out arguments and destructure as they come
// JS
// Bunching rest of the values with a spread operator
var list = [1, 2, 3, 5, 6, 7, 8];
var [x, y, z, …a] = list;
console.log(x, y, z, a);
// Note - The last param will be the spread operator(...) Powerful for array merging, and data processing. More on spread operators// Ignoring parts
var list = [1, 2, "ignore me", 3];
var [x,y,,z] = list;
console.log(x,y,z);
Clojure matches with it
;; Clojure
;; Spread operator
(let [[x y z & rest-of-it :as list] [1 2 3 5 6 7 8]]
(println x y z rest-of-it));; Ignoring
(let [[x y _ z :as list] [1 2 "ignore me" 3]]
(println x y z))
Picking default values is simple, without all those flimsy if checks from yore.
// JS
// Old way - Default values
var list = [1,2];
var x = list[0], y = list[1];
var z = list[2] || 'default z'; // This check is flimsy, you need to check for undefined to avoid falsy errors. for eg: false will pass to second part
// Default values
var list = [1, 2];
var [x,y,z="default z"] = list;
console.log(x,y,z);
Same in Clojure
;; Clojure
;; Default values
(let [[x,y,z :or [z 10]] [1 2]]
(println x y z))
Objects and nested structures
This is where the real fun lies. Very powerful for functions which take configurations and optional parameters by simplifying the assignments.
// JS
// Object
var o = {“x”: 1, “y”: 2, “z”: 3};
var {x, y, z} = o;
console.log(x, y, z);
// Also works - var {x, y, z} = {x: 1, y: 2, z: 3};
// Also works - var x,y,z; ({x, y, z} = o);// Renaming
var o = {“x”: 1, “y”: 2, “z”: 3};
var {x: a, y: b, z: c} = o;
console.log(a, b, c);// Default values with renaming
var o = {“x”: 1, “y”: 2, “z”: 3};
var {x, y, z: c, p: d=10, q=20} = o;
console.log(a, b, c, d, q);// Usage in functions
function doSomethingAwesome({mandatoryParam, renameParam: changedParam, optionalParam=1, optionalRenameParam: changedOptionalParam=2}){
console.log(mandatoryParam, changedParam, optionalParam, changedOptionalParam);
}
doSomethingAwesome({mandatoryParam: 10, renameParam: 20});// Nested objects
var o = {
“a”: {
“x”: 1,
“y”: [2, 3]
}
};// With renaming and default values
var {a: {x, y: [aa, bb, cc=20], z=10}} = o;
console.log(a, x, y, p, aa, bb, cc, z);
Compared to Javascript, Clojure has some guns, like :keys and :strs binding forms to bunch the destructuring together. Warning — Entering a little more complicated territory
;; Clojure
;; Object — not so efficient when there is no renaming
(let [{x :x y :y} {:x 1 :y 2 :z 3}]
(println x y))
;; Object with renaming
(let [{rx :x y :y} {:x 1 :y 2 :z 3}]
(println rx y));; Object with keywords and strings, but no renaming
(let [{:keys [x y z] :as list} {:x 1 :y 2 :z 3}
{:strs [xx yy zz] :as list-str} {“xx” 11 “yy” 22 “zz” 33}]
(println x y z xx yy zz));; Object with defaults
(let [{:keys [x y z xx yy zz] :or {xx 10 yy 20 zz 30} :as list} {:x 1 :y 2 :z 3}]
(println x y z xx yy zz))
;; Object defaults with renaming
(let [{rx :x y :y rz :z :or {rz 30}} {:x 1 :y 2}]
(println rx y rz))
;; Usage in functions — not so efficient
(defn do-something-awesome [{
mandatoryParam :mandatoryParam
changedParam :renameParam
optionalParam :optionalParam
changedOptionalParam :optionalRenameParam
:or {optionalParam 1 changedOptionalParam 2} }]
(println mandatoryParam changedParam optionalParam changedOptionalParam))(do-something-awesome {:mandatoryParam 10 :renameParam 20});; Usage in functions — better
(defn do-something-awesome [{
:keys [mandatoryParam optionalParam optionalRenameParam]
changedParam :renameParam
changedOptionalParam :optionalRenameParam
:or {optionalParam 1 changedOptionalParam 2}
}]
(println mandatoryParam changedParam optionalParam changedOptionalParam))(do-something-awesome {:mandatoryParam 10 :renameParam 20});; Nested objects
(def o {
:a {
:x 1
:y [2, 3]
}
})
;; With default values
(let [{ {:keys [x y]} :a} o
[y1,y2] y] ;; forced to add a line to break y to params
(println x y y1 y2));; y1, y2 don’t get assigned, what’s wrong? a TODO for me to figure out
(let [{ {:keys [x [[y1,y2] :y]]} :a} o]
(println x y y1 y2))
The usage of :keys binding form and regular destructure is a good way to use rather than only one. Renaming is supported with :as binding form at a higher level
Now time for some overkill. Lets see which combination of the above breaks the REPL.
JS Overkill — Very hard to find this case — renaming with internal destructuring
// Overkill?
var o = {
“a”: {
“x”: 1,
“y”: [2, 3]
}
};
var {a: {x, y: p = [aa, bb, dd=20], z=10}} = o;
console.log(a, x, y, p, aa, bb, dd, z);
// Throws error, renaming with internal destructuring doesn’t work
Clojure Overkill — Was far easier to find. Trying to destructure a vector inside an object while destructuring the object.
;; y1, y2 don't get assigned, what’s wrong? a TODO for me to figure out
(let [{ {:keys [x [[y1,y2] :y]]} :a} o]
(println x y y1 y2))
Destructuring makes for a more intuitive code and easier to write functions, read functional programming. Comparing the two, it makes Javascript’s functional origins clearer. A new point to wonder —Does the Javascript prototype object fall under a functional programming paradigm?
Unfortunately for Javascript users, destructuring is not fully safe to use on all browser based JITs, but if you know your deployment, go for it.
Do let me know your thoughts and how you would use them, and please improve anything I may have written wrong.
References
Javascript
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
Clojure
Very useful to read this one first — http://blog.brunobonacci.com/2014/11/16/clojure-complete-guide-to-destructuring/
http://clojure.org/guides/destructuring
https://gist.github.com/john2x/e1dca953548bfdfb9844
P.S: PHP sucks, Javascript is beautiful, Java is consistent, CSS is beautiful again, Objective C is weird, Python is sensitive(read whitespace), C#/.Net is a mutant and so on. All personal opinions.
P.P.S : The gists I used for reference
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!