defstruct with :constructor 
Author Message
 defstruct with :constructor



Quote:
>I would like to be able to write:
>(defstruct (triangle :constructor make-triangle)
> ...)

>(defun make-triangle (&key (side1 0.0) (side2 0.0) (side3 0.0)
>                  (area (triangle-area side1 side2 side3)))
>  (make-triangle-system :side1 side1  :side2 side2  :side3 side3 :area :area))

(defstruct (triangle :constructur make-triangle-internal)
  ...)

(defun make-triangle (&key (side1 0.0) (side2 0.0) (side3 0.0)
                      (area (compute-triangle-area side1 side2 side3)))
  (make-triangle-internal :side1 side1  :side2 side2  :side3 side3 :area :area))

(defun compute-triangle-area (s1 s2 s3)
  ...)

Note that the name of the function used to compute the area is *not*
TRIANGLE-AREA.  That's the name of the accessor for the AREA slot that will
be defined automatically by DEFSTRUCT, so you need a different name for the
computation function (well, you *could* use the :CONC-NAME option to
specify a different naming scheme for the accessors, but I think that would
be more confusing to users, and COMPUTE-TRIANGLE-AREA is better for this
function anyway).

--

GTE Internetworking, Powered by BBN, Cambridge, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.



Mon, 18 Sep 2000 03:00:00 GMT  
 defstruct with :constructor

Quote:

> My problem is the following: I have a structure with one of the fields
> being computable from the rest, e.g
> (defstruct triangle
>  (side1 0.0 :type double-float)
>  (side2 0.0 :type double-float)
>  (side3 0.0 :type double-float)
>  (area 0.0 :type double-float))

I usually do:

(defstruct triangle
  (side1 0.0 :type double-float)
  (side2 0.0 :type double-float)
  (side3 0.0 :type double-float)
  (area-internal nil))

(defun triangle-area (triangle)
  (or (triangle-area-internal triangle)
      (setf (triangle-area-internal triangle) ...)))

with appropriate type and/or inline declarations for triangle-area as needed,
as well as either a check-type or declaration of the triangle arg to triangle.
But the basic approach is hopefully clear.

Of course, these approaches assume you neer side-effect side1, side2,
and side3 since you otherwise need to cache the values.  But one nice
thing about the above an side-effecting is that youc an just bash
area-internal back to nil, you don't have to recompute the area on each
change to the triangle, only on each request for its area where it's not
already known.
 --Kent



Mon, 18 Sep 2000 03:00:00 GMT  
 defstruct with :constructor



Quote:
>I get an error: function make-triangle-internal is not defined.

Sorry, I made the same syntax error you did (not to mention a misspelling).  It should be:

(defstruct (triangle (:constructor make-triangle-internal)) ...)

--

GTE Internetworking, Powered by BBN, Cambridge, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.



Mon, 18 Sep 2000 03:00:00 GMT  
 defstruct with :constructor



Quote:
>Specifically: there are cases when make-triangle-internal will be called
>by the system, and a triangle with bad area will be created.  The only
>example I can think of is
>    (read-from-string "#S(triangle side1 1 side2 1 side3 1)")
>this is highly unfortunate, but, apparently, there is no way out.

True.  But normally, #S is used to read the printed representation of a
structure that was created by another call, so it will have its area filled
in.

Your best bet is to use the lazy technique that Kent suggested, where you
compute the area the first time you need it and cache it in the area slot.

Quote:
>Another trouble is that I will be specifying the default values for
>sides twice - in my (defstruct triangle) and (defun make-triangle).

Don't bother specifying them in the (defstruct triangle), since everyone
should be calling make-triangle.

Quote:
>What about permitting the init expressions defstruct to refer to the
>slots themselves, so that they are evaluated recursively, as labels?

Sorry, not available.  You can do that if you define a :constructor with a
fancy lambda list.

--

GTE Internetworking, Powered by BBN, Cambridge, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.



Tue, 19 Sep 2000 03:00:00 GMT  
 defstruct with :constructor

< Another trouble is that I will be specifying the default values for
< sides twice - in my (defstruct triangle) and (defun make-triangle).
<
< What about permitting the init expressions defstruct to refer to the
< slots themselves, so that they are evaluated recursively, as labels?

You could do this, but &aux is evil, unnecessary, and maybe even
misleading (I think I used it wrong anyway). Just thought you might
like to play with it.

(defstruct (triangle
            (:constructor make-triangle
                          (&key (side1 0.0) (side2 0.0) (side3 0.0)
                           &aux (assert (every #'numberp
                                               (list side1 side2 side3))))))
  side1 side2 side3
  (area (+ side1 side2 side3)))

You could always do something like this, or use an auxillery function
as Barry suggested.

(defun assert-all (fn &rest args)
  (if (every fn args) t
      (error "Insert error message here...")))

(defun triangle-make-area (a b c)
  (assert-all #'numberp a b c)
  (* (+ a c) 1/2))

(defstruct (triangle
            (:constructor make-triangle
                          (&key (side1 0.0) (side2 0.0) (side3 0.0))))
  side1 side2 side3
  (area (triangle-make-area side1 side2 side3)))

Actually, I would probably just use this. It's usually pretty easy to
spot an invalid type I think. I always hate when the majority of the
code is checking for something to be wrong.

(defstruct (triangle
            (:constructor make-triangle
                          (&key (side1 0.0) (side2 0.0) (side3 0.0))))
  (side1 :type double-float)
  (side2 :type double-float)
  (side3 :type double-float)
  (area (* (+ side1 side3) 1/2)))

USER(93): (make-triangle :side1 #\r)

Error: EXCL::+_2OP: `#\r' is not of the expected type `NUMBER'
  [condition type: TYPE-ERROR]
[1] USER(94): :current
(+ #\r 0.0)

[1] USER(95): :zoom
Evaluation stack:

   (ERROR TYPE-ERROR :DATUM ...)
 ->(+ #\r 0.0)
   (MAKE-TRIANGLE :SIDE1 #\r)
   (EVAL (MAKE-TRIANGLE :SIDE1 #))
   (TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
   (TPL:START-INTERACTIVE-TOP-LEVEL

        #x811794a>
      #<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP> ...)
[1] USER(96):

Seems like the hard is finding out who called make-triangle (well not
in this case). You should get the general idea. There's a bug in the
latter so you know; doesn't check type of :side2, but I figure, if you
put the declarations there, why check for invalid types? have fun.



Tue, 19 Sep 2000 03:00:00 GMT  
 
 [ 5 post ] 

 Relevant Pages 

1. defstruct option :CONSTRUCTOR

2. Problem with defstruct : constructor &aux

3. defstruct slots (was a free lisp for winnt? (+ a defstruct question))

4. Type-checking and defstruct

5. defstruct SRFI?

6. A defstruct package

7. defstruct and defclass

8. Strange defstruct behavoir (?)

9. DEFSTRUCT: NEED TO RECOMPILE ?

10. sorting on defstruct slot

11. evaluation of defstruct slot initforms

12. removing a DEFSTRUCT structure

 

 
Powered by phpBB® Forum Software