Symbols, vars, and namespaces

37 views
Skip to first unread message

Sean Bowman

unread,
Feb 11, 2012, 5:11:46 AM2/11/12
to Clojure
I've been working with Compojure and Ring lately to build an app
server, and I've gotten my brain stuck trying to figure out wrap-
reload. It seems like I have to turn my routes into vars to get wrap-
reload to work, but I don't understand why, and I suppose I don't
really understand the "when" or "where" of the situation either.

For example, if I have a bunch of resource namespaces with
(defroutes ...) calls in them, and a main "core" namespace that
aggregates them all, wrap-reload will work in a scenario like this:

(defroutes combined
#'myapp.resource.users
#'myapp.resource.products
#'myapp.resource.other
(route/resources "/")
(route/not-found "Page not found"))

In the above, changing code in any of the resource namespaces auto-
reloads them.

But if I put two defroutes in the same namespace and try to "var" one
of them, wrap-reload fails:

(defroutes defaults
(route/resources "/")
(route/not-found "Page not found"))

(defroutes all-routes
myapp.resource.users
myapp.resource.products
myapp.resource.other)

(defroutes combined
#'all-routes
defaults)

If I pass combined to (handler/site ...) in Compojure, wrap-reload
doesn't work.

And, if I take the original, and remove the vars, then try to call
handler/site and var that route, it doesn't work either:

(defroutes combined
myapp.resource.users
myapp.resource.products
myapp.resource.other
(route/resources "/")
(route/not-found "Page not found"))

(def app (handler/site #'combined))

What am I not understanding here? I feel like this is the area I
struggle with most, and I'm not getting it at all.

P.S. Please don't point me at lein-ring and have me just run that.
I'm trying to finally understand this, and I also need to be able to
access the REPL in the same classloader as the running server for
other reasons.

Alan Malloy

unread,
Feb 11, 2012, 5:59:25 AM2/11/12
to Clojure
In this case the var is simply acting as a mutable pointer, so that
when the implementation is changed the route reflects the new value.
Here's a simple example of that behavior in action, divorced from
webservers and the like:

;; caller accepts a function and returns a new one that forwards to it
repl-1=> (defn caller [f] (fn [x] (f x)))
;; initially we use identity
repl-1=> (defn callee [x] x)

;; the first form passes the current value of callee; the second
passes the "pointer"
repl-1=> (def passed-value (caller callee))
repl-1=> (def passed-var (caller #'callee))

;; if we redefine callee, only the pointer-version sees the new value
repl-1=> (defn callee [x] (* 2 x))
repl-1=> (passed-value 4)
4
repl-1=> (passed-var 4)
8

Sean Bowman

unread,
Feb 11, 2012, 3:37:37 PM2/11/12
to Clojure
Using your example--very helpful, BTW--I simplified it a bit more:

user=> (def x 10)
#'user/x
user=> (def y #'x)
#'user/y
user=> y
#'user/x
user=> @y
10
user=> (def x (fn [me] (println "Welcome" me)))
#'user/x
user=> (y "hello")
Welcome hello
nil
user=> (@y "yikes")
Welcome yikes
nil

What I come away with from this is a better understanding that when I
refer to "y" in the above code, it's going to return a symbol. When I
place this value in the first slot of a list, the symbol will act in
its role as a function. Otherwise, if "y" is holding a reference to a
symbol that is a value, like 10, then I need to dereference it to use
it.

user=> (def x 15)
#'user/x
user=> @y
15
user=> (y)
java.lang.ClassCastException: java.lang.Integer cannot be cast to
clojure.lang.IFn (NO_SOURCE_FILE:0)

I believe that ClassCastException makes more sense to me now. Like
you said, pointers. I suppose my brain just doesn't want to accept
pointers in the JVM!
Reply all
Reply to author
Forward
0 new messages