clojurecompojurecompojure-api

why are compojure routes defined as macros?


for example the Luminus website states that

Compojure route definitions are just functions that accept request maps and return response maps...

(GET "/" [] "Show something")
...

But compojure routes are not functions

(defmacro GET "Generate a `GET` route."
  [path args & body]
  (compile-route :get path args body))

One can use the function make-route that returns a functions, but does not allow for destructuring. So as a function you can not use compojure's special syntax for destructing (i.e the vector) but does this stop any form of destructuring? Does the macro from given them a performance increase?

(make-route :get "/some_path" some_handler)

Couldn't the destructing syntax be passed to the function using a macro wrapper?


Solution

  • One reason macros are used is so the user can write function and symbol names without having to quote everything. Take this example from the Clojure Cookbook:

    ; Routing
    (defroutes main-routes
      (GET "/"    [] (index))
      (GET "/en/" [] (index))
      (GET "/fr/" [] (index-fr))
      (GET "/:greeting/" [greeting] (view greeting)))
    

    All of the index* symbols, plus view and greeting would have to be quoted if GET were a function:

    ; Routing
    (defroutes main-routes
      (GET "/"    [] '(index))
      (GET "/en/" [] '(index))
      (GET "/fr/" [] '(index-fr))
      (GET "/:greeting/" '[greeting] '(view greeting)))
    

    Since function arguments are evaluated before the function is invoked, (index) et al would be evaluated as soon as the form was read. Also, the greeting arg will be changing on each HTTP request, so it obviously is not known ahead of time.

    The macro also takes care of all the destructuring magic which is not (normally) possible with a regular function.

    The thing which is often confusing (and is not well explained to beginners) is that a line like:

    (GET "/:greeting/" [greeting] (view greeting))
    

    is not normal "Clojure code". Instead, it is a type of shorthand (Domain-Specific Language or DSL to be precise) that the GET macro will ingest and use as instructions on how to generate "legal" Clojure code. The DSL is normally much shorter, simpler, & more convenient for a human than the final generated code, just as Clojure is much shorter, simpler, & more convenient than the Java Bytecode produced by the Clojure compiler, or the machine assembly language code eventually produced by the JVM.

    In short, each macro is a "pre-compiler" that turns the DSL into plain Clojure, which is then ingested by the Clojure compiler to generate Java bytecode.

    Having said that, it could be re-arranged to put all of the macro magic into the defroutes macro so that the GET symbol was neither a function nor a macro, but just a type of marker like the :get keyword in the implementation. As a user, these kinds of implementation details normally don't matter much.

    Update

    It is best to use macros only when a function won't work or is very awkward. The deciding factor is usually if one wants to use bare (unquoted) symbols, but not evaluate them in advance. Core Clojure itself uses macros for many constructs that are "built-ins" in other languages, including defn, for, and, or, when, and others.

    Also note that a macro cannot do some things a function can, such as being a parameter to a higher-order function like filter.

    In summary, a function defines a behavior. A macro defines a language extension.