Hi, Am 18.05.2009 um 18:56 schrieb Mark Engelberg:
OK, so let's talk more about what this glue code should look like. I've been lumping this problem together with the other, because in my mind they are related, but perhaps there is a clean way to glue together modules without the incremental extension technique. Let's say I've got a namespace for my generate functions, a namespace for my solve functions, a namespace for my analysis functions, and a namespace for my filter functions. Now, the generator uses the solve functions, e.g., generator/generate-puzzle will somewhere call solve/solve-puzzle So presumably, at the top of generator.clj, I have (ns generator (:require solve)) Similarly, generator also has dependencies on functions in analysis and filter namespaces. Furthermore, some of the filter functions have dependencies on the analysis functions, and so on. In other words, assume that most of the modules have dependencies on each other. However, let's say my solve-puzzle function isn't working as effectively as I'd like. It solves the puzzle in a brute force way, and I realize I'd like to try another version that solves the puzzle more like the way a human would solve it. So, I want to swap it out with a new solve module, i.e., a new namespace, and have all the other namespaces point to it. Maybe even make it easy to swap back and forth at runtime so I can compare the two approaches. So how to do this? As far as I can tell, these various modules are all "hard-linked" to point at one another, and I don't see how to make this linkage more dynamic. To change one file, I'd have to create new versions of ALL the files. It would be great if each file could store a variable for what namespace it needs to use to get at the other functions. For example, the generator has a variable that points at the solver namespace, and accesses the solver functions through that variable. Then, you could have one master file that loads the particular versions of the modules you want, and links them to one another. But I really don't see how such glue could be written. Any suggestions? On the other hand, it's pretty straightforward to achieve this when you use "objects" for your modules. To be clear, I'm *not* a fan of using OO to create bundled data structures with elaborate chains of inheritance, but what I'm starting to realize is that in OO, you get a pretty decent "module" system for free, using the same concepts that OO programmers are already familiar with. In the absence of OO, the details of the module system become much more important because modularity becomes an orthogonal issue that is not really covered by the other aspects of the language (thus all the research papers on the topic of modules in functional programming languages).
I'm still not sure I understand all your problems, but please find below some thought on how to decouple the modules. (ns puzzle.generator (:use [clojure.contrib.def :only (defvar)])) ; Various parameters (defvar *gravity* 1.0 "The default gravity of Earth: 1.0") (defn puzzle-seq ([] (puzzle-seq *gravity*)) ([gravity] (repeatedly #(generate-puzzle gravity)))) (ns puzzle.analyzer) (defn make-standard-analyzer [] (fn [x y z] (judge-difficulty-on-parameter x y z))) (defn make-other-analyzer [] (fn [x y z] (do-other-difficulty-judgement x y z))) (ns puzzle.solver (:use [puzzle.analyzer :only (make-standard-analyzer)])) (defn brute-force-strategy [puzzle analyzer] ...) (defn smart-human-strategy [puzzle analyzer] ...) (defn solve [puzzle & options] (let [{:keys [analyzer strategy] :or {analyzer (make-standard-analyzer) strategy brute-force-strategy}} (apply hash-map options)] (let [difficulty (strategy puzzle analyzer)] [difficulty puzzle]))) (ns puzzle.glue (:require (puzzle [generator :as generator] [solver :as solver]))) (defn create-puzzles [difficulties] (for [[d p] (map solver/solve (generator/puzzle-seq)) :when (some difficulties d)] p)) (ns puzzle.glue.variationX (:require (puzzle [generator :as generator] [analyzer :as analyzer] [solver :as solver]))) ; Try different strategy with other analysis method. (defn experimental-strategy [puzzle analyzer] ...) (defn create-puzzles [difficulties] (for [[d p] (map (fn [puzzle](solver/solve puzzle :strategy experimental- strategy :analyzer (analyzer/make-other- analyzer)))
(generator/create-puzzles 0.75)) :when (some difficulties d)] p)) I think the idea of decoupling is called "inversion of control" or "dependency injection". I'm sure it works for you, but it sure did in this (admittedly) simple example. There are various ways at passing around parameter: - global with binding - multi-arity functions - keyword functions Using some structure with multimethods would also be possible. (ns puzzle.solver) (defmulti solve (fn [solver _] (type solver))) (ns puzzle.solver.brute-force (:use puzzle.solver)) (let [type-info {:type ::BruteForceSolver}] (defn make-solver [param1 param2] (with-meta {:param1 param1 :param2 param2} type-info))) (defmethod solve ::BruteForceSolver [solver puzzle] (let [{:keys [param1 param2]} solve] ...)) Similar for puzzle.solver.human-solver. I think you get the idea.... Maybe this gives you some ideas on how to modularise your setup. The various ways worked for me in the past... Sincerely Meikel
smime.p7s
Description: S/MIME cryptographic signature