Object Programming in Lisp Observation #1

January 5, 2009

When developing with the object framework, I end up excreting all the side-effect-free (SEF) code from the object. Remember the reason for an object is to encapsulate state.

(defun format-results (result-totals)
  (destructuring-bind (result-totals asserts passes errors elapses) result-totals
    (list
     (format nil "~A Total Test(s)" result-totals)
     (format nil "~A Assertions" asserts)
     (format nil "~A Passes" passes)
     (format nil "~A Failures" (- asserts passes))
     (format nil "~A Errors" errors)
     (format nil "~,3F Second(s)" elapses))))

(defun result-sums (result-totals)
  (loop for (nil assert pass error elapse) in result-totals
        summing assert into asserts
        summing pass into passes
        summing error into errors
        summing elapse into elapses
        finally
        (return
         (list (length result-totals) asserts passes errors elapses))))

(defobj results-model
  (:members ((database nil)))
  (:methods ((:get-result-totals ()
                  (format-results (result-sums (retrieve-totals database))))

             (:connect-db (db)
               (setf database db))

             (:insert-result-data (result-data)
               (add-result database result-data))))

This method really it makes it easier to test the SEF functions. When using the object’s methods for interacting with state during tdd, you have to maintain a reference to the object. By excreting the functionality out of the object, you no longer need the reference. You just test for functionality.

(test-fixture
 :format-results
 (:tests
   (should-return-format-results-empty
    (assert-equal '("0 Total Test(s)" "0 Assertions" "0 Passes" "0 Failures" "0 Errors"
           "0.000 Second(s)") (format-results (list 0 0 0 0 0))))))

(test-fixture
 :result-sums
 (:tests
   (should-return-no-sums
    (assert-equal '(1 0 0 0 0) (result-sums (list (list :no-name 0 0 0 0)))))

   (should-return-sums
    (assert-equal '(2 1 1 1 0.15100001) (result-sums (list (list :t1 0 0 1 0.001) (list :t2 1 1 0 0.15)))))))

(test-fixture
 :results-model
 (:setup ((db (make-database))
          (model (make-results-model)))
   (connect-db model db))
 (:tests
   (should-transmit-result-totals
    (let ((totals '("0 Total Test(s)" "0 Assertions" "0 Passes" "0 Failures" "0 Errors"
           "0.000 Second(s)")))
      (assert-equal totals (get-result-totals model))))

   (should-insert-result-data
    (let ((totals '("1 Total Test(s)" "1 Assertions" "1 Passes" "0 Failures" "0 Errors"
           "0.000 Second(s)")))
      (insert-result-data model (list :no-name 1 1 0 0.0))
      (assert-equal totals (get-result-totals model))))))

This is very conducive to programming incrementally.

The function we want to look at is get-result-totals. This function retrieve the totals from the database. We pass this then to result-sums. It’s results are then passed to format-results.

  1. Retrieve results from database.
  2. Sum results.
  3. Format results.

Happy January!

Advertisements

Recursive Tree Rabbit Holes

December 8, 2008
  1. WHY? When using my own home grown object system, I found out that it could not handle keyword parameters.
  2. Problem. Take a parameter list and remove keyword parameters.
  3. Solution. Parse the argument list to the functions, remove and if needed replace keyword.
  4. Example. Need to remove &key and replace with keyword and variable.
    • (a b) => (a b)
    • (a (b)) => (a (b))
    • (&key a) => (:a a)
    • (&key a b) => (:a a :b b)
    • (a &key b) => (a :b b)
    • (a (&key b)) => (a (:b b))

Read the rest of this entry »