On Fri, Mar 25, 2011 at 6:00 PM, Stuart Sierra <the.stuart.sie...@gmail.com> wrote: > I try to use namespaces in layers. Each namespace defines an "interface" as > a set of functions, and only calls functions of the layer(s) "below" it. I > build from small utilities and Java interop at the "bottom" layer, to > application-level logic in the "middle" layers, with the external API in the > "top" layer. > > This helps with the one hard-and-fast rule of Clojure namespaces: no > circular dependencies. If namespace A depends on namespace B, then > namespace B cannot depend on namespace A.
Unless you use dependency injection tricks. E.g.: (ns foo) (def somemap (atom {})) (defn foo-inject [k v] (swap! somemap assoc k v)) (defn foo-dosomething [a b c] (let [d ((@somemap :injected-fn) a)] ...)) ... (ns bar) (defn some-fn [a] ... ... depends indirectly on foo-dosomething, ... but avoids unbounded recursion if called ... by foo-dosomething ...) (defn bar-init [] (foo/foo-inject :injected-fn some-fn) ...) As long as bar-init is called before anything that calls foo-dosomething/bar-init/etc., and the some-fn/foo-dosomething recursion remains bounded, this is OK. I had this come up recently in connection with a fairly complex system that had to bootstrap itself. There was a low-level DAL that had to fall back on certain in-memory defaults, which had to hold references to functions that operated at a much higher level, eventually calling a lookup function in some cases that depended in turn on the low-level DAL. Cramming the DAL, the lookup engine, and some higher level abstractions into a single namespace seemed excessive, and I wanted these as three separate layers, but then the lowest of these layers needed maps containing objects containing functions defined in the highest. To be exact, the lookup engine knows natively how to search data of type X. But the system is extensible to be able to search data of types Y, Z, etc. with the added search know-how being itself supplied as data of type X. So the lookup engine uses the type of search to first look up the appropriate lookup engine, if the type isn't X. This leads to the lookup engine calling the DAL, which in turn needs to be able to supply the "how to search for stuff" data of type X as soon as the system has started up, which data needs to contain information on searching for data of type Y as that's built in and not user-defined and needed at a higher level of the system, and that information contains search functions themselves, which punt to the lookup engine ... The only alternative to the in-memory bootstrap procedure noted above would be to rely on certain disk files being created ahead of the first execution of the program on a new system. With the bootstrap procedure, the system can easily generate the disk files if they're absent, can usually recover if they get scrogged (and certain files can be deleted to make it recover otherwise), and can eschew wasting disk space storing "what it already knows", including only user-added information in disk files. For managing circular dependencies in general, the only alternative to using a mutable of some sort to inject them is to defer their resolution to runtime. Example 1: (ns foo) (defn foo-dosomething [a b c] (let [d ((@(ns-resolve 'bar 'some-fn)) a)] ...)) ... (ns bar) (defn some-fn [a] ... ... depends indirectly on foo-dosomething, ... but avoids unbounded recursion if called ... by foo-dosomething ...) This variation just finds some-fn in namespace bar at runtime, so ns foo will compile without ns bar, ns bar can then be compiled, and then at runtime everything works. If things are slowed too much at runtime in foo-dosomething, the resolution can be cached: (ns foo) (def some-fn (atom nil)) (defn foo-dosomething [a b c] (let [f @some-fn f (if f f (reset! some-fn @(ns-resolve 'bar 'some-fn))) d (f a)] ...)) or you can define and use a memoized function that wraps @(ns-resolve x y). If there are going to be many of these calls in your codebase you will want to sugar things up: (defn lookup [sym] @(ns-resolve (symbol (namespace sym)) (symbol (name sym)))) (def lookup (memoize lookup)) (defmacro use [sym & args] `((lookup (quote ~sym)) ~@args)) ... (ns foo) (defn foo-dosomething [a b c] (let [d (use bar/some-fn a)] ...)) This is nearly as nice as apply, though you have to fully qualify the symbol bar/some-fn. The downside is, no compile time checking for typos etc.; if there's no bar/some-fn at runtime you'll get an NPE thrown out of lookup when foo-dosomething is called, as ns-lookup will return nil and (deref nil), well ... -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en