How to define a structure in lisp with arbitrary number of args?
Asked Answered
S

3

6

I'm trying to define a structure with some properties I know I want, and an arbitrary number of other properties that aren't necessary for the basic structure.

(defstruct (node (:type list)) label [other args here])

I know in a function you can do:

(defun foo (arg1 &rest args) ...)

Is there some sort of &rest equivalent for defstruct?

I'm just learning lisp, so I have a feeling that I'm missing something. If there isn't an &rest equivalent, any ideas on how I might go about this? Thanks in advance!

Searchlight answered 9/7, 2013 at 17:18 Comment(0)
A
6

It's not clear exactly what you're looking for. The default case for structures is a record type with a fixed number of slots, each of which has a name, and is accessible through a function generated by the defstruct macro. E.g., once you've done

(defstruct node 
  label)

you can access a node's label with node-label and get fast lookup time (since it's typically just an index into a memory chunk). Now, as you're doing, you can opt to use lists as the implementation of structures, in which case node-label is just an alias for car or first.

(defstruct (node (:type list))
  label)

CL-USER> (make-node :label 'some-label)
(SOME-LABEL)
CL-USER> (node-label (make-node :label 'some-label))
SOME-LABEL
CL-USER> (first (make-node :label 'some-label))
SOME-LABEL
CL-USER> (car (make-node :label 'some-label))

If you're looking for arbitrary list-based key value pairs, you probably want a property list, for which Common Lisp contains some convenience functions.

If you want to have a structure that also contains a property list, you could add a special constructor populate that list. For instance,

(defstruct (node (:type list)
                 (:constructor make-node (label &rest plist)))
  label
  plist)

CL-USER> (make-node 'some-label :one 1 :two 2)
(SOME-LABEL (:ONE 1 :TWO 2))
CL-USER> (node-plist (make-node 'some-label :one 1 :two 2))
(:ONE 1 :TWO 2)
Adara answered 9/7, 2013 at 18:15 Comment(3)
Thanks, this is almost exactly what I'm looking for. But is there any way to have the property list, as you put it, as just other slots in the node? In other words, is there a way to do (make-node 'some-label :one 1 :two 2) => (SOME-LABEL :ONE 1 :TWO 2), instead of (SOME-LABEL (:ONE 1 :TWO 2))?Searchlight
In short, no. Rainer's answer goes into more depth about structures in general, but the point is that structures have a fixed number of slots. Even though you can use defstruct to create “poor-man's records” with the (:type list) option, you're still only going to have a fixed number of slots (which means the list has a fixed number of elements). It really sounds like you just want a property list.Adara
Yeah, reading through Rainer's answer, I think that's more firepower than I need for what I'm doing. Thanks for the advice, guys!Searchlight
L
9

In Common Lisp, structures are thought as rigid and low-level records. They don't have fancy dynamic features.

What you can do with structures is to defined a new structure type which inherits from another one. There is single inheritance available.

To handle dynamic extensibility, a typical way is to add a property list slot to a structure. See the answer of Joshua.

Then there is the Common Lisp Object System, which provides multiple inheritance and you can change classes at runtime. So you can add a slot to a class and the instances of that class update themselves. You can also change the class of an object and slots may get added or deleted. Still, typically all instances of a class will have the same set of slots. Again, one sees that a slot with a property list can be added and used for extensibility.

There are other object systems for Common Lisp, which can easily add slots on a per-instance base. But it is usually too much to use them just for that, since they are quite a bit more powerful.

With CLOS and the Meta-object protocol one can try to hide it. Here I am using LispWorks:

We define a mixin class for our properties:

(defclass property-mixin ()
  ((plist :initform nil))
  #+lispworks
  (:optimize-slot-access nil))

Setting and reading the properties:

(defmethod set-property ((object property-mixin) key value)
  (setf (getf (slot-value object 'plist) key) value))

(defmethod get-property ((object property-mixin) key)
  (getf (slot-value object 'plist) key))

Now we write methods to make SLOT-VALUE accepting our property names:

(defmethod (setf clos:slot-value-using-class)
       (value (class standard-class) (object property-mixin) slot-name)
  (declare (ignorable class)) 
  (if (slot-exists-p object slot-name)
      (call-next-method)
    (progn
      (set-property object slot-name value)
      value)))

(defmethod clos:slot-value-using-class ((class standard-class)
                                        (object property-mixin)
                                        slot-name)
  (declare (ignorable class))
  (if (slot-exists-p object slot-name)
      (call-next-method)
    (get-property object slot-name)))

Example. We define an automobile class with two slots:

(defclass automobile (property-mixin)
  ((company :initarg :company)
   (motor :initarg :motor))
  #+lispworks
  (:optimize-slot-access nil))

Now an instance:

CL-USER 45 > (setf a6 (make-instance 'automobile :company :audi :motor :v6))
#<AUTOMOBILE 402005B47B>

We can get a normal slot value:

CL-USER 46 > (slot-value c1 'motor)
:V6

Let's write to a slot which does not exist, but will be added to our property list:

CL-USER 47 > (setf (slot-value a6 'seats) 4)
4

We can get the value back:

CL-USER 48 > (slot-value c1 'seats)
4
Liss answered 9/7, 2013 at 19:14 Comment(1)
> There are other object systems for Common Lisp, which can easily add slots on a per-instance base. But it is usually too much to use them just for that, since they are quite a bit more powerful. Can you please give a pointer to some? I am interested.Gilbart
A
6

It's not clear exactly what you're looking for. The default case for structures is a record type with a fixed number of slots, each of which has a name, and is accessible through a function generated by the defstruct macro. E.g., once you've done

(defstruct node 
  label)

you can access a node's label with node-label and get fast lookup time (since it's typically just an index into a memory chunk). Now, as you're doing, you can opt to use lists as the implementation of structures, in which case node-label is just an alias for car or first.

(defstruct (node (:type list))
  label)

CL-USER> (make-node :label 'some-label)
(SOME-LABEL)
CL-USER> (node-label (make-node :label 'some-label))
SOME-LABEL
CL-USER> (first (make-node :label 'some-label))
SOME-LABEL
CL-USER> (car (make-node :label 'some-label))

If you're looking for arbitrary list-based key value pairs, you probably want a property list, for which Common Lisp contains some convenience functions.

If you want to have a structure that also contains a property list, you could add a special constructor populate that list. For instance,

(defstruct (node (:type list)
                 (:constructor make-node (label &rest plist)))
  label
  plist)

CL-USER> (make-node 'some-label :one 1 :two 2)
(SOME-LABEL (:ONE 1 :TWO 2))
CL-USER> (node-plist (make-node 'some-label :one 1 :two 2))
(:ONE 1 :TWO 2)
Adara answered 9/7, 2013 at 18:15 Comment(3)
Thanks, this is almost exactly what I'm looking for. But is there any way to have the property list, as you put it, as just other slots in the node? In other words, is there a way to do (make-node 'some-label :one 1 :two 2) => (SOME-LABEL :ONE 1 :TWO 2), instead of (SOME-LABEL (:ONE 1 :TWO 2))?Searchlight
In short, no. Rainer's answer goes into more depth about structures in general, but the point is that structures have a fixed number of slots. Even though you can use defstruct to create “poor-man's records” with the (:type list) option, you're still only going to have a fixed number of slots (which means the list has a fixed number of elements). It really sounds like you just want a property list.Adara
Yeah, reading through Rainer's answer, I think that's more firepower than I need for what I'm doing. Thanks for the advice, guys!Searchlight
T
2

I thought this would be worth a separate reply rather then a comment, so here goes:

Some times, when you think you need a structure or an object, but you have some special requirements that these entities don't fulfil, perhaps this is because what you actually needed is some different data structure? Objects or structs are good when some conditions are met, one such condition is that the slots are statically known - this allows the compiler to better reason about the code, which is both good for optimization and error reporting.

On the other hand, there are data structures. Some provided with the language standard library, others added on top of it. Here's one library that provides many of them: http://cliki.net/cl-containers but there are even more for special cases.

Now, I will argue that using a structure such as list, array, some sort of a tree etc is better then trying to extend objects or structs to allow adding slots dynamically. This is because usually we expect the time to access a slot to be negligible. That is we expect it to be O(1). This is what normally happens regardless of the number of slots an object has. Now, when you are using a list underneath you are making it O(n), while you keep the same semantics! You could, of course, use a hash-table to make it O(1) (although this will be still generally slower then slot access), but then you will have some other unexpected behaviour, such as nil returned when slot doesn't exist instead of the regular error etc.

I don't think that extending objects in such way is a common practice in CL, this is probably why other responses don't discourage you from doing that. I know less of CL then other respondents, but I've had a good deal of grief with this kind of manipulations in another language, where this is common and usually discouraged.

Thermodynamics answered 10/7, 2013 at 6:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.