An Object in Lisp. Part 2

*****************************************************************************************************************

WARNING

DO NOT USE THIS EXAMPLE! THE MACROS DO NOT IMPLEMENT PARAMETERS PROPERLY. I WILL BE ADDING AN ADDENDUM TO SUPPLEMENT THIS POST WITH-IN THE WEEK

GUTZOFTER

11/21/2008

*****************************************************************************************************************

In Part 1, we described different implementations of objects in lisp. In this part we will delve into creating an abstraction so that we don’t have to deal with repetitive code.

Here is the abstraction I want for creating an object:

(defobj counter-obj
  (:members
    ((counter 0)))
  (:methods
    (:increment-counter (lambda () (incf counter))
      :get-counter (lambda ()counter))))

So the defobj will hold three items, a name, a list for the encapsulated variables, and a list for the methods that we want to create. I think this is pretty well cut and dry.
To create an instance all we will then need is this:

(defmacro defobj (name &rest class-data)
  (let ((members (getf (first class-data) :members))
        (methods (getf (second class-data) :methods)))
    `(defun ,(intern (concatenate 'string "MAKE-" (symbol-name name))) ()
       (let ,members
         (list ,@methods)))))

The first part of the macro extracts out the first list (members) and the second list (methods) and stores them into temporary variables.

The second part of the macro is the expansion. This expansion creates a function with the prefix of “make-” and the object name. (defun make-<name> …). The members are spliced into a let and the methods are spliced into the let. (See Part 1 make-counter3).

The macro-expansion generated from the macro is:

(DEFUN MAKE-COUNTER-OBJ ()
  (LET ((COUNTER 0))
    (LIST :INCREMENT-COUNTER
          (LAMBDA ()
            (INCF COUNTER))
          :GET-COUNTER
          (LAMBDA ()
            COUNTER))))

So far we have created a macro that creates the object for us, but we still are dependent on the implementation details of the object to access the behavior of the object:

(defparameter counter (make-counter-obj))
(funcall (getf counter :increment-counter))
(funcall (getf counter :increment-counter))
(funcall (getf counter :increment-counter))
(funcall (getf counter :get-counter))

The next step is to expand (pun intended!) on our defobj macro to abstract out those access functions. Here is how we want to access the counter-obj:

(defun increment-counter (obj)
  (funcall (getf obj :increment-counter)))
(defun get-counter counter ()
  (funcall (getf obj :get-counter))

So the total output of the macro will be:

(defun make-counter-obj ()
  (let ((counter 0))
    (list :increment-counter
          (lambda ()
            (incf counter))
          :get-counter
          (lambda ()
            counter))))

(defun increment-counter (obj)
  (funcall (getf obj :increment-counter)))

(defun get-counter (obj)
  (funcall (getf obj :get-counter))

Here are the macros:

(defmacro make-make-property (name members methods)
  `(defun ,(make-name name) ()
     (let ,members
       (list ,@methods))))

(defmacro make-property (name)
  `(defun ,(new-symbol (symbol-name name)) (obj)
    (funcall (getf obj ,name))))

(defmacro make-properties (property-names)
  `(progn
     ,@(loop for name in property-names collect `(make-property ,name))))

(defmacro defobj (name &rest class-data)
  (let ((members (getf (first class-data) :members))
        (methods (getf (second class-data) :methods)))
    `(progn
       (make-make-property ,name ,members ,methods)
       (make-properties ,(property-names methods)))))

There are some helper functions:

(defun group (source n)
  (if (zerop n)
      (error "zero length or source is nil"))
  (labels ((rec (source acc)
             (let ((rest (nthcdr n source)))
               (if (consp rest)
                   (rec rest (cons (subseq source 0 n) acc))
                   (nreverse (cons source acc))))))
    (if source (rec source nil) nil)))

(defun new-symbol (&rest args)
  "Concatenate symbols or strings to form an uninterned symbol"
  (intern (format nil "~{~a~}" args)))

(defun make-name (name)
  (new-symbol "MAKE-" (symbol-name name)))

(defun list-properties (property-list)
  (group property-list 2))

(defun property-names (properties)
  (let ((names))
    (dolist (property (list-properties properties))
      (push (first property) names))
    (nreverse names)))

Two of the functions I borrowed. “group” is taken from OnLisp and “new-symbol” is taken from Paradigms of AI Programming.

In evaluating this implementation of objects so far we see that we still have to do some typing beyond what we really want to do:

(defobj counter-obj
  (:members
    ((counter 0)))
  (:methods
    (:increment-counter (lambda () (incf counter))
      :get-counter (lambda ()counter)))
(test-fixture object-tests
    (:setup
      ((counter-obj (make-counter-obj))))

  (:tests
    (should-allways-make-object-be-true-with-implemented-functions
     (assert-true counter-obj)
     (assert-equal 0 (get-counter counter-obj))
     (assert-equal 1 (increment-counter counter-obj))
     (assert-equal 1 (get-counter counter-obj))
     (assert-equal 2 (increment-counter counter-obj))
     (unintern 'get-counter)
     (unintern 'increment-counter)
     (unintern 'make-counter-obj))))

Looking at the unit tests we see that in order to use an object we have to use “make-counter-obj” to generate the list and also store it into a refence variable ‘counter-obj’ now we have to enter ‘counter-obj’ as a parameter to our access functions. As a programmer I then need to remember that reference variable.

I’m thinking that maybe it might be better to do this:

(defobj counter
  (:members
    ((counter 0)))
  (:methods
    (:increment
      (lambda () (incf counter))
      :count
      (lambda ()counter))))

(test-fixture no-reference-to-object
    (:setup
      ((make-counter 'obj)))

  (:tests
    (should-use-obj-functions
     (assert-equal 0 (obj-count))
     (assert-equal 1 (obj-increment))
     (assert-equal 1 (obj-count))
     (assert-equal 2 (obj-increment)))))

Hmmm… til part 3


Syntax hi-lighting <tohtml.com>

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: