Destructuring forms and Compojure? - clojure

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.

Related

Clojure ring-cors/wrap-cors setup

I'm using ring-cors and trying to pass a cors-policy for the (wrap-cors) function. This is what my code looks like:
(def cors-policy
{:access-control-allow-origin [#"http://localhost:8080"]
:access-control-allow-methods [:get :put :post]})
(def dev-handler (-> #'japi/routes
wrap-reload
wrap-params
(wrap-cors cors-policy) ;; <- Here
wrap-json-response
(wrap-defaults api-defaults)
push-state/handle))
This results in an error:
No value supplied for key: {:access-control-allow-origin
#{"http://localhost:8080"}, :access-control-allow-methods #{:get :post :put}}
Looking at the source code for (wrap-cors) it looks like the error is coming from trying to apply (hash-map) to my cors-policy map. It seems like I cannot pass a map definition but instead I have to pass the keys/values explicitly when calling (wrap-cors). Any ideas to work around this?
I've tried (apply hash-map cors-policy) in the repl and that works fine, however when passing a dummy handler such as (wrap-cors identity cors-policy) this again results in the same error.
Edit: cfrick's answer is correct, note however that I had to remove shadow-cljs' (push-state/handle) handler at the end of my dev-handler definition for my setup to work.
The wrapper uses a "pattern" that is sometimes seen and focuses on
"human consumption" of the function. It takes the "rest" of the
arguments and turns the pairs of it into a map. This is already "meh"
for humans and is utterly bad for machines (e.g. to pass as arguments).
You have to do the call it like this:
(wrap-cors $handler :a 1 :b 2)
So the easiest way from here would be:
(def cors-policy
[:a 1
:b 2])
(apply wrap-cors $handler cors-policy)
Or if you want to stick with the map (IMHO a good approach), you have to
flatten the map beforehand. e.g.
(apply wrap-cors $handler (into [] cat cors-policy))
But with the use of the threading macro -> this becomes harder to do
now (-> is just a macro and the resulting code would be (apply $handler wrap-cors ...) which is unintended.
So at this point I'd add my own defn that just takes the handler
again. E.g. something like
(defn cors-wrapper
[handler config-map]
(apply wrap-cors handler (into [] cat config-map)))

How can I iterate over a list with a macro?

I am trying to print the documentation for all functions in a given namespace by invoking the following expression in a REPL:
(doseq
[f (dir-fn 'clojure.repl)]
(doc f))
However the invocation of this expression returns nil without printing the documentation to the REPL. I know this might have to do with doc being a macro, but I'm a Clojure novice and am not entirely sure how to understand the problem.
Why does this expression return nil without printing the documentation?
How can this expression be modified so that it prints the documentation for each function in a given namespace?
Thanks!
Update: Combined both provided answers:
(defn ns-docs [ns']
(doseq [[symbol var] (ns-interns ns')]
(newline)
(println symbol)
(print " ")
(println (:doc (meta var)))))
(ns-docs 'clojure.repl)
I would, instead, start here:
The Clojure CheatSheet
ClojureDocs.org
Clojure-Doc.org (similar name, but different)
The API & Reference sections at Clojure.org
Note that doc is in the namespace clojure.repl, which reflects its intended usage (by a human in a repl). Here is some code that will also iterate on a namespace & print doc strings (using a different technique):
(doseq [[fn-symbol fn-var] (ns-interns 'demo.core)]
(newline)
(println fn-symbol)
(println (:doc (meta fn-var))))
where demo.core is the namespace of interest.
Note that ns-interns gives you both a symbol and var like:
fn-symbol => <#clojure.lang.Symbol -main>
fn-var => <#clojure.lang.Var #'demo.core/-main>
The meta function has lots of other info you may want to use someday:
(meta fn-var) =>
<#clojure.lang.PersistentArrayMap
{ :arglists ([& args]),
:doc "The Main Man!",
:line 9, :column 1,
:file "demo/core.clj",
:name -main,
:ns #object[clojure.lang.Namespace 0x14c35a06 "demo.core"]}>
While this probably won't help you with answering your question, the problem of evaluating macro's comes up a lot when you are learning Clojure.
Macros are responsible for the evaluation of their arguments. In this case clojure.repl/doc will ignore the current lexical context and assume that the symbol f that you're giving it is the name of a function you want to see the documentation for. It does this because it's intended to be used at the REPL, and is assuming you wouldn't want to type quotes all the time.
As f doesn't exist, it prints nothing. Then doseq returns nil, since it exists to do something for side effects only - hence starting in do. In order to pass an argument to a macro that refuses to respect the lexical context like this, you need to write the code for each element in the list.
You can do this by hand, or by constructing the code as data, and passing it to eval to execute. You can do this in an imperative style, using doseq:
(doseq [f (ns-interns 'clojure.repl)]
(eval `(doc ~(symbol "clojure.repl" (str (first f))))))
or in a slightly more Clojurey way (which will allow you to see the code that it would execute by removing eval from the end and running it at the REPL):
(->> (ns-interns 'clojure.repl)
(map #(list 'clojure.repl/doc (symbol "clojure.repl" (str (first %)))))
(cons `do)
eval)
In both of these we use quote and syntax-quote to construct some code from the list of symbols reflected from the namespace, and pass it to eval to actually execute it. This page on Clojure's weird characters should point you in the right direction for understanding what's going on here.
This an example of why you shouldn't write macro's, unless you've got no other options. Macro's do not compose, and are often difficult to work with. For a more in depth discussion, Fogus's talk and Christophe Grand's talk are both good talks.
Why does this expression return nil without printing the documentation?
Because the doc macro is receiving the symbol f from your loop, instead of a function symbol directly.
How can this expression be modified so that it prints the documentation for each function in a given namespace?
(defn ns-docs [ns']
(let [metas (->> (ns-interns ns') (vals) (map meta) (sort-by :name))]
(for [m metas :when (:doc m)] ;; you could filter here if you want fns only
(select-keys m [:name :doc]))))
(ns-docs 'clojure.repl)
=>
({:name apropos,
:doc "Given a regular expression or stringable thing, return a seq of all
public definitions in all currently-loaded namespaces that match the
str-or-pattern."}
...
)
Then you can print those maps/strings if you want.

assoc with argument clojure

If I have an hash-map and I want to assoc a value to it, and I get the key as an argument, what should i do?
(defn define [name type kind] "define new var in one of the tables"
(if (or (= type "static") (= type "field"))
(def classScope (assoc classScope name (list type kind (addCount kind))))
(def methodScope (assoc methodScope name (list type kind (addCount kind))))
)
)
My problem is that i can't use :name, and not 'name.
Thanks!!
Update: If you want your keys to be in keyword form, just call keyword on them....
(defn my-map-fn [name type kind]
(assoc some-map (keyword name) (some-fn type kind)))
e.g.
(my-map-fn "some-name" "some-type" "some-kind") => {:some-name some-val}
Note that you shouldn't use def inside of defn. It looks like you want to keep a map of data and as you call define you want to store some more data in that map. A way that I go about this is to use atoms (there are other ways too).
(defonce classScope (atom {})
(defonce methodScope (atom {}))
(defn define
"if you want a doc string it goes here"
[name type kind]
(swap! (if (#{"static" "field"} type) classScope methodScope)
#(assoc % name (list type kind (addCount kind)))))
the benefit here is you get atomic updates and calls to define that may happen really close together won't butt heads.
Let's start with your explanatory comment:
I'm trying to create an hash-map that's like a symbol table: I will
identify each variable by its name, and it will have a list with its
type, kind and index in the code.
A few thoughts:
Don't use a list for the variable's characteristics; use a map.
You can think of the name of a variable as
a plain old string
a symbol
a keyword
Any of these works as the key of a map entry. Keep it simple. Use a string.
You're going to need such a table for every scope. And a scope should know its enclosing scope.
The descriptors static and field are not types; nor are they
alternatives in - say - Java.
I suggest you look at clojure.spec and typed Clojure to see how similar problems are handled within Clojure.

Om Next's query->ast and ast->query functions

According to Om Next's documentation:
query->ast
(om.next/query->ast '[(:foo {:bar 1})])
Given a query expression return the AST.
ast->query
(om.next/ast->query ast)
Given a query expression AST, unparse it into a query expression.
Question: Why would one need these functions? That is, why would one need to directly manipulate a query abstract syntax tree (which I'm assuming are clojure maps that represent a query tree, along with some meta data) in om next?
There are some scenarios where you need to manipulate the query ast directly. In remote parsing mode, the parser expects your read functions to return either {:remote-name true } or a (possibly modified) {:remote-name AST-node} (which comes in as :ast in env). Most often you'll have to modify the AST to restructure it or add some data.
Example 1:
You have a query: [{:widget {:list [:name :created]}}]
The :widget part is pure UI related, your server doesn't need to know it exists, it only cares/knows about the :list.
Basically you'll have to modify the AST in the parser:
(defmethod read :list
[{:keys [ast query state]} key _ ]
(let [st #state]
{:value (om/db->tree query (get st key) st)
:remote (assoc ast :query-root true)}))
If you use om/process-rootsin your send function, it'll pick up the :query-root out of the ast and rewrite the query from [{:widget {:list [:name :created]}}] to [{:list [:name :created]}].
Example 2:
Another example would be when you want to mutate something at a remote:
(defmethod mutate 'item/update
[{:keys [state ast]} key {:keys [id title]}]
{:remote (assoc ast :params {:data {:id id :title title })})
Here you need to explicitly tell Om to include the data you want to send in the AST. At your remote you then pick apart :data to update the title at the given id
Most of the time you won't use the functions you described in your questions directly. The env available in every method of the parser has the ast in it.
Something I stumbled on, while trying to use Compassus:
Let's say you have a complex union/join query that includes parametric sub-queries. Something like this:
`[({:foo/info
{:foo/header [:foo-id :name]
:foo/details [:id :description :title]}} {:foo-id ~'?foo-id
:foo-desc ~'?foo-desc})]
Now let's say you want to set parameters so on the server you can parse it with om/parser and see those params as 3rd argument of read dispatch. Of course it's possible to write a function that would find all necessary parameters in the query and set the values. That's not easy though, and as I said - imagine your queries can be quite complex.
So what you can do - is to modify ast, ast includes :children :params key. So let's say the actual values for :foo-id and :foo-desc are in the state atom under :route-params key:
(defn set-ast-params [children params]
"traverses given vector of `children' in an AST and sets `params`"
(mapv
(fn [c]
(let [ks (clojure.set/intersection (-> params keys set)
(-> c :params keys set))]
(update-in c [:params] #(merge % (select-keys params (vec ks))))))
children))
(defmethod readf :foo/info
[{:keys [state query ast] :as env} k params]
(let [{:keys [route-params] :as st} #state
ast' (-> ast
(update :children #(set-ast-params % route-params))
om/ast->query
om.next.impl.parser/expr->ast)]
{:value (get st k)
:remote ast'}))
So basically you are:
- grabbing ast
- modifying it with actual values
you think maybe you can send it to server right then. Alas, no! Not yet. Thing is - when you do {:remote ast}, Om takes :query part of the ast, composes ast out of it and then sends it to the server. So you actually need to: turn your modified ast into query and then convert it back to ast again.
Notes:
set-ast-params function in this example would only work for the first level (if you have nested parametrized queries - it won't work),
make it recursive - it's not difficult
there are two different ways to turn ast to query and vice-versa:
(om/ast->query) ;; retrieves query from ast and sets the params based
;; of `:params` key of the ast, BUT. it modifies the query,
;; if you have a join query it takes only the first item in it. e.g. :
[({:foo/foo [:id]
:bar/bar [:id]} {:id ~'?id})]
;; will lose its `:bar` part
(om.next.impl.parser/ast->expr) ;; retrieves query from an ast,
;; but doesn't set query params based on `:params` keys of the ast.
;; there are also
(om/query->ast) ;; and
(om.next.impl.parser/expr->ast)

What's the "big idea" behind compojure routes?

I'm new to Clojure and have been using Compojure to write a basic web application. I'm hitting a wall with Compojure's defroutes syntax, though, and I think I need to understand both the "how" and the "why" behind it all.
It seems like a Ring-style application begins with an HTTP request map, then just passes the request through a series of middleware functions until it gets transformed into a response map, which gets sent back to the browser. This style seems too "low level" for developers, thus the need for a tool like Compojure. I can see this need for more abstractions in other software ecosystems as well, most notably with Python's WSGI.
The problem is that I don't understand Compojure's approach. Let's take the following defroutes S-expression:
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
I know that the key to understanding all of this lies within some macro voodoo, but I don't totally understand macros (yet). I've stared at the defroutes source for a long time, but just don't get it! What's going on here? Understanding the "big idea" will probably help me answer these specific questions:
How do I access the Ring environment from within a routed function (e.g. the workbench function)? For example, say I wanted to access the HTTP_ACCEPT headers or some other part of the request/middleware?
What's the deal with the destructuring ({form-params :form-params})? What keywords are available for me when destructuring?
I really like Clojure but I am so stumped!
Compojure explained (to some degree)
NB. I am working with Compojure 0.4.1 (here's the 0.4.1 release commit on GitHub).
Why?
At the very top of compojure/core.clj, there's this helpful summary of Compojure's purpose:
A concise syntax for generating Ring handlers.
On a superficial level, that's all there is to the "why" question. To go a bit deeper, let's have a look at how a Ring-style app functions:
A request arrives and is transformed into a Clojure map in accordance with the Ring spec.
This map is funnelled into a so-called "handler function", which is expected to produce a response (which is also a Clojure map).
The response map is transformed into an actual HTTP response and sent back to the client.
Step 2. in the above is the most interesting, as it is the handler's responsibility to examine the URI used in the request, examine any cookies etc. and ultimately arrive at an appropriate response. Clearly it is necessary that all this work be factored into a collection of well-defined pieces; these are normally a "base" handler function and a collection of middleware functions wrapping it. Compojure's purpose is to simplify the generation of the base handler function.
How?
Compojure is built around the notion of "routes". These are actually implemented at a deeper level by the Clout library (a spinoff of the Compojure project -- many things were moved to separate libraries at the 0.3.x -> 0.4.x transition). A route is defined by (1) an HTTP method (GET, PUT, HEAD...), (2) a URI pattern (specified with syntax which will apparently be familiar to Webby Rubyists), (3) a destructuring form used in binding parts of the request map to names available in the body, (4) a body of expressions which needs to produce a valid Ring response (in non-trivial cases this is usually just a call to a separate function).
This might be a good point to have a look at a simple example:
(def example-route (GET "/" [] "<html>...</html>"))
Let's test this at the REPL (the request map below is the minimal valid Ring request map):
user> (example-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "<html>...</html>"}
If :request-method were :head instead, the response would be nil. We'll return to the question of what nil means here in a minute (but notice that it is not a valid Ring respose!).
As is apparent from this example, example-route is just a function, and a very simple one at that; it looks at the request, determines whether it's interested in handling it (by examining :request-method and :uri) and, if so, returns a basic response map.
What is also apparent is that the body of the route does not really need to evaluate to a proper response map; Compojure provides sane default handling for strings (as seen above) and a number of other object types; see the compojure.response/render multimethod for details (the code is entirely self-documenting here).
Let's try using defroutes now:
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
The responses to the example request displayed above and to its variant with :request-method :head are like expected.
The inner workings of example-routes are such that each route is tried in turn; as soon as one of them returns a non-nil response, that response becomes the return value of the whole example-routes handler. As an added convenience, defroutes-defined handlers are wrapped in wrap-params and wrap-cookies implicitly.
Here's an example of a more complex route:
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
Note the destructuring form in place of the previously used empty vector. The basic idea here is that the body of the route might be interested in some information about the request; since this always arrives in the form of a map, an associative destructuring form can be supplied to extract information from the request and bind it to local variables which will be in scope in the route's body.
A test of the above:
user> (echo-typed-url-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/foo/bar"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "http://127.0.0.1:80/foo/bar"}
The brilliant follow-up idea to the above is that more complex routes may assoc extra information onto the request at the matching stage:
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
This responds with a :body of "foo" to the request from the previous example.
Two things are new about this latest example: the "/:fst/*" and the non-empty binding vector [fst]. The first is the aforementioned Rails-and-Sinatra-like syntax for URI patterns. It's a bit more sophisticated than what is apparent from the example above in that regex constraints on URI segments are supported (e.g. ["/:fst/*" :fst #"[0-9]+"] can be supplied to make the route accept only all-digit values of :fst in the above). The second is a simplified way of matching on the :params entry in the request map, which is itself a map; it's useful for extracting URI segments from the request, query string parameters and form parameters. An example to illustrate the latter point:
(defroutes echo-params
(GET "/" [& more]
(str more)))
user> (echo-params
{:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:query-string "foo=1"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "{\"foo\" \"1\"}"}
This would be a good time to have a look at the example from the question text:
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
Let's analyse each route in turn:
(GET "/" [] (workbench)) -- when dealing with a GET request with :uri "/", call the function workbench and render whatever it returns into a response map. (Recall that the return value might be a map, but also a string etc.)
(POST "/save" {form-params :form-params} (str form-params)) -- :form-params is an entry in the request map provided by the wrap-params middleware (recall that it is implicitly included by defroutes). The response will be the standard {:status 200 :headers {"Content-Type" "text/html"} :body ...} with (str form-params) substituted for .... (A slightly unusual POST handler, this...)
(GET "/test" [& more] (str "<pre> more "</pre>")) -- this would e.g. echo back the string representation of the map {"foo" "1"} if the user agent asked for "/test?foo=1".
(GET ["/:filename" :filename #".*"] [filename] ...) -- the :filename #".*" part does nothing at all (since #".*" always matches). It calls the Ring utility function ring.util.response/file-response to produce its response; the {:root "./static"} part tells it where to look for the file.
(ANY "*" [] ...) -- a catch-all route. It is good Compojure practice always to include such a route at the end of a defroutes form to ensure that the handler being defined always returns a valid Ring response map (recall that a route matching failure results in nil).
Why this way?
One purpose of the Ring middleware is to add information to the request map; thus cookie-handling middleware adds a :cookies key to the request, wrap-params adds :query-params and/or :form-params if a query string / form data is present and so on. (Strictly speaking, all the information the middleware functions are adding must be already present in the request map, since that is what they get passed; their job is to transform it to be it more convenient to work with in the handlers they wrap.) Ultimately the "enriched" request is passed to the base handler, which examines the request map with all the nicely preprocessed information added by the middleware and produces a response. (Middleware can do more complex things than that -- like wrapping several "inner" handlers and choosing between them, deciding whether to call the wrapped handler(s) at all etc. That is, however, outside the scope of this answer.)
The base handler, in turn, is usually (in non-trivial cases) a function which tends to need just a handful of items of information about the request. (E.g. ring.util.response/file-response doesn't care about most of the request; it only needs a filename.) Hence the need for a simple way of extracting just the relevant parts of a Ring request. Compojure aims to provide a special-purpose pattern matching engine, as it were, which does just that.
There is an excellent article at booleanknot.com from James Reeves (author of Compojure), and reading it made it "click" for me, so I have retranscribed some of it here (really that's all I did).
There is also a slidedeck here from the same author, that answers this exact question.
Compojure is based on Ring, which is an abstraction for http requests.
A concise syntax for generating Ring handlers.
So, what are those Ring handlers ? Extract from the doc :
;; Handlers are functions that define your web application.
;; They take one argument, a map representing a HTTP request,
;; and return a map representing the HTTP response.
;; Let's take a look at an example:
(defn what-is-my-ip [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)})
Pretty simple, but also quite low-level.
The above handler can be defined more concisely using the ring/util library.
(use 'ring.util.response)
(defn handler [request]
(response "Hello World"))
Now we want to call different handlers depending on the request.
We could do some static routing like so :
(defn handler [request]
(or
(if (= (:uri request) "/a") (response "Alpha"))
(if (= (:uri request) "/b") (response "Beta"))))
And refactor it like this :
(defn a-route [request]
(if (= (:uri request) "/a") (response "Alpha")))
(defn b-route [request]
(if (= (:uri request) "/b") (response "Beta"))))
(defn handler [request]
(or (a-route request)
(b-route request)))
The interesting thing that James notes then is that this allows nesting routes, because "the result of combining two or more routes together is itself a route".
(defn ab-routes [request]
(or (a-route request)
(b-route request)))
(defn cd-routes [request]
(or (c-route request)
(d-route request)))
(defn handler [request]
(or (ab-routes request)
(cd-routes request)))
By now, we are beginning to see some code that looks like it could be factored, using a macro. Compojure provides a defroutes macro:
(defroutes ab-routes a-route b-route)
;; is identical to
(def ab-routes (routes a-route b-route))
Compojure provides other macros, like the GET macro:
(GET "/a" [] "Alpha")
;; will expand to
(fn [request#]
(if (and (= (:request-method request#) ~http-method)
(= (:uri request#) ~uri))
(let [~bindings request#]
~#body)))
That last function generated looks like our handler !
Please make sure to check out James post, as it goes into more detailed explanations.
For anybody who still struggled to find out what is going on with the routes, it might be that, like me, you don't understand the idea of destructuring.
Actually reading the docs for let helped clear up the whole "where do the magic values come from?" question.
I'm pasting the relevant sections below:
Clojure supports abstract structural
binding, often called destructuring,
in let binding lists, fn parameter
lists, and any macro that expands into
a let or fn. The basic idea is that a
binding-form can be a data structure
literal containing symbols that get
bound to the respective parts of the
init-expr. The binding is abstract in
that a vector literal can bind to
anything that is sequential, while a
map literal can bind to anything that
is associative.
Vector binding-exprs allow you to bind
names to parts of sequential things
(not just vectors), like vectors,
lists, seqs, strings, arrays, and
anything that supports nth. The basic
sequential form is a vector of
binding-forms, which will be bound to
successive elements from the
init-expr, looked up via nth. In
addition, and optionally, & followed
by a binding-forms will cause that
binding-form to be bound to the
remainder of the sequence, i.e. that
part not yet bound, looked up via
nthnext . Finally, also optional, :as
followed by a symbol will cause that
symbol to be bound to the entire
init-expr:
(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
[a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]
Vector binding-exprs allow you to bind
names to parts of sequential things
(not just vectors), like vectors,
lists, seqs, strings, arrays, and
anything that supports nth. The basic
sequential form is a vector of
binding-forms, which will be bound to
successive elements from the
init-expr, looked up via nth. In
addition, and optionally, & followed
by a binding-forms will cause that
binding-form to be bound to the
remainder of the sequence, i.e. that
part not yet bound, looked up via
nthnext . Finally, also optional, :as
followed by a symbol will cause that
symbol to be bound to the entire
init-expr:
(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
[a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]
I haven't started on clojure web stuff yet but, I will, here's the stuff I bookmarked.
https://docs.google.com/Doc?docid=0AQqGP1CDN0uIZGhmZjJmcGZfMjNjNHIycGZu&hl=en
https://github.com/weavejester/compojure/wiki/Routes-In-Detail
http://mmcgrana.github.com/2010/03/clojure-web-development-ring.html
What's the deal with the destructuring ({form-params :form-params})? What keywords are available for me when destructuring?
The keys available are those that are in the input map. Destructuring is available inside let and doseq forms, or inside the parameters to fn or defn
The following code will hopefully be informative:
(let [{a :thing-a
c :thing-c :as things} {:thing-a 0
:thing-b 1
:thing-c 2}]
[a c (keys things)])
=> [0 2 (:thing-b :thing-a :thing-c)]
a more advanced example, showing nested destructuring:
user> (let [{thing-id :id
{thing-color :color :as props} :properties} {:id 1
:properties {:shape
"square"
:color
0xffffff}}]
[thing-id thing-color (keys props)])
=> [1 16777215 (:color :shape)]
When used wisely, destructuring declutters your code by avoiding boilerplate data access. by using :as and printing the result (or the result's keys) you can get a better idea of what other data you could access.