// A.h
class B;
class A {
//...
B* pb;
};
// B.h
class A;
class B {
//...
A* pa;
};
How do we accomplish the same in Common Lisp? I'd rather not take the
types out of the class definitions here...
(defwidget dir-tree-widget ()
(...
(photo-explorer :accessor photo-explorer
:initarg :photo-explorer
:type photo-explorer-widget)))
(defwidget photo-explorer-widget (widget)
(...
(current-dir-tree-widget :accessor current-dir-tree-widget
:initarg :current-dir-tree-widget
:type dir-tree-widget
:initform nil)))
Right now they are sitting in the same file. I imagine the same thing
has to occur in Common Lisp land? Splitting them into 2 files and
somehow forward declaring a class?
> Right now they are sitting in the same file. I imagine the same thing
> has to occur in Common Lisp land? Splitting them into 2 files and
> somehow forward declaring a class?
Hmm. On the extremely rare occasions where I've needed something
like this, I think I've just defined a dummy version of the class
first. I.e.:
(defclass a ()
;; dummy version, completely empty
())
(defclass b ()
;; real version
(..fill inn stuff here..))
(defclass a ()
;; real version
(..fill inn stuff here..))
--
(espen)
Remove the line:
:type dir-tree-widget
you don't earn anything really with it.
(defclass a ()
((b :initarg :b :accessor a-b)))
(defclass b ()
((a :initarg :a :accessor b-a)))
(defmethod print-object ((self a) stream)
(print-unreadable-object (self stream :identity t :type t)
(write `(:b ,(a-b self)) :circle t :stream stream))
self)
(defmethod print-object ((self b) stream)
(print-unreadable-object (self stream :identity t :type t)
(write `(:a ,(b-a self)) :circle t :stream stream))
self)
(setf *print-circle* t)
(let ((a (make-instance 'a))
(b (make-instance 'b)))
(setf (a-b a) b
(b-a b) a)
(list a b))
--> (#1=#<A (:B #2=#<B (:A #1#) #x1D531CF1>) #x1D531CE1> #2#)
--
__Pascal Bourguignon__
It's actually even worse than that. Declaring a slot type is a promise
you make to the compiler, not a constraint that the compiler enforces.
So if you declare a slot type and then ever put something that is not of
that type in that slot (like NIL) then your program can crash.
rg
> So in C++ when class A needs a pointer to class B and likewise class B
> needs the same you just do:
>
> // A.h
> class B;
> class A {
> //...
> B* pb;
> };
>
> // B.h
> class A;
> class B {
> //...
> A* pa;
> };
>
> How do we accomplish the same in Common Lisp? I'd rather not take the
> types out of the class definitions here...
Short answer: It isn't an issue.
Generally you don't use the :type slot option. AFAIK there isn't any
run-time benefit to using types for class objects in Common Lisp
compilers, so one tends not to insert such type declarations.
:TYPE as a slot option is much more useful for numeric types such as
:double-float or fixnum, where optimizations in storage and use become
:possible.
So, one would generally follow Pascal Bourguigno's approach and just not
include a :type slot option. If you insist on having such slots, then
an empty forward definition like Espen Vespre's approach can be taken.
That is pretty much an exact analog of the C++ method you illustrate.
But it really isn't necessary, so what one would tend to do would be
simply do the following, using :documentation if you wanted to document
the type of object held by the slot. Bear in mind that in Common Lisp,
type declarations are promises from the programmer to the compiler, and
are not necessarily enforced by the compiler. So if you want to force
type checking, then you would need to write additional methods to do
that anyway.
(defclass A ()
((b :initarg :b :accessor b :documentation "Holds items of type B")))
(defclass B ()
((a :initarg :a :accessor a :documentation "Holds items of type A")))
--
Thomas A. Russ, USC/Information Sciences Institute
On the other hand, it's not difficult to write quasi-portable code that
checks that the value of a slot is of a given type, if that's really
what you want. But this check is performed at runtime, not at compile
time as in the example above, so there's a performance cost that is
probably not justified in practice. A compromise could be to perform the
check at instance initialization time, using something like the following:
(defmethod initialize-instance :after ((obj class) &rest initargs)
(dolist (slot (mop:class-direct-slots (class-of obj)))
(when (slot-boundp obj (mop:slot-definition-name slot))
(assert (typep (slot-value obj (mop:slot-definition-name slot))
(mop:slot-definition-type slot))))))
If instead you want to perform this check every time you assign a new
value to a slot, then things get hairier...
Alberto
> jrwats <jrw...@gmail.com> writes:
>
>> So in C++ when class A needs a pointer to class B and likewise class B
>> needs the same you just do:
>>
>> // A.h
>> class B;
>> class A {
>> //...
>> B* pb;
>> };
>>
>> // B.h
>> class A;
>> class B {
>> //...
>> A* pa;
>> };
>>
>> How do we accomplish the same in Common Lisp? I'd rather not take the
>> types out of the class definitions here...
> [...]
> But it really isn't necessary, so what one would tend to do would be
> simply do the following, using :documentation if you wanted to document
> the type of object held by the slot. Bear in mind that in Common Lisp,
> type declarations are promises from the programmer to the compiler, and
> are not necessarily enforced by the compiler. So if you want to force
> type checking, then you would need to write additional methods to do
> that anyway.
Also, since they are promises, if you really want to give a type, you
must either provide an :INITFORM with an expression evaluating to that
type, or widden that type to include NIL or the value you give to
:INITFORM.
And of course, you can also make a "forward" declaration of a class,
since you can always redefine (refine) a class later:
(defclass b () ())
(defclass A ()
((b :initarg :b :accessor b
:type b :initform (make-instance 'b)
:documentation "Holds items of type B")))
(defclass B ()
((a :initarg :a :accessor a
:type a :initform (make-instance 'a)
:documentation "Holds items of type A")))
But in this case, these :INITFORM won't work, giving an infinite loop:
when creating an A, we create a B, and to create that B, we need to
create a A, therefore we create a B, ...
Therefore you will need to accept that when you create an instance of
either, it has no instance of the other class in its slot. The type
must be (OR NIL A) or (OR NIL B).
(defclass b () ())
(defclass A ()
((b :initarg :b :accessor b
:type (or nil b) :initform nil
:documentation "Holds items of type B, or NIL")))
(defclass B ()
((a :initarg :a :accessor a
:type (or nil a) :initform nil
:documentation "Holds items of type A, or NIL")))
--
__Pascal Bourguignon__
You cannot rely on all conforming implementations to do something
useful with it, but depending on your implementation (SBCL, CMUCL,
possibly ACL, not sure if Allegro's new declarations-as-assertions
mode applies to slots) you may get something useful from it.
> It's actually even worse than that. Declaring a slot type is a promise
> you make to the compiler, not a constraint that the compiler enforces.
> So if you declare a slot type and then ever put something that is not of
> that type in that slot (like NIL) then your program can crash.
No, it's not (necessarily) a promise, the case where the declaration
and the value don't match is explicitly undefined, which is another
way of saying it's up to the implementation to assign meaning to type
declarations. So depending on the compiler you're using, and what
settings you're using, it might be a promise, it might be an
assertion, or it might be ignored.
In any case, it's probably not the best idea for someone new to the
language, unless they're intentionally limiting what they are doing to
a declarations-as-assertions compiler/setting.
The type declarations should use null instead of nil. Null is the type that
contains only nil.
bob
> Therefore you will need to accept that when you create an instance of
> either, it has no instance of the other class in its slot. The type
> must be (OR NIL A) or (OR NIL B).
If you're dealing with classes rather than structures,
you can just leave the slots unbound, rather than NIL.
Besides, the type (OR NIL A) means the same as A.
You likely meant (OR NULL A) or (OR (EQL NIL) A) instead.