From: Giannandrea Castaldi
Subject: object or struct?
Date: 
Message-ID: <c1i4vh$p3t$1@lacerta.tiscalinet.it>
Hi,
I'm studying lisp and after I've read several books that explains the 
language features and tried the code examples I want to write some 
simple applications.
At work I program with java and then in my first simple application (a 
system to manage todo lists) I've used CLOS but after I've read an 
article by Paul Grahm about the excessive use of objects 
(http://www.paulgraham.com/noop.html) I've tried to rewrite the same 
services using structs.
Using two strcuts to define todo-item and todo-list I've write 45 lines 
of code while with two classes in clos they are 65 (you can see the code 
below) and than perhaps it's better to start with struct and than to use 
clos only if necessary with more complex functionalities.
How do you usually start writing your new systems, immediatly using clos 
or initially with something simpler (structs or lists or clousures) and 
passing to clos afterwards only if necessary? Which are your criteria?

Thanks.

Giannandrea

CLOS code ----------------------------------
(in-package cl-todo)

(defclass todo-item ()
   ((descripiton :initarg :description
		:reader description)
    (priority :initarg :priority
	     :accessor priority)
    (year :initarg :year
	 :accessor year)
    (month :initarg :month
	  :accessor month)
    (day :initarg :day
	:accessor day)
    (hour :initarg :hour
	:accessor hour)
    (minute :initarg :minute
	:accessor minute)))

(defun make-todo-item (&key description (priority 5) year month day hour 
minute)
   (multiple-value-bind (def-sec def-min def-hour def-day def-month 
def-year) (get-decoded-time)
     (make-instance
      'todo-item
      :description description	
      :priority priority
      :year (if year year def-year)
      :month (if month month def-month)
      :day (if day day def-day)
      :hour (if hour hour def-hour)
      :minute (if minute minute def-min))))

(defmethod print-object ((item todo-item) stream)
   (format stream "<todo-item (~A): ~A - ~A/~A/~A ~A:~A>"
	  (priority item)
	  (description item)
	  (day item) (month item) (year item) (hour item) (minute item)))

(defmethod date ((item todo-item))
   (encode-universal-time 0 (minute item) (hour item) (day item) (month 
item) (year item)))

(defclass todo-list ()
   ((todo-items :initform (list))))

(defun make-todo-list ()
   (make-instance 'todo-list))

(defmethod todo-items ((a-todo-list todo-list) &key (order #'priority))
   (with-slots (todo-items) a-todo-list
	      (if (eq order #'priority)
		  todo-items
		  (sort (copy-seq todo-items) #'lessp
			:key order))))

(defmethod add-todo-item ((a-todo-list todo-list) todo-item)
   (with-slots (todo-items) a-todo-list
	      (push todo-item todo-items)
	      (setf todo-items (sort (todo-items a-todo-list) #'< :key 
#'priority))))

(defmethod print-object ((list todo-list) stream)
   (format stream "<todo-list: ~A>" (todo-items list)))

(defmethod lessp ((elem1 NUMBER) (elem2 NUMBER))
   (< elem1 elem2))

(defmethod lessp ((elem1 STRING) (elem2 STRING))
		(string-lessp elem1 elem2))



Struct code ----------------------------------
(in-package cl-todo)

(defstruct (todo-item (:conc-name todo-)
		      (:print-function print-todo-item))
   description
   (priority 5)
   (year (sixth (multiple-value-list (get-decoded-time))))
   (month (fifth (multiple-value-list (get-decoded-time))))
   (day (fourth (multiple-value-list (get-decoded-time))))
   (hour (third (multiple-value-list (get-decoded-time))))
   (minute (second (multiple-value-list (get-decoded-time)))))

(defun print-todo-item (item stream depth)
   (format stream "<todo-item (~A): ~A - ~A/~A/~A ~A:~A>"
	  (todo-priority item)
	  (todo-description item)
	  (todo-day item) (todo-month item) (todo-year item) (todo-hour item) 
(todo-minute item)))

(defun date (item)
   (encode-universal-time 0 (todo-minute item) (todo-hour item) 
(todo-day item)
			 (todo-month item) (todo-year item)))

(defstruct (todo-list (:conc-name list-)
		      (:print-function print-todo-list))
   (items (list)))

(defun print-todo-list (a-todo-list stream depth)
   (format stream "<todo-list: ~A>" (todo-items a-todo-list)))

(defun todo-items (a-todo-list &key (order #'todo-priority))
   (if (eq order #'todo-priority)
       (list-items a-todo-list)
       (sort (copy-seq (list-items a-todo-list)) #'lessp
	    :key order)))

(defun add-todo-item (a-todo-list a-todo-item)
   (push a-todo-item (list-items a-todo-list))
   (setf (list-items a-todo-list)
	(sort (list-items a-todo-list) #'< :key #'todo-priority)))

(defmethod lessp ((elem1 NUMBER) (elem2 NUMBER))
   (< elem1 elem2))

(defmethod lessp ((elem1 STRING) (elem2 STRING))
		(string-lessp elem1 elem2))

From: Frode Vatvedt Fjeld
Subject: Re: object or struct?
Date: 
Message-ID: <2hptc3iafj.fsf@vserver.cs.uit.no>
Giannandrea Castaldi <········@tiscali.it> writes:

> [..] At work I program with java and then in my first simple
> application (a system to manage todo lists) I've used CLOS but after
> I've read an article by Paul Grahm about the excessive use of
> objects (http://www.paulgraham.com/noop.html) I've tried to rewrite
> the same services using structs. [..]

I think Paul Graham's article is about the excessive use of "Object
Oriented Programming", not about objects. Whether one uses defstruct
or defclass in Common Lisp is completely unrelated to whether your
program is "Object Oriented". (I'm quoting "Object Oriented" because
it's a very vague term, as the first reference in Graham's article
discusses.) In Common Lisp, a defstruct or defclass instance (or
anything else, like a number or symbol) are all objects.

I'd advise you to use defclass rather than defstruct by default,
because defclass classes and instances behave much more nicely with
respect to dynamic and exploratory programming, two concepts that I
believe are much more important than object oritentation.

-- 
Frode Vatvedt Fjeld
From: Pascal Costanza
Subject: Re: object or struct?
Date: 
Message-ID: <c1i6av$t3v$1@newsreader2.netcologne.de>
Giannandrea Castaldi wrote:
> Hi,
> I'm studying lisp and after I've read several books that explains the 
> language features and tried the code examples I want to write some 
> simple applications.
> At work I program with java and then in my first simple application (a 
> system to manage todo lists) I've used CLOS but after I've read an 
> article by Paul Grahm about the excessive use of objects 
> (http://www.paulgraham.com/noop.html) I've tried to rewrite the same 
> services using structs.
> Using two strcuts to define todo-item and todo-list I've write 45 lines 
> of code while with two classes in clos they are 65 (you can see the code 
> below) and than perhaps it's better to start with struct and than to use 
> clos only if necessary with more complex functionalities.
> How do you usually start writing your new systems, immediatly using clos 
> or initially with something simpler (structs or lists or clousures) and 
> passing to clos afterwards only if necessary? Which are your criteria?

The most widely taken approach, as far as I understand it, is to use 
DEFCLASS by default and only occasionally use DEFSTRUCT.

DEFCLASS gives you the advantage that you can change a class at runtime. 
  Changing a class means that all existing instances are updated to 
reflect the change. This is very helpful during development and 
sometimes also for deployment. (There have been several threads recently 
about updating a program while it is running as a model for "real-world" 
deployment.)

Furthermore, DEFCLASS is also more flexible in other respects. For 
example, you can adapt the behavior of class by adding methods to 
SLOT-MISSING and SLOT-UNBOUND. When your implementation supports a MOP 
for CLOS, you can go even further and, for example, add persistence more 
or less seamlessly.

DEFSTRUCT doesn't provide any of this - at least, the runtime 
changeability is not prescribed by the ANSI standard. This also means 
that DEFSTRUCT can usually be implemented more efficiently, and it also 
offers some variants by default that you could otherwise "only" 
implement on your own via the MOP in CLOS.

It seems to me that typically, DEFSTRUCT is used as a replacement for 
DEFCLASS only for time critical sections of a program, after having 
profiled the code to identify the real bottlenecks.

Paul Graham is not a friend of object-oriented programming. It can be 
argued that OOP adds overheads to a program that are sometimes 
superfluous. However, if you feel confident with OOP then I don't see 
any real issue here.

On the other hand, it is worthwhile to expand oneself's horizon every 
now and then.


Pascal

-- 
Tyler: "How's that working out for you?"
Jack: "Great."
Tyler: "Keep it up, then."
From: Thomas A. Russ
Subject: Re: object or struct?
Date: 
Message-ID: <ymiad35u2dp.fsf@sevak.isi.edu>
Giannandrea Castaldi <········@tiscali.it> writes:

> 
> Using two strcuts to define todo-item and todo-list I've write 45 lines 
> of code while with two classes in clos they are 65 (you can see the code 
> below) and than perhaps it's better to start with struct and than to use 
> clos only if necessary with more complex functionalities.
> How do you usually start writing your new systems, immediatly using clos 
> or initially with something simpler (structs or lists or clousures) and 
> passing to clos afterwards only if necessary? Which are your criteria?

I usually use CLOS.  Structs are something I only use for very simple
data structures that I don't expect to either be (strongly) hierarchical
or to CHANGE.  Otherwise I would only switch to structs if there were a
demonstrated performance problem.

CLOS gives you a lot more flexibility.  One of the chief benefits is
that you can redefine CLOS classes and still use the old instances.  You
can't really do this with structs.  Now in some ways CLOS is a bit more
verbose, especially since there aren't as many convenient default
settings for generating accessors, etc, but you do get more flexibility
that way.


> Thanks.
> 
> Giannandrea
> 
> CLOS code ----------------------------------
> (in-package cl-todo)
> 
> (defclass todo-item ()
>    ((descripiton :initarg :description
> 		:reader description)
>     (priority :initarg :priority
> 	     :accessor priority)
>     (year :initarg :year
> 	 :accessor year)
>     (month :initarg :month
> 	  :accessor month)
>     (day :initarg :day
> 	:accessor day)
>     (hour :initarg :hour
> 	:accessor hour)
>     (minute :initarg :minute
> 	:accessor minute)))
> 

Instead of this function, I would rather write a method on
initialize-instance that takes care of the defaulting of the arguments.
One could also use :initform on the slots, but for something like a
destructured time argument, that doesn't work very well.

Of course, perhaps the simplest solution is to just store the date/time
information as a Common Lisp universal time.  You can then write
accessor methods that get the value in question out of it.  This is a
design issue on which one could easily go either way.  It pretty much
depends on which forumlation makes the rest of the application easier.

Also, you may want to structure your class a bit differently, namely by
creating a timestamp class which handles all of the time-related
functions and then use this timestamp class as a parent of todo-item.

In that case, you will definitely want to use a method on
initialize-instance rather than a specific function.

> (defun make-todo-item (&key description (priority 5) year month day hour 
> minute)
>    (multiple-value-bind (def-sec def-min def-hour def-day def-month 
> def-year) (get-decoded-time)
>      (make-instance
>       'todo-item
>       :description description	
>       :priority priority
>       :year (if year year def-year)
>       :month (if month month def-month)
>       :day (if day day def-day)
>       :hour (if hour hour def-hour)
>       :minute (if minute minute def-min))))
> 
> (defmethod print-object ((item todo-item) stream)
>    (format stream "<todo-item (~A): ~A - ~A/~A/~A ~A:~A>"
> 	  (priority item)
> 	  (description item)
> 	  (day item) (month item) (year item) (hour item) (minute item)))
> 
> (defmethod date ((item todo-item))
>    (encode-universal-time 0 (minute item) (hour item) (day item) (month 
> item) (year item)))
> 
> [snip]
> Struct code ----------------------------------
> (in-package cl-todo)

The problem with the defstruct code is that the initialization is really
broken.  That is because it has to be split across five separate
invocations of (get-decoded-time).  That means that the date and time
defaults are not initialized from the same time.  

In addition to problems when the calls to (GET-DECODED-TIME) span a
change in the hour or day, there are other reasons why you don't want to
have a distributed defaulting of values.  If someone specifies the
month, day and hour for an item, do you really want to default the
CURRENT minute?  Or would it make more sense to just use zero for the
minute field?  This is the sort of decision that you can only make if
you use a centralized default handling such as you get with CLOS.


> (defstruct (todo-item (:conc-name todo-)
> 		      (:print-function print-todo-item))
>    description
>    (priority 5)
>    (year (sixth (multiple-value-list (get-decoded-time))))
>    (month (fifth (multiple-value-list (get-decoded-time))))
>    (day (fourth (multiple-value-list (get-decoded-time))))
>    (hour (third (multiple-value-list (get-decoded-time))))
>    (minute (second (multiple-value-list (get-decoded-time)))))

> (defun print-todo-item (item stream depth)
>    (format stream "<todo-item (~A): ~A - ~A/~A/~A ~A:~A>"
> 	  (todo-priority item)
> 	  (todo-description item)
> 	  (todo-day item) (todo-month item) (todo-year item) (todo-hour item) 
> (todo-minute item)))
> 
> (defun date (item)
>    (encode-universal-time 0 (todo-minute item) (todo-hour item) 
> (todo-day item)
> 			 (todo-month item) (todo-year item)))
> [snip]




-- 
Thomas A. Russ,  USC/Information Sciences Institute