help-gnu-emacs
[Top][All Lists]
Advanced

[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: Wed, 04 Feb 2009 11:33:06 +0100
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/22.2 (gnu/linux)

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)

Pourquoi pas.  Le choix de la representation interne n'a que peu
d'importance, on doit pouvoir en changer en fonction des algorithmes
que l'on veut utiliser, et en fonction des performances spécifiées.

Par contre, que l'on choisisse defstruct, defclass, des a-list, des
p-list (noter que defstruct couvre aussi les listes plates et les
vecteurs, et qu'en emacs lisp, eieio implemente les classes avec des
vecteurs), ce qui compte c'est de cacher ce choix derrière une
abstraction fonctionnelle.

Les accesseurs générés par defstruct ou defclass constituent cette
abstraction fonctionnelle.  Si on choisi une autre representation, il
faut prendre soin de définir soi-même les fonctions nécessaires.

Il y a une petite différence entre les accesseurs définis par
defstruct et defclass: ceux définis par defstruct sont des fonctions
normales, tandis que ceux définis par defclass sont des méthodes de
fonctions génériques, ce qui permet de les appliquer sur des classes
différentes et des sous-classes.    Mais si on ne considère qu'une
classe, on peut spécifier des noms identiques pour les accesseurs dans
les deux cas.


Dans ton exemple set-property défini un interface "dynamique" sur les
slots de l'objet (la p-list), et en plus encapsule la "base de donnée"
des personnes.  Dans certains cas, ça peut être avantageux, d'ailleurs
CLOS (et eieio) définissent un accesseur similaire: (slot-value object
field).  Mais ça sert plus dans le cas où on fait de la
métaprogrammation (par exemple, un sérialiseur / desérialiseur), ou si
il y a des traitements uniformes à faire sur tous les slots.

Dans les autres cas, je crois que c'est plus pratique de définir comme
avec defstruct et defclass, des accesseurs spécifiques.  Évidement, si
on a beaucoup de slots (ou de "classes" de p-list), il vaut mieux
écrire une macro pour les générer.

(require 'cl)

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



(defmacro define-structure (name &rest fields)
   `(progn
      (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.
                           (object)
                        (getf (cdr object) ,(make-keyword (symbol-name 
field))))) fields)
      ;; writers:
      ,@(mapcar (lambda (field)
                   `(defun ,(intern (format "set-%s-%s" name field)) (object 
value)
                        (assert (not (null object)))
                        (setf (getf (cdr object)  ,(make-keyword (symbol-name 
field))) value)))
                fields)
      ,@(mapcar (lambda (field)
                  `(defsetf ,(intern (format "%s-%s" name field))
                           ,(intern (format "set-%s-%s" name field))))
                fields)
     ',name))


(progn (pprint (macroexpand '(define-structure person name birthdate job))) nil)
-->
(progn
  (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)
  'person)

(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)
  tintin)
--> (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]