Data directed programming chapter.
This commit is contained in:
		
							
								
								
									
										145
									
								
								sec-2-4-3-data-directed.lisp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								sec-2-4-3-data-directed.lisp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
;; in this chapter we do data directed programming
 | 
			
		||||
;; to solve what is, essentially, the expression problem
 | 
			
		||||
;; or a variation of it anyway.
 | 
			
		||||
;; in common lisp, we do have hash tables, so we'll just use that.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
;; exercise 2.73
 | 
			
		||||
;; these aren't optimised, and they don't eliminate unnecessary elements.
 | 
			
		||||
;; mainly because we already did that. I'm just focusing on the data-driven
 | 
			
		||||
;; approach here.
 | 
			
		||||
(defparameter *deriv* (make-hash-table))
 | 
			
		||||
(defun make-sum (a b)
 | 
			
		||||
  (cond
 | 
			
		||||
    ((and (numberp a) (numberp b)) (+ a b))
 | 
			
		||||
    ((and (numberp a) (= a 0)) b)
 | 
			
		||||
    ((and (numberp b) (= b 0)) a)
 | 
			
		||||
    (t (list '+ a b))))
 | 
			
		||||
(defun sum-deriv (operands var)
 | 
			
		||||
  (reduce #'make-sum
 | 
			
		||||
	  (mapcar (lambda (x) (deriv x var))
 | 
			
		||||
		  operands)))
 | 
			
		||||
(setf (gethash '+ *deriv*) #'sum-deriv)
 | 
			
		||||
 | 
			
		||||
;; the important thing to notice, here, is that we can easily
 | 
			
		||||
;; add another operation that we want to derive without ever modifying
 | 
			
		||||
;; derive or any of the other methods, by simply adding new entries to
 | 
			
		||||
;; the table.
 | 
			
		||||
;; this ease of extension is what is valuable here.
 | 
			
		||||
;; if I were the user of a library such as this, I could easily modify
 | 
			
		||||
;; this without ever touching the source.
 | 
			
		||||
 | 
			
		||||
(defun make-product (a b)
 | 
			
		||||
  (cond
 | 
			
		||||
    ((and (numberp a) (numberp b)) (* a b))
 | 
			
		||||
    ((and (numberp a) (= a 1)) b)
 | 
			
		||||
    ((and (numberp b) (= b 1)) a)
 | 
			
		||||
    ((or (zerop a) (zerop b)) 0)
 | 
			
		||||
    (t (list '* a b))))
 | 
			
		||||
(defun prod-deriv (operands var)
 | 
			
		||||
  (make-sum
 | 
			
		||||
   (make-product (first operands)
 | 
			
		||||
		 (deriv (second operands) var))
 | 
			
		||||
   (make-product (deriv (first operands) var)
 | 
			
		||||
		 (second operands))))
 | 
			
		||||
(setf (gethash '* *deriv*) #'prod-deriv)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(defun operator (e)
 | 
			
		||||
  (car e))
 | 
			
		||||
(defun operands (e)
 | 
			
		||||
  (cdr e))
 | 
			
		||||
(defun deriv ( exp var)
 | 
			
		||||
  (cond ((numberp exp) 0)
 | 
			
		||||
	((symbolp exp) (if (eql exp var) 1 0))
 | 
			
		||||
	(t (funcall (gethash (operator exp) *deriv*)
 | 
			
		||||
		       (operands exp)
 | 
			
		||||
		       var))))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
;; exercise 2.74
 | 
			
		||||
;; I'm going to make some assumptions here.
 | 
			
		||||
;; First off, I'm going to make up a data representation for each
 | 
			
		||||
;; division, and I'm going to make up some divisions, and employees for each division.
 | 
			
		||||
;;
 | 
			
		||||
 | 
			
		||||
(defparameter *salary-getters* (make-hash-table))
 | 
			
		||||
(defparameter *employee-getters* (make-hash-table))
 | 
			
		||||
 | 
			
		||||
(defun file-type (f)
 | 
			
		||||
  (car f))
 | 
			
		||||
(defun file-contents (f)
 | 
			
		||||
  (cadr f))
 | 
			
		||||
(defun employee-type (e)
 | 
			
		||||
  (car e))
 | 
			
		||||
(defun employee-contents (e)
 | 
			
		||||
  (cadr e))
 | 
			
		||||
 | 
			
		||||
;; get-employee returns a list containing the division as a symbol as the
 | 
			
		||||
;; first element, and the actual data returned by the division-specific
 | 
			
		||||
;; function as the second element.
 | 
			
		||||
;; get-salary takes that list, strips the type, and calls the division
 | 
			
		||||
;; specific function to get the salary
 | 
			
		||||
(defun get-salary (rec)
 | 
			
		||||
  (funcall (gethash (employee-type rec) *salary-getters*)
 | 
			
		||||
	   (employee-contents rec)))
 | 
			
		||||
(defun get-employee (name file)
 | 
			
		||||
  (list (file-type file) ;; add the division at the front.
 | 
			
		||||
	(funcall (gethash (file-type file) *employee-getters*) name)))
 | 
			
		||||
 | 
			
		||||
(defparameter *div-a* (list 'A
 | 
			
		||||
			    `(("John" . ,(make-div-a-employee "John" 15 150))
 | 
			
		||||
			      ("Jane" . ,(make-div-a-employee "Jane" 20 200))))
 | 
			
		||||
  "Division A has opted to use an alist for their employee file.
 | 
			
		||||
   Further, each employee is represented as a plist, containing their name,
 | 
			
		||||
   age, and salary.")
 | 
			
		||||
(defun make-div-a-employee (name age salary)
 | 
			
		||||
  (list :name name
 | 
			
		||||
	:age age
 | 
			
		||||
	:salary salary))
 | 
			
		||||
(defun div-a-get-salary (rec)
 | 
			
		||||
  (getf rec :salary))
 | 
			
		||||
(setf (gethash 'A *salary-getters*) #'div-a-get-salary)
 | 
			
		||||
(defun div-a-get-employee (name)
 | 
			
		||||
  (cdr (assoc name (file-contents *div-a*) :test #'string=)))
 | 
			
		||||
(setf (gethash 'A *employee-getters*) #'div-a-get-employee)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(defstruct div-b-employee name age salary)
 | 
			
		||||
(defparameter *div-b* (list 'B (make-hash-table :test #'equalp)))
 | 
			
		||||
(setf (gethash "Mark" (cadr *div-b*))
 | 
			
		||||
      (make-div-b-employee :name "Mark" :age 190 :salary 1000))
 | 
			
		||||
(setf (gethash 'B *employee-getters*) (lambda (name) (gethash name (cadr *div-b*))))
 | 
			
		||||
(setf (gethash 'B *salary-getters*) #'div-b-employee-salary)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(get-salary (get-employee "John" *div-a*))
 | 
			
		||||
;=> 150
 | 
			
		||||
(get-salary (get-employee "Mark" *div-b*))
 | 
			
		||||
;=> 1000
 | 
			
		||||
 | 
			
		||||
(defparameter *all-divisions* (list *div-a* *div-b*))
 | 
			
		||||
 | 
			
		||||
;; When a new company is taken over, you simply need to make sure the new
 | 
			
		||||
;; company's file is a list starting with the type tag, and whatever else
 | 
			
		||||
;; as the rest. Then you can just add the functions for each operation
 | 
			
		||||
;; into the *employee-getters* and *salary-getters* tables.
 | 
			
		||||
;; It's a little annoying that each file must be a cons cell with
 | 
			
		||||
;; the car being the symbol, but it does achieve the goal here.
 | 
			
		||||
;; presumably, in common lisp, I would be using the CLOS and methods
 | 
			
		||||
;; for this.
 | 
			
		||||
 | 
			
		||||
;; okay 22/01/2025, cleaned up the functions a little bit,
 | 
			
		||||
;; now the division-specific salary function just gets their own
 | 
			
		||||
;; data structures without having to manage the type tag themselves.
 | 
			
		||||
;; we could potentially get rid of the type tag in the division
 | 
			
		||||
;; file as well, through another table that associates type tags
 | 
			
		||||
;; to the files... but I'm not sure if that's very robust.
 | 
			
		||||
;; also forces each company to be aware of the main table...
 | 
			
		||||
;; look, this is good enough.
 | 
			
		||||
 | 
			
		||||
(defun find-employee (name divisions)
 | 
			
		||||
  (loop for i in divisions do
 | 
			
		||||
	(let ((rec (get-employee name i)))
 | 
			
		||||
	  (if (employee-contents rec)
 | 
			
		||||
	      (return rec)))))
 | 
			
		||||
		Reference in New Issue
	
	Block a user