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
?varlist = [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 [123] [x y z] list] (println x y z))
;; Better one, list ref created within the destructuring
(let [[x y z a :as list] [123]] (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 partsvarlist = [1, 2, "ignore me", 3];
var [x,y,,z] = list;
console.log(x,y,z);
Clojure matches with it
;; Clojure
;; Spreadoperator
(let [[x y z & rest-of-it :as list] [1235678]]
(println x y z rest-of-it))
;; Ignoring
(let [[x y _ z :as list] [12"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 checkis flimsy, you need tocheckfor undefined to avoid falsy errors. for eg: false will pass tosecond part// Default valuesvarlist = [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);// Renamingvar o = {“x”: 1, “y”: 2, “z”: 3};var {x: a, y: b, z: c} = o;
console.log(a, b, c);
// Default values with renamingvar 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 functionsfunctiondoSomethingAwesome({mandatoryParam, renameParam: changedParam, optionalParam=1, optionalRenameParam: changedOptionalParam=2}){console.log(mandatoryParam, changedParam, optionalParam, changedOptionalParam);
}
doSomethingAwesome({mandatoryParam: 10, renameParam: 20});
// Nested objectsvar o = {
“a”: {
“x”: 1,
“y”: [2, 3]
}
};
// With renaming and default valuesvar {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} {:x1:y2:z3}]
(println x y))
;; Object with renaming
(let [{rx :x y :y} {:x1:y2:z3}]
(println rx y))
;; Object with keywords and strings, but no renaming
(let [{:keys [x y z] :as list} {:x1:y2:z3}
{: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} {:x1:y2:z3}]
(println x y z xx yy zz))
;; Object defaults with renaming
(let [{rx :x y :y rz :z:or {rz 30}} {:x1:y2}]
(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 {:mandatoryParam10:renameParam20})
;; 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 {:mandatoryParam10:renameParam20})
;; Nested objects
(defo {:a {
:x1: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
https://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.
This post was initially featured at https://medium.com/@aravindbaskaran/destructuring-clojure-with-javascript-bd1398bdacb6