0

I was browsing some Clojure source code and came across this function:

(defn draw-mask [app-state [r c]]
  (let [x1 (+ 25 (* c 50))
        y1 (+ 25 (* r 50))]
    [:circle {:cx x1 :cy y1 :r 12 :fill "white"}]))

What I do not understand is how [app-state [r c]] is parsed. What would a typical data structure passed to this and how does defn parcel it out. Any references to this in the clojure docs would be appreciated, especially since ClojureDocs.org was of no help on the subject.

3
  • Iirc, all destructing uses the deconstruct (or something like that) macro. It just turns it into calls to first, rest, and nth for lists, and delegates to accessors for maps. Just typical macro "magic". Commented Dec 7, 2016 at 16:06
  • 1
    The word for this is "destructuring" -- using that should help you find good documentation, such as clojure.org/guides/destructuring Commented Dec 7, 2016 at 16:07
  • ...frankly, I think that describing how destructuring works in general (which is how I read this) is too wide a scope to be a good StackOverflow question -- if you look at the destructuring page linked above, there's much more content than what an answer can reasonably cover! Commented Dec 7, 2016 at 16:10

2 Answers 2

3

Correction

As @Josh points out, sequential destructuring requires nth to work: seqability is not enough.


This is a simple case of sequential destructuring. Let's use a function that shows what's going on:

(defn foo [app-state [r c]]
 {:app-state app-state, :r r, :c c})
  • The first argument, app-state, can be anything.
  • The second argument, [r c], must be something like a sequence that nth applies to. Then
    • r is its first element, and
    • c is its second.
  • If the sequence isn't long enough, these yield nil.

Examples:

(foo 1 ())
;{:app-state 1, :r nil, :c nil}

(foo inc "hello, world!")
;{:app-state #<core$inc clojure.core$inc@32d23c2f>, :r \h, :c \e}

(foo :one [:two :three :four])
;{:app-state :one, :r :two, :c :three}

(foo  "Flubalub" (drop 5 (iterate #(* 10 %) 1)))
;{:app-state "Flubalub", :r 100000, :c 1000000}

But

(foo 99 #{1 2})
;java.lang.UnsupportedOperationException: nth not supported on this type: PersistentHashSet
; ...
Sign up to request clarification or add additional context in comments.

Comments

2

The function draw-mask in your example takes two arguments. The first is app-state, which is not used in your code, and the second can be one of several different types of data: values from a map, a string, a list, or most commonly, a vector. You can see the different types here that can be used in nthFrom in the clojure code.

This is called sequential destructuring, and as other comments above mention, it's a big topic. However, for your case, this is how it works:

(draw-mask xyz [3 9])     ---> In draw-mask, r is 3, and c is 9.
(draw-mask xyz [3])       ---> In draw-mask, r is 3, and c is nil.
(draw-mask xyz [3 9 12])  ---> In draw-mask, r is 3, and c is 9 -- 12 is not bound

One clarification here: whether or not a structure is seqable? is not the main criteria for being able to be destructured. For example, a set is seqable? but cannot be destructured. The main criteria for sequential destructuring (different from associative destructuring, using maps, which is not discussed here) is that nth must be supported on it. In RT.java, you will see the list of possible types. They are: CharSequence, a native Java array, RandomAccess, Matcher, Map.Entry, and Sequential (Sequential will cover the most common structures: list and vector).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.