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