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

Reply via email to