I'm trying to make my code extensible and I'm having trouble deciding
what is the best way to do this.

The algorithm is a tree walker that modifies a few data structures
before, post, and during the traversal of each node. The code detects
circular dependencies, resolves dependencies once, and ignores
exclusions. However, there are different ways to determine what
qualifies as an exclusion and an already resolved dependency such as
matching only the artifact id to prevent multiple versions, which
motivates me to make the code extensible. After all, there are
additional possibilities which may need to be accounted for in the
future.

The code is the following:

note:
a dependency takes the form [groupid/artifactid "1.3.0-version"]
get-direct-dependencies-parse! downloads and parses a pom.xml file
extract-dependencies converts a dependency in parse data into the form
[groupid/artifactid "1.3.0-version"]
qualify-dependency ensures a dependency-form is well-formed
extract-exclusions does the same as extract-dependencies but gets
exclusions instead

(defn get-all-dependencies! [dependency-list & options]
  ([dependency-list exclusions]
     (let [ ;; circular dependency detection
           seen (ref #{})
           ;; needed to preserve order
           resolved (ref [])
           resolved-set (ref #{})
           exclusions (ref (set exclusions))
           ;; may want to do something with this in the future
           optional (ref #{})]
       (letfn [(get-direct-dependencies!
                ;; create list of direct dependencies and update exclusions
                [d]
                (for [{dependency :dependency} (get-direct-dependencies-parse! 
d)]
                  (let [dependency-map (apply merge dependency)
                        dependency-form (extract-dependency dependency-map)]
                    (dosync (alter exclusions set/union (extract-exclusions
dependency-map)))
                    (when (and (:verbose options)
                               (:optional dependency-map))
                      (println "resolving optional " dependency-form))
                    (dosync (alter optional conj dependency-form))
                    dependency-form)))
               (resolve-dependency
                ;; walk tree
                [d]
                (let [d (qualify-dependency d)]
                  (when (:verbose options) (println "resolving " d))
                  (when-not (or ((:resolved-fn options) @resolved-set d)
                                (let [result ((:exclusion-fn options) 
@exclusions d)]
                                  (when (and result
                                             (:verbose options))
                                    (println "excluding " d))
                                  result))
                    (if (@seen d)
                      (throw (Exception. "Circular dependency detected"))
                      (do
                        (dosync (alter seen conj d))
                        (doall (map resolve-dependency 
(get-direct-dependencies! d)))
                        (dosync
                         (alter seen disj d)
                         (alter resolved conj d)
                         (alter resolved-set conj d)))))))]
         (doseq [dependency dependency-list]
           (resolve-dependency dependency))
         @resolved)))
  ([dependency-list]
     (get-all-dependencies! dependency-list nil)))

I've started making it more general with the options map which can
potentially contain different predicate functions for determining
whether or not to follow a node, but I'm trying to make it even more
general. I feel that I can break it down into code that initializing
data structures, predicates that determine whether to recurse into a
node, code to execute before recurse into node, code to execute during
traversal (aka code in get-direct-dependencies!) and code to execute
after recurse.

I've been thinking about multimethods but they execute one function
out of many possible. I want to execute many functions over many
possible.

I've also thought about making hook maps, maps that contain several
hook functions such as :init, :predicate, :pre, :get, and :post) and
at each stage in the get-all-dependencies! function, dynamically
creates a list of hooks  from a list of all the hooks, that fulfill a
condition (in other words, filters the hooks), based on runtime
information, and then executes them. I've started this in an
experimental branch but it doesn't look pretty and appears (just from
the code) to not be particularly efficient. Speed may or may not be a
factor, but I'd assume there are ways to dynamically generate an
optimized function (which doesn't do as many map lookups during
runtime) once per call of get-all-dependencies assuming parameters
don't change during the execution of the algorithm, however, the
implementation does not appear to be trivial.

How should I make this code more extensible?

Best,
Brent

-- 
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