I understand where your pain is coming from, but I think the cure is worse than the disease. Your currying approach introduces "argument noise" that is prevalent amongst dependency injection techniques. Namely, a change in the dependencies of one low-level method percolates all the way to the very top of your application. So in your example, every bit of shared configuration bubbles to the very top of your application like so:
(defn create-bid [db]
(fn [request]
(transact db tx-data)
(defn build-routes [db]
(defroutes routes
(POST "/bids" [] (actions/create-bid db))
(resources "/")))
(defn create-handler [db]
(build-routes db)
(-> (var routes)
(keyword-params/wrap-keyword-params)
(nested-params/wrap-nested-params)
(params/wrap-params)))
This is fine for an application whose actions only talk to the DB. What about when you introduce Redis? RabbitMQ? Log4J? I suppose you could pass along a map of keys to shared global state, but now you have the same drawbacks as vars (every method could interact with every bit of global state) without the advantages. I don't see how your currying approach improves things, but perhaps I am missing something.
I think an alternative approach that uses with-redefs
provides those same benefits without the pain. You can define defaults for a particular shared resource or value (even if that default is nil
) and then override that in your particular context. (See Chas Emerick's post at http://cemerick.com/2011/10/17/a-la-carte-configuration-in-clojure-apis/ for a good discussion of the two techniques).
It's probably inaccurate to think of Clojure as "post-global-state." It provides mechanisms for intelligently managing global state. I think it's important to apply those mechanisms with pragmatism.
Explicit dependencies tend to expose pain that's worth having exposed, IMHO. It's hard(er) to build a BBOM when it becomes annoying to have things depend on too much.