If anyone is curious, I ended up learning about and using protocols. It was pretty trivial to convert since it was basically what I was already doing, although I wasn't really using protocols for dispatching but for grouping of functions together with shared data.
On Jun 1, 7:09 pm, Brent Millare <brent.mill...@gmail.com> wrote: > 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