On Language Design: Magic Variables in Compojure

The Perl language is riddled with special variables. Consider the following example:

open(FILE, “bla.txt”);

while(<FILE>) { print; }

In case you don’t speak Perl, this is equivalent to:

open(FILE, “bla.txt”);

while(<FILE>) { print $_; }

Still unclear? Alright, once more:

open(FILE, “bla.txt”);

while($line = <FILE>) { print $line; }

Perl is developed by linguist Larry Wall, who likes to put all kinds of natural language things into Perl. $_ refers to the subject of the sentence, it’s “it” as it were. print $_, or simply print means “print it”. How do we know this variable exists? We don’t. Unless we read the manual. Although they might be useful, magic variables like these are generally bad practice, it’s one of many reasons that Perl code is often called write only code.

PHP also has some magic variables whose behavior do not adhere to any usual rules, they are called super globals. In particular there are $_POST and $_GET. They do not adhere to the scoping rules defined in PHP.

I don’t know about you, but I like to see my variables declared. I like to see where they come from, are they local variables, parameters, or imported from a module that I can lookup? Magic variables are confusing, because they have different behavior than other variables in a language, they do not adhere to the usual rules.

As part of Adia, I played around with Compojure, a Clojure web framework. Generally, Compojure is a well-designed framework, but I came across one instance where it uses magic variables: in the defroutes macro. An example:

(defroutes webservice

 (GET “/”

 (str “Hello, “

 (:name (:query-params request)))))

This defines a mapping from the “/” URI, to code to be executed when that URI is requested. As you can see, I use the variable request in this example. Upon inspection, it not clear where it comes from. At first, you may assume it’s a global that is dynamically bound (Clojure has dynamically scoped variables). But some refactoring of the code shows that this is not the case:

(defn say-hello []

 (str “Hello, “ (:name (:query-params request))))

(defroutes webservice

 (GET “/”

 (say-hello)))

This results in a “symbol cannot be resolved” error for the request variable. So clearly, request is not a dynamically bound variable either. So what is it, and where does it come from? I had to dig into the Compojure source code to find it, it is defined in the with-request-bindings macro, which defines some magic symbols: params, cookies, session and flash that are only accessible from inside the macro’s body:

(defmacro with-request-bindings

 “Add shortcut bindings for the keys in a request map.”

 [request & body]

 `(let [~’request ~request

 ~’params (:params ~’request)

 ~’cookies (:cookies ~’request)

 ~’session (:session ~’request)

 ~’flash (:flash ~’request)]

 ~@body))

I find this confusing and therefore bad practice. So, what is the alternative? It turns out there’s no perfect solution here, it’s all about trade-offs.

The problem magic variables try to solve is enabling quick access to data that is otherwise not accessible in a concise manner. In this case it could have been solved by declaring the request and other variables somewhere, for instance as parameters to defroutes:

(defroutes webservice [request params cookies session flash]

 …)

A reason not to go with this solution is likely to be its verbosity, nobody likes to write functions with a large number of arguments. A second alternative is only passing the request parameter and letting the user pull out the other from that map every time they need it. A reason not to go with that solution is user inconvenience, users likely want quick access to all five of these values and don’t want to look them up in the request map every time. A third alternative is using dynamically bound global variables. In Clojure these can be bound to a new value only for a certain thread within a certain code execution path:

(def request nil) ; root binding is nil

(defn print-request []

 (println request))

(binding [request (build-request …)] ; rebind

 (print-request) ; prints request

 …))

I use them in Adia for access to the request, post and get parameters. For this I declare 3 variables in the adia.web module:

(def *request* nil)

(def *form* nil)

(def *query* nil)

Values have been dynamically bound to their respective values by the time they are used within a webfn:

(defwebfn say-hello []

 (str “Hello, “ (:name *query*)))

My thinking is that by using the *name* convention, it is at least clear these are not normal locally defined variables, they are different. Secondly, they are only available if you import the adia.web module and are part of that module’s interface, i.e. you can look up their declaration. What is still not clear is where their value comes from, but I suppose the user will have to abstract from that. The alternative to this approach would be to pass these variables as parameters to every webfn, but I decided against this because it would make writing webfn defintions too verbose. It was also not very clear what the syntax should have been: (defwebfn fn-name request form query [arg1 type arg2 type] …)? (defwebfn fn-name [request form query] [arg1 type arg2] …)? Neither of these options seemed very elegant to me, which is why I decided to be pragmatic and go with dynamically scoped globals.

I think this is an acceptable compromise. While the five globals are essentially still input parameters, they do not have to be passed around all the time. However, if you invoke a webfn outside a path where values are bound to the *request*, *form* and *query* variables, you are likely to receive NullPointerExceptions.

As a general rule: to keep programs readable and comprehensible, it should be easy to see where your variables and symbols come from. Are they local variables, parameters, globals? Magic variables should be avoided if possible.