(ns e2-73-74
  (:use clojure.test))
(def *table* (ref {}))
(defn get-binding [table sym] (get (deref *table*) sym))
(defn put-binding [table sym f] (dosync (alter *table* assoc sym f)))
(defn same-variable? [exp v] (and (symbol? exp) (= exp v)))
(defn variable? [exp] (symbol? exp))
(defn operator [exp] (first exp))
(defn operands [exp] (rest exp))
(defn deriv [exp v]
  (cond (number? exp) 0
        (variable? exp) (if (same-variable? exp v) 1 0)
        :else ((get-binding 'deriv (operator exp))
                  (operands exp) v)))
(defn rest-or-one [xs] (if (= (count xs) 1) (first xs) xs))
(defn install-deriv-ops []
  (defn make-sum [a & x] (concat (list '+ a) x))
  (defn make-product [a & x] (concat (list '* a) x))
  (put-binding 'deriv '+ (fn [args v] (make-sum (deriv (first args) v)
                                                (deriv (rest-or-one (rest args)) v))))
  (put-binding 'deriv '* (fn [args v] (make-sum (make-product (deriv (rest-or-one (rest args)) v) (first args))
                                                (make-product (deriv (first args) v) (rest-or-one (rest args)))))))
(deftest test-ops
  (install-deriv-ops)
  (is (= (deriv '(+ x 3) 'x) '(+ 1 0)))
  (is (= (deriv '(* x y) 'x) '(+ (* 0 x) (* 1 y)))))
(put-binding 'deriv '** (fn [args v] nil))

(defn get-binding [sym op] ...)

instead of

(defn get-binding [op sym] ...)

Nothing would change in the differentiation system. We would only change the order of arguments to the get-binding function. I didn't really understand this exercise...

The requirements for this exercise are quite vague:

  1. Each department has its own format for files
  2. Files are sets keyed by the persons name
  3. Keyed records are themselve sets keyed by different types of data (salary/...)

a) In order to make a generic get-record function which will work with any type of file we can use the same principle of data-directed programming with additivity. We will have (a) a registry which will be used by different departments to register their implementations and (b) a protocol which will be implemented and registered by those departments.

(def *file-registry* (ref {}))
(defn get-record [file-type person]
  ((get (get (deref *file-registry*) file-type) 'get-person) person))
(defn file-type-registered? [registry file-type]
  (not (nil? (get (deref registry) file-type))))
(defn register-op [file op]
  (assoc file (first op) (second op)))
(defn register-file [file-type & bindings]
  (do (assert (not (file-type-registered? *file-registry* file-type)))
      (assert (even? (count bindings)))
      (dosync (alter *file-registry* assoc file-type
                     (reduce register-op {} (partition 2 bindings))))))
(defn install-records []
  (defn get-person-a [person] (first (filter #(= (:name %) person) [{:name 'John :salary 1000}])))
  (defn get-person-b [person] (first (filter #(= (:surname %) person) [{:sex 'Male :surname 'Rob :salary 2000}])))
  (register-file 'department-a
    'get-person get-person-a)
  (register-file 'department-b
    'get-person get-person-b))

Returns a sequence of elements matching the given item in the list of given files selected by the given op (operation)

(defn get-in-files
  [files op item]
  (mapcat #(let [v ((get % op) item)]
                 ; I think there is a clojure idiom which lets to turn nils into
                 ; empty seqs...
                 (if (nil? v) [] [v])) files))

Returns a salary for the given person. Searches through all of the departments

(defn get-salary
  [person]
  (first (map #(get % :salary) (get-in-files (vals (deref *file-registry*)) 'get-person person))))

Returns one record matching the person in the given files as persons should be unique even across departments

(defn find-employee-record
  [person files]
  (first (get-in-files files 'get-person person)))
(deftest test-find-employee
  (install-records)
  (is (= (find-employee-record 'John (vals (deref *file-registry*))) (get-record 'department-a 'John)))
  (is (= (get-salary 'John) (:salary (get-record 'department-a 'John)))))

(register-file 'new-department-name 'get-person x ... ...)

and make sure that the person entries have a :salary tag.