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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With