unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
From: pjb@informatimago.com (Pascal J. Bourguignon)
To: help-gnu-emacs@gnu.org
Subject: Re: How to describe something in Lisp?
Date: Thu, 05 Feb 2009 03:28:10 +0100	[thread overview]
Message-ID: <87ljslipmt.fsf@galatea.local> (raw)
In-Reply-To: 7c63jq3319.fsf@pbourguignon.anevia.com


And now, translated in English:

pjb@informatimago.com (Pascal J. Bourguignon) writes:

> Tassilo Horn <tassilo@member.fsf.org> writes:
>
>> Johan Andersson <johan.rejeep@gmail.com> 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)
   `(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__


  parent reply	other threads:[~2009-02-05  2:28 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-02-03 14:23 How to describe something in Lisp? Johan Andersson
2009-02-03 16:24 ` Thierry Volpiatto
2009-02-03 16:46 ` Tassilo Horn
     [not found] ` <mailman.6652.1233679633.26697.help-gnu-emacs@gnu.org>
2009-02-04 10:33   ` Pascal J. Bourguignon
2009-02-04 11:43     ` Tassilo Horn
2009-02-05  2:28     ` Pascal J. Bourguignon [this message]
2009-02-05  7:22       ` Johan Andersson
     [not found]       ` <mailman.16.1233818553.17492.help-gnu-emacs@gnu.org>
2009-02-06 18:53         ` Ted Zlatanov
     [not found]   ` <7c63jq3319.fsf@pbourguignon.informatimago.com>
     [not found]     ` <mailman.6723.1233747843.26697.help-gnu-emacs@gnu.org>
2009-02-04 13:26       ` Pascal J. Bourguignon
     [not found] <mailman.6644.1233674526.26697.help-gnu-emacs@gnu.org>
2009-02-03 15:48 ` Andreas Politz
2009-02-03 16:40   ` Pascal J. Bourguignon
2009-02-03 16:44     ` Johan Andersson
2009-02-03 16:54       ` Tassilo Horn
2009-02-03 17:07         ` Johan Andersson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87ljslipmt.fsf@galatea.local \
    --to=pjb@informatimago.com \
    --cc=help-gnu-emacs@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).