[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: How to describe something in Lisp?

From: Pascal J. Bourguignon
Subject: Re: How to describe something in Lisp?
Date: Thu, 05 Feb 2009 03:28:10 +0100
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/22.3 (darwin)

And now, translated in English:

address@hidden (Pascal J. Bourguignon) writes:

> Tassilo Horn <address@hidden> writes:
>> Johan Andersson <address@hidden> writes:
>> Hi Johan,
>>> Then I could easy update attributes on the objects, remove and add
>>> people and then update the file.
>>> I tried with a couple of solutions for this in Lisp:
>>> 1) One list named people:
>>> (
>>>   (name age married sex)
>>>   ...
>>> )
>> I think a list of plists would be quite intuitive to someone with an OO
>> background.  Here's a quick and dirty snippet which should get you
>> started:
>> --8<---------------cut here---------------start------------->8---
>> (defvar persons
>>   '((:name "Klaus" :age 36 :sex male)
>>     (:name "Claudia" :age 31 :sex female)))
>> [...]
>> ;; Adjust the ages
>> (set-property "Klaus" :age 37)
>> (set-property "Claudia" :age 32)

Why not.  The choice of internal representation doesn't matter.  You
must be able to change the internal representation at will, depending
on the algorithms and specified performance.

On the other hand, whether you use defstruct, defclass, a-lists,
p-lists (note that defstruct can also generate flat lists and vectors,
and in emacs lisp, eieio implements classes as vectors), you must hide
this choice behind a functional abstraction.

The accessors generated by defstruct or defclass make up this
functional abstraction.  If you choose another representation, you
will have to define yourself the needed functions.

There's a little difference between the accessors defined by defstruct
and defclass: the formers are normal functions, while the later are
methods on generic functions, which allows to apply them on objects of
various classes and subclasses.  But if you consider only one class,
and use the same names in both cases, they'll be exchangeable.

In your example, set-property defines a "dynamical" interface on the
slots of the object (thep-list) (and in addition encapsulate the
person "database").  In some cases it may be a good way to do it; CLOS
(and eieio) do define a similar accessor: (slot-value object field).
But this is useful more in the case of metaprogramming (eg. a
serializer/deserializer) or if there are uniform processings on
all the slots.

In other cases, I think it's more practical to define specific
accessors, as defstruct and defclass do. Of course, if you have a lot
of slots (or "classes" of p-lists), it's worthwhile to write a macro
to generate them automatically:

(require 'cl)

(defun make-keyword (name) (intern (format ":%s" name)))

(defmacro define-structure (name &rest fields)
      (defun* ,(intern (format "make-%s" name)) 
             (&key ,@fields)
          (list ',name ,@(mapcan (lambda (field)
                             (list (make-keyword (symbol-name field))
                                   field)) fields)))
      ;; readers
      ,@(mapcar (lambda (field)
                   `(defun ,(intern (format "%s-%s" name field)) ; defstruct 
like naming.
                        (getf (cdr object) ,(make-keyword (symbol-name 
field))))) fields)
      ;; writers:
      ,@(mapcar (lambda (field)
                   `(defun ,(intern (format "set-%s-%s" name field)) (object 
                        (assert (not (null object)))
                        (setf (getf (cdr object)  ,(make-keyword (symbol-name 
field))) value)))
      ,@(mapcar (lambda (field)
                  `(defsetf ,(intern (format "%s-%s" name field))
                           ,(intern (format "set-%s-%s" name field))))

(progn (pprint (macroexpand '(define-structure person name birthdate job))) nil)
  (defun* make-person (&key name birthdate job)
    (list 'person :name name :birthdate birthdate :job job))
  (defun person-name (object) (getf (cdr object) :name))
  (defun person-birthdate (object) (getf (cdr object) :birthdate))
  (defun person-job (object) (getf (cdr object) :job))
  (defun set-person-name (object value)
    (assert (not (null object)))
    (setf (getf (cdr object) :name) value))
  (defun set-person-birthdate (object value)
    (assert (not (null object)))
    (setf (getf (cdr object) :birthdate) value))
  (defun set-person-job (object value)
    (assert (not (null object)))
    (setf (getf (cdr object) :job) value))
  (defsetf person-name set-person-name)
  (defsetf person-birthdate set-person-birthdate)
  (defsetf person-job set-person-job)

(define-structure person name birthdate job)
--> person

(make-person :name "Tintin" :birthdate 1918 :job "Reporter")
--> (person :name "Tintin" :birthdate 1918 :job "Reporter")

(let ((tintin (make-person :name "Tintin" :birthdate 1918 :job "Reporter")))
  (setf (person-birthdate tintin) 1920)
--> (person :name "Tintin" :birthdate 1920 :job "Reporter")

(let ((tintin (make-person :name "Tintin" :birthdate 1918 :job "Reporter")))
  (insert (format "%s is a %s\n" (person-name tintin) (person-job tintin))))
Tintin is a Reporter

Then, if you notice that p-list are too slow, or that you need
subclasses, you can easily substitute defstruct for define-structure,
and get structures with direct access slots, or if you notice that you
need multiple inheriting, you can substitute a defclass for the
define-structure, all with the rest of the program unchanged, since
using the functional abstraction defined by these accessors.

__Pascal Bourguignon__

reply via email to

[Prev in Thread] Current Thread [Next in Thread]