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.
Tags: clojure, Language Design
While I do agree with your principles, there is more than one way to view the disappearing variables issue. Having them only available from directly within the handler forces the developer to explicitly pass only the data necessary in generating a response to further function calls. This data can include the request itself, but typically it would just be the parameters and session values of note. This way the intent of functions remains clear and there are no unintentional dependencies on variables that are context dependent. I think Rich called the danger here “action from afar” in reference to dynamic thread local bindings.
That being said, I hate magic local variables that appear from nowhere just as much as you do :)
The “magic” local bindings in Compojure are also a feature I don't particularly like; and I wrote that piece of code. It was the least worst solution I could think of at the time.
The reason I used local bindings, rather than thread-local vars, is because I didn't want the request map to be passed implicitly to the controller functions, like so:
(defn do-something []
(str “Hello ” (params :name)))
By using local bindings, I ensure that people need to explicitly choose which parts of the request they want to use. I'm of the opinion that by removing unnecessary information early, you limit the amount of things that can go wrong.
But I've never been happy with using magic variables in this way. So in the “refactor” branch of the Compojure repository I'm trying a different approach. I'm adding an extra argument to the route macros that determines the binding. Clojure's binding syntax is quite sophisticated, so I can do something like:
(GET “/book/:isbn” {{:keys [isbn]} :params}
(get-book isbn))
So now the variables are all explicitly bound. However, it's still rather verbose, especially since 99% of the time, you'll likely only want to access the parameters. So I've added some syntax sugar for handling parameters:
(GET “/book/:isbn” [isbn]
(get-book isbn))
If a vector binding is passed instead, Compojure treats it as a binding of parameters. I think this should cover the vast majority of cases. Things like sessions I'm thinking should be handled by global vars bound in middleware.
I'd be interested in your opinion on this syntax. Can you pick any holes in it?
OT: You can use to indent code. bleh…. Disqus should do something about that…
Yes, I am aware of this, but think it's a trade off. Either keep passing loads of values around to functions (because at some point, some function may need that value), or solve it with dynamically bound variables. Neither is perfect.
I agree that limiting the availability of the
requestvalue within the handler can be good, but it should not appear out of nowhere, it should be declared as part of the syntax, like weavejester is proposing now.That looks a lot nicer, however the :params there should actually be just params, am I right?