Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Destructuring forms and Compojure?

I'd thought I'd post this as I got it to work through guesswork without a real understanding of what's going on and I thought it might be helpful if someone explained it.

I understand how to get at an element of the :params map in a Compojure handler:

(GET "/something" [some_arg] "this is the response body")

or

(GET "/something" {{some_arg "some_arg"} :params} "this is the response body")

although I don't completely understand what the {some_arg "some_arg"} part is doing :(

I also wanted to access the :remote-addr part of the request as well as some_arg. And I ended up with

(GET "/something" {{some_arg "some_arg"} :params ip :remote-addr}
    (do-something-with some_arg ip))

So, I get that the unquoted strings some_arg and ip are the names of variables to which I want the values bound but the map above isn't a valid Clojure map. How does it work?

I also get that this is evaluated against the Ring request map (which is somehow supplied by the defroutes macro) but the expression above isn't a function or macro definition so how can it 'exist' as a valid expression in my code? Is there some sort of suspension of the normal rules for macro arguments? I've been unable to find a definition of the syntax of destructuring forms comprehensible to this non-Lisp'er.

like image 936
edoloughlin Avatar asked Nov 03 '10 16:11

edoloughlin


2 Answers

The map is a valid destructuring map. In any place where you bind names, you can use destructuring. You could do the same thing in a let, like this:

user=> (let [{{some-arg "some_arg"} :params ip :remote-addr} {:remote-addr "127.0.0.1" :params {"some_arg" "some_value"}}] [ip some-arg])
["127.0.0.1" "some_value"]

I wrote a post about map destructuring in the context of named arguments, but it applies here. You might find this useful: Clojure - named arguments

There are a lot of blog posts demonstrating destructuring, including this one. I'm not sure which one would be a canonical place to learn from.

I don't pretend to know what exactly compojure does with that map under the hood, but I presume it throws it in a let or something similar as I demonstrated above. GET is a macro, so it doesn't have to evaluate the map you pass it, which is why you wouldn't get an error unless it evaluated it.

user=> (defmacro blah [m])
#'user/blah
user=> (blah {a "b" c "d"})
nil
user=> (defn blah [m])
#'user/blah
user=> (blah {a "b" c "d"})
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:9)

Under the hood, magic happens to that map and it gets passed to a function called destructuring that does the destructuring magic.

There isn't really anything special going on here other than normal macro/special form foo and delayed evaluation.

like image 89
Rayne Avatar answered Sep 28 '22 01:09

Rayne


Destructing takes place within a binding form, and for map destructuring the var to be bound is on the left, and the key is on the right:

user=> (let [{a :foo}  {:foo :bar}]
user=*   a)
:bar

Compojure is doing a binding form behind the scenes, so that map destructuring form you were using above is effectively turned into something like:

(let [{{some_arg "some_arg"} :params}  request]
  ...)

Where request is an implicitly provided map.

The vector version (e.g., [some_arg]), is an alternative that just binds against the :params map contained in the request.

like image 30
Alex Taggart Avatar answered Sep 28 '22 03:09

Alex Taggart