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

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to