An Object in Lisp. Part 6

January 2, 2009

What a way to start a little update on the the object framework that I have been pushing on.

The last update talked about ‘destructuring-bind‘. I had an epipheny the other day when I was trying to tdd a way to create generic functions for the framework. That led me to throwing away the destructuring-bind.

In order to explain my reasoning we need to go back to the representation of an object in the framework. The representation is a list of closures referenced by properties.

(defun hello (obj &rest args)
  (apply (getf obj :hello) args)

(defvar hold1 (list :hello (lambda () (print 'hello))))
(defvar hold2 (list :hello (lambda (x) (print x))))

The above lists can be accessed with a getf that retrieves the lambda from the list. To execute the lambda just apply. The hello defun above will handle either list as obj.

Something that I now know,  is that the lambda does its own destructuring-bind on its parameter list.  So here is how I create the accessor function for all objects  (generic and multiple signatures):

(defmacro make-property (name)
  (let ((g (gensym)))
    (multiple-value-bind (sym-name key-name) (symbol-and-keyword name)
      `(progn
         (if (not (fboundp ',sym-name))
             (defun ,sym-name (obj &rest ,g)
               (block ,sym-name
                 (apply (getf obj ,key-name) ,g)))))))

Happy New Year!

gutzofter


An Object in Lisp. Part 5

December 18, 2008

Destructuring Bind

… binds the variables specified in lambda-list to the corresponding values in the tree structure resulting from the evaluation of expression; then destructuring-bind evaluates forms.

‘destructuring-bind‘ is a wonderful macro. The first thing you do is hand it a lambda list and a list of values. It acts like a pattern matcher. If the list of values does not match up to the lambda list it will throw an error.

Here is what I’m talking about:

(defun test-d-bind (&rest args)
  (destructuring-bind (a &optional (b 3) &rest x &key c (d a)) args
    (list a b c d x))
(test-d-bind nil)
;>>(NIL 3 NIL NIL NIL)
(test-d-bind 1 6 :d 8 :c 9 :d 10)
;>>(1 6 9 8 (:D 8 :C 9 :D 10)

Read the rest of this entry »


Lisp Programming Trick #1

December 7, 2008

Getter/Setters

How many times have you created two function for setting a specific value and then getting the value?

(let ((value ""))
  (defun property (&key is)
    (cond
      (is (setf value is))
      (t value)))
  ;;; here is getter and setter functions
  (defun get-property ()
    value)
  (defun set-property (data)
    (setf value data)))

(define-test get_set-property
  (assert-equal "" (get-property))
  (assert-equal "property" (set-property "property"))
  (assert-equal "property" (get-property))
  (assert-equal "" (set-property "")))

(define-test property
  (assert-equal "" (property))
  (assert-equal "property" (property :is "property"))
  (assert-equal "property" (property))
  (assert-equal "" (property :is ""))

Using a &key keyword parameter adds syntatic sugar to setting properties. It also means less typing.

It is done with a conditional. Another benefit is that behavior for the property is located in only one function instead of two functions.

New Use Case

(Zach Beane’s Request)

Need to be able to set the property to nil. Added the test to my unit tests. It failed. Modified the code and now it passes.

You just need to add a supplied-p parameter.

(let ((value ""))
  (defun property (&key (is nil is-p))
    (cond
      (is-p (setf value is))
      (t value))))

(define-test property
  (assert-equal "" (property))
  (assert-equal "property" (property :is "property"))
  (assert-equal "property" (property))
  (assert-equal "" (property :is ""))
  (assert-equal nil (property :is nil)))

Happy Lisp Cargo-Culting!


An Object in Lisp. Part 4

December 3, 2008
  • What does it mean to be an object?

  • A [computer] language mechanism for binding data with methods that operate on that data. Object (Computer Science).

    • Binding is the creation of a simple reference to something that is larger and more complicated and used frequently. Binding (Computer Science).

    • Data refers to a collection of facts usually collected as the result of […] processes within a computer system […]. Data.

    • […] a method usually consists of a sequence of statements to perform an action, a set of input parameters to customize those actions, and possibly an output value (called return value) of some kind. Methods (Computer Science).

So the following code by the above definitions must be an object.

(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))
  • ‘make-counter-obj’ creates the binding.
  • The ‘counter variable in ‘make-counter-obj’ is the storage for our data.
  • The two functions/methods ‘increment-counter’ and ‘get-counter’ are definitely methods of some kind, that allow us to operate on the ‘counter data.

Throughout this entire series it seemed to be inevitable that p-lists were to be used not only the actual storage mechanisms for the methods that act on the data, but also in the macros, p-lists are a major data structure.

Right now, I’m pretty comfortable with the implementation of this type of object. But already I can see future implementations. There is definitely another abstraction that can be pulled out of this. The ‘get-object-id’ function is a place to start. All object instances, that use my objects language, use this function. It could be considered part of a parent property.

Right now I want to start integrating Event Driven Architecture to further my cargo-cultism of Lisp.

Have a Happy Holiday,

Joe G


An Object in Lisp. Part 3

November 30, 2008

Adding constructors to object

The object needs to be able to implement default constructors. This required making changes to ‘defobj’ and ‘make-make-property’.

Specifically:

(defobj init
  (:init (d1 d2))
  (:methods
    ((:get-data () (values d1 d2))))
(defmacro defobj (name &rest class-data)
  (let ((members (when-select :members class-data))
        (methods (when-select :methods class-data))
        (constructors (when-select :init class-data)))
    `(progn
       (make-make-property ,name ,constructors ,members ,methods)
       (make-properties ,methods)))
(defmacro make-make-property (obj-name constructors members methods)
  (let ((property-list (loop for property in methods
                             append
                             (let ((name (first property))
                                   (args (second property))
                                   (commands (cddr property)))
                               `(,name (lambda ,args ,@commands))))))
    `(defun ,(make-name obj-name) (&rest args)
       (destructuring-bind ,constructors args
         (let ,members
           (list ,@property-list)))))

Using ‘defobj’ we extract out the code for the constructor. A change was made to ‘make-make-property by adding another parameter, ‘constructors. We now make a change to our code-generation to include args. then we use a destructuring-bind to bind the ‘constructors to ‘args. This will then wrap members and the list into a let. That is it.

Universal Identification

Because of later uses of objects, I need to have a way to create a specific identifier for each instance of a specific object. This isn’t a very good implementation of a universal identification generator. If anybody has a much better implementation, let me know. The implementation of a UID requires three new functions:

(defun unique-string-name (name-string)
  (format nil "~(~{~2,'0X~}~)"
          (coerce (sb-md5:md5sum-string
                   (sb-ext:native-namestring name-string))
                  'list)))

(defun uid (random-fn name-string)
  (unique-string-name
   (format nil "~a~a" name-string (funcall random-fn 1000000000))))

(defun object-id (obj)
  (funcall (getf obj :object-id)))

‘unique-string-name’ is a function taken from sb-cover. It takes a string and generates a funky string.

‘uid’ actually generates the uid. I pass in ‘random-fn, because I wanted to unit test this functions. The function will create a string from ‘name-string (object’s name) and randomly generate a number up to 1,000,000,000.

‘object-id’ is implemented as a function outside of the ‘defobj’ macro. All ‘defobj’ generated objects will implement an object-id in it’s list. This is done in ‘make-make-property’:

(defmacro make-make-property (obj-name constructors members methods)
  (let ((property-list (loop for property in methods
                             append
                             (let ((name (first property))
                                   (args (second property))
                                   (expressions (cddr property)))
                               `(,name (lambda ,args ,@expressions))))))
    `(defun ,(make-name obj-name) (&rest args)
       (destructuring-bind ,constructors args
         (let ((object-id (uid #'random ,(symbol-name obj-name))))
           (let ,members
             (list ,@property-list ,@(list :object-id `(lambda () object-id))))))))

All that was done was we close over the previous let with a new let that contains a variable object-id. The next part is when we create the list the contains the closures, we add the object-id closure.


An Object in Lisp. Part 2 Addendum

November 27, 2008

Addendum

The code for an object:

(defobj counter-obj
  (:members
    ((counter 0)))
  (:methods
    ((:increment-counter ()
       (incf counter))
     (:get-counter ()
       counter)
     (:set-counter (x)
       (setf counter x))))

is different than the previous implementation. The previous implementation could not implement parameters to the methods. One other thing was the redundancy of having everything wrapped in lambdas, so you implement them with name, parameters, and code-block.

I like to break my macros up into individual bits of responsibility. It makes for easier debugging. I also use the REPL a lot. Here are the macros:

(defmacro make-make-property (name members methods)
  (let ((property-list (loop for property in methods
                             append
                             (let ((name (first property))
                                   (args (second property))
                                   (commands (cddr property)))
                               `(,name (lambda ,args ,@commands))))))
    `(defun ,(make-name name) ()
       (let ,members
         (list ,@property-list)))))

(defmacro make-property (name&args)
  (let ((name (first name&args))
        (args (second name&args)))
    `(defun ,(new-symbol (symbol-name name))
       (obj ,@(when args args))
       (funcall (getf obj ,name) ,@(when args args))
       )))

(defmacro make-properties (methods)
  `(progn
     ,@(loop for name&args in (extract-name&args methods)
             collect `(make-property ,name&args))))

(defmacro defobj (name &rest class-data)
  (let ((members (when-select :members class-data))
        (methods (when-select :methods class-data)))
    `(progn
       (make-make-property ,name ,members ,methods)
       (make-properties ,methods))))

Going through the macros bottom to top, ‘defobj’ takes a name and everything as a &rest parameter.  We extract each section of code; members and methods, then supply them to each individual sub-macro.

The code this will generate is:

(PROGN
 (MAKE-MAKE-PROPERTY COUNTER-OBJ ((COUNTER 0))
                     ((:INCREMENT-COUNTER NIL (INCF COUNTER))
                      (:GET-COUNTER NIL COUNTER)
                      (:SET-COUNTER (X) (SETF COUNTER X))))
 (MAKE-PROPERTIES
  ((:INCREMENT-COUNTER NIL (INCF COUNTER)) (:GET-COUNTER NIL COUNTER)
   (:SET-COUNTER (X) (SETF COUNTER X))))

‘make-make-property’ will make the function that creates our closure with our p-list. The macro first unwraps methods and reconstruct the p-list items (name, lambda., and code-block). It will then code-generate the defun, and the closure:

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

‘make-properties’ then creates the access functions to the p-list with a loop. It utilizes a sub-macro to build each access function:

(PROGN
 (MAKE-PROPERTY (:INCREMENT-COUNTER NIL))
 (MAKE-PROPERTY (:GET-COUNTER NIL))
 (MAKE-PROPERTY (:SET-COUNTER (X))))

Nothing very interesting here, except for getting just name and parameters from the methods list. The loop is taken from PCL (unit test section).

‘make-property’ is very interesting.  first we separate out the name and args from the input to the sub-macro. We then generate the function with the code generation for accessing the p-list. Originally I had two functions being generated, depending on an ‘if’ args. It seemed too much duplication so I went ahead and added the ‘when’ functions. Here is the macros output:

(DEFUN INCREMENT-COUNTER (OBJ)
  (FUNCALL (GETF OBJ :INCREMENT-COUNTER))

Here is the output from a ‘make-property’ that has a paramter:

(DEFUN SET-COUNTER (OBJ X)
  (FUNCALL (GETF OBJ :SET-COUNTER) X))

Happy Gobbling,

Joe G.


Object Oriented Programmer in Crisis

November 19, 2008

Code coverage is the glove that fits over unit testing. In order to get an idea of what code coverage follow the link.

I started back into software development with VB6 and then added C#. I love the idea of Test Driven Development (TDD). Reflection in C# seemed to me a HUGE step forward in the tools available to a simple  programmer. NUnit, NCover, Rhino-Mocks, Resharper, and NDepend were my tools of choice.

The paradigm for the use of these tools was to focus from the outside in. What I mean is that you would start at the Integration level and keep adding functionality and refactoring to the lowest type. The problem with this was that I always rolled my integrator/interpreter. I would feed the interperter with a text file:

# Basic Move of Player
Map 3, 3
Player 1, 1
Move n
Move s
move e
move w
Assert 1, 1

# Verify that player stays in north boundaries
map 3, 3
player 1, 1
move n
move n
move n
assert 1, 0

# Verify that player stays in west boundaries
move w
move w
move w
assert 0, 0

# Verify that player stays in south boundaries
move s
move s
move s
assert 0, 2

# Verify that player stays in east boundaries
MOVE e
MOVE e
moVE e
ASSERT 2, 2

See My Tutorial in C#

It is human readable. If you’ve read Pragmatic Programmer you will know what I’m talking about.

The problem with using an interpreter is that I had no way to pin down the text file. Yes I could pin down the interpreter machinery with unit testing, but not the script file. The text file is data not code. Unit testing works on code not data.

In order to pin the script down it would need to be implemented in my source language, but it is very hard to do in C# reflection and all.

It seemed to work pretty good, but I could never shake this feeling that something was missing, until I ran into Greenspun’s Tenth Rule.

The promise of code is data and data is code appealed to me.

So started my travels into Lisp (excpet checking out some AI book when I was in the US Navy).

After reviewing the eco-system for Common Lisp I’ve come to some conclusions.

  1. Automation using a tool stack that will let you use unit testing, code coverage, profiling, and integration testing is just not for beginners.
  2. Not enough tutorials on building applications (Not Including PCL){Great Book!}
  3. More in-depth knowledge of the basics and their implementation in applications. On Lisp is good for beginners. But in the book PG says: “In real programs, the closures and data structures would also be more elaborate than those we see in make-adder or make-dbms.”, what could those elaborations be?
  4. Some code that I’ve seen can be Spaghetti Code or a Big Ball of Mud.
  5. My own code in lisp can be characterized by item 4.

My goal is to work on item 1.

I’ve worked on adding a fixture to lisp-unit. See My Downloads Section. Now I’m starting on adding sb-cover.

Also I’m going to be getting into ASDF. I think their is a lot of potential their for using it to automate item 1.

Happy Lambda