views:

102

answers:

3

I've noticed that some libraries such as clojure-twitter use special vars (the ones intended for dynamic binding that are surrounded by asterisks) for oauth authentication. You save your authentication in a var and then use (with-oauth myauth ..). I think this is a very nice solution to this sort of problem, because you can rebind the auth var for each user of the application.

I've taken a similar route in an email client I've been writing. I have a special var named session that I bind to a map with the current user's session, and user info, and there are various important functions that use information from that var. I wrote a macro, with-session to temporarily rebind it in the context of a set of forms passed to with-session. It turns out to be a pretty clean solution (to me).

So, my question is this: am I 'doin' it rite'? Is this a bad design decision, or is this one of the intended usages of special vars?

+6  A: 

You seem to be doing it exactly right. In fact, there's a number of built-in / contrib macros which work similarly, say with-out-str or clojure.contrib.sql/with-connection. The latter is a rather key part of present day Clojure infrastructure, so whatever idioms it uses have been scrutinised by a lot of people.

The important gotcha to keep in mind is that threads you launch while in scope of a bindings / with-bindings form do not inherit the rebound values for the vars in question; rather, they see the root bindings. If you want to propagate your bindings to worker threads / agents, either pass them on explicitly (as function arguments, say) or use bound-fn.

Michał Marczyk
Thank you for the answer. Very informative. I'll keep that in mind. :)
Rayne
You're welcome, of course, although I see that Brian came along and stirred things up for you... Actually I'm back to post a link to this discussion I suddenly remembered: http://groups.google.com/group/clojure/browse_thread/thread/a7d020f8c76ecb77/dbd4f016fbb5f2e4 -- the title of the thread is "Sessions for Ring". Ring works server-side, of course, but I think the discussion there may still help you. The opinions seem to be split between storing session data in atoms (one per session) and functional purity all around... Good arguments from both sides, take your pick.
Michał Marczyk
Oh, also, even the version with atoms puts them in a session which is passed around explicitly, but Ring sessions are relevant to a large portion of Ring's code *and* there's likely to be a multitude of them being passed around all the time. With an e-mail client, on the other hand, you're likely to be able to put all relevant information in just one data structure (possibly containing login credentials etc. for multiple servers -- but all of them belonging to the same human being) which will *always* be there. That makes it, in my eyes, a somewhat different beast from a webapp... Still, YMMV.
Michał Marczyk
+2  A: 

Every time you make a global var that you plan to re-bind, you're adding an extra implicit argument to every function that accesses that variable. Unlike proper (explicit) arguments, this hidden argument doesn't show up in the functions's signature and there may be little indication that the function is using it. Your code become less "functional"; calling the same function with the same arguments may result in different return values based on the current state of these global dynamic vars.

The benefit of global vars is that you can easily specify a default value, and it lets you be lazy by not having to pass that var around to every function that uses it.

The downside is that your code is harder to read, test, use and debug. And your code becomes potentially more error-prone; it's easy to forget to bind or re-bind the var before you call the function that uses it, but it's not so easy to forget to pass in a session parameter when it's right in the arglist.

So you end up with mystery bugs, and weird implicit dependencies between functions. Consider this scenario:

user> (defn foo [] (when-not (:logged-in *session*) (throw (Exception. "Access denied!"))))
#'user/foo
user> (defn bar [] (foo))
#'user/bar
user> (defn quux [] (bar))
#'user/quux
user> (quux)
; Evaluation aborted.  ;; Access denied!

The behavior of quux depends implicitly on the session having a value, but you wouldn't know that unless you dug down through every function quux calls, and every function those functions call. Imagine a call chain 10 or 20 levels deep, with one function at the bottom depending on *session*. Have fun debugging that.

If instead you had (defn foo [session] ...), (defn bar [session] ...), (defn quux [session] ...), it would be immediately obvious to you that if you call quux, you'd better have a session ready.

Personally, I would use explicit arguments unless I had a strong, sane default value that tons of functions used, that I planned to very rarely or never rebind. (e.g. it would be silly to pass STDOUT around as an explicit argument to every function that wants to print anything.)

Brian Carper
Well, I guess that means I'll be redesigning my entire application. I just love it when I get two very confident, but completely incompatible answers. :\Thanks for the answer.
Rayne
I've been there... I had a webapp with global session/params/HTTP headers/cookies vars that were dynamically bound on every request. When I rewrote it all to take explicit params, everything was better. e.g. most fns don't need the WHOLE session, they only need one or two bits of it. You can pass just those bits as arguments. The amount of argument data being thrown around was greatly lessened in my case. The code is slightly more verbose, but much easier to understand. You can use macros if it's really too much typing.
Brian Carper
I think that a session as seen in a webapp might be significantly different from a session as seen in an e-mail client. For one thing, I'd imagine that an e-mail client would mostly operate on local storage (maybe a local CouchDB / RDBMS connection) and only make connections when there's stuff to be sent sitting on some queue or some time has passed since the last request for new mail from (one of) the server(s). No reason why the login credentials couldn't be provided with `binding` to a `bound-fn` if that's the case... Anyway, the fundamental design issue is I think not whether thread-local
Michał Marczyk
bindings are used, but rather whether the functionality is clearly separated into a library of pure functions performing transformations on data and the impure 'glue', which should IMO be free to use any state container that feels right (makes for clean design etc.). Perhaps a combination of Refs to hold the actual e-mails etc. and dynamic Vars for things like with-smtp-connection... I wouldn't want to be perceived as a detractor from purely functional style, though -- the smaller the number of impure operations and the more localised their impact on the code the better. No doubt about that.
Michał Marczyk
Anyway, see that link I posted as a comment on my answer for some additional thoughts on related matters... @Rayne: Good luck with the redesign if you do pursue it, which may very well be a good idea. Brian's arguments are sound and I'll definately remember them the next time I think of writing a `with-session` macro...
Michał Marczyk
@michal-marczyk I believe I'll use FleetDB as a database. I appreciate all the comments. The redesign didn't take as much as I thought it was. I created a new deftype to hold important information for each user. My 'session' is more than just a JavaMail session, it's user credentials, the session, and something else that I can't remember at the moment (that's bad, isn't it? ;p). Anyways, it was just a matter of changing everything to take the session as an explicit argument, and everything else pretty much just worked itself out.
Rayne
+1  A: 
Arthur Ulfeldt