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.
Source — http://java.ociweb.com/mark/clojure/article.html
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.
To put it simply, load variables from vectors and arrays quickly without repeating a word more or less.
//JS
// Old wayvar list = [1,2,3];var x = list[0], y = list[1], z = list[2];console.log(x, y, z);
// Destructured wayvar 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 operatorvar 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 partsvar 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 valuesvar 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 valuesvar 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))
This is where the real fun lies. Very powerful for functions which take configurations and optional parameters by simplifying the assignments.
// JS// Objectvar 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 functionsfunction doSomethingAwesome({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} {: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 :mandatoryParamchangedParam :renameParamoptionalParam :optionalParamchangedOptionalParam :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 :renameParamchangedOptionalParam :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_assignmenthttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
ClojureVery useful to read this one first — http://blog.brunobonacci.com/2014/11/16/clojure-complete-guide-to-destructuring/http://clojure.org/guides/destructuringhttps://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