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

Reply via email to