This is a read-only archive of lispforum.com. The forum was locked to new users and posts and is preserved here as static HTML from a database snapshot taken on 2019-09-07.

Variable reference in structures

19 posts · 27682 views

Good evening, I have a simple new-be question about structures, as I am playing around looking how much of an object oriented system I can pull out from them...
So I wuold like to ask, is it possible to define a variable inside of a structure in terms of an other? I mean, to use a variable of a structure to define an other variable into the same structure? Something like the definitions of a LET* or a DO*, to be clear... thanks!

Re: Variable reference in structures

Common Lisp is much older than nearly all other programming languages (only Assembly and Fortran are older) and therefore often has "weird" names for nearly everything. The structure "fields" for example in Common Lisp are called "slots", and I think that is what you mean with "variables".

If I have understood right then your problem looks like this:
(defstruct foo a b c)
(make-foo :a 1 :b 2 :c (+ foo-a foo-b)) => error: unbound variable FOO-A
No matter what you try, the reason why it doesn't work is that the arguments of "make-foo" are evaluated first and the new "foo" structure still doesn't exist at the time when the arguments are evaluated.

But you can do things like:
(defparameter *foo*
  ;; create a FOO structure locally stored in BAR
  ;; with FOO-A = 1, FOO-B = 2, and FOO-C = NIL
  (let ((bar (make-foo :a 1 :b 2)))
    ;; compute FOO-C from FOO-A and FOO-B
    (setf (foo-c bar) (+ (foo-a bar) (foo-b bar)))
    ;; return the fully initialized structure from BAR
    ;; and assign it to *FOO*
    bar))

*foo* => #S(FOO :A 1 :B 2 :C 3)
Structures are meant to be used as templates for stupid data stores. If you need intelligent behaviour you should use classes and methods, where you can define everything yourself.

- edgar

Re: Variable reference in structures

Exactly I meant slots :) and... sure, I don't doubt about classes and methods being the best option... I'm waiting for the book on CLOS to arrive, but in the mean time I was wondering about how much of a class I could obtain from a structure... probably not so much I fear. My thinking was: if I can use slots for both attributes and methods, since I can store functions as data, I won't be so far from the point... but in order to do that it wuold be essential to reference the value of a slot from the definition of an other slot. I wasn't thinking to do that at instatiation time, but inside the structure, at definition time, something like:
(defstruct my-class
    (attr1 0)
    (attr2 (+ attr1 1)))
If I can't do it I wuold have methods incapable of manipulating attributes, so the whole idea wuold fade in forgetfulness :D

Re: Variable reference in structures

J.Owlsteam wrote:My thinking was: if I can use slots for both attributes and methods, since I can store functions as data, I won't be so far from the point.
That's in principle correct, Paul Graham shows in the last chapter of ANSI Common Lisp how to build an C++-like object system with inheritance out of simple Common Lisp vectors (one-dimensional arrays), but it's much more limited than CLOS, it re-invents the wheel with much poorer possibilities, but it's a very funny example to read.

You're right if you think that structures under-the-hood are classes, but the main difference is that with structures the constructor and the slot-acessors are hard-wired and predefined, while in CLOS you have the possibility to define the constructor, inheritance and acessors yourself.

It's possible to overwrite the constructor for a self-defined structure to implement LET* behaviour:
(defstruct foo a b c)

;; IMPORTANT: save the original MAKE-FOO constructor FIRST!
(defparameter *make-foo* (symbol-function 'make-foo))

(defmacro make-foo (&key a b c)
  ;; create local variables for the EVALUATED arguments
  ;; that SHADOW the parameter variables with the same name
  `(let* ((a ,a)
          (b ,b)
          (c ,c))
     ;; call the original constructor
     (funcall *make-foo* :a a :b b :c c)))
Now you can write things like:
(make-foo :a 1 :b 2 :c (+ a b)) => #S(FOO :A 1 :B 2 :C 3)
But you still run into problems if you reference variables that have not specified before. For example, this still won't work:
(make-foo :a 1 :b (+ a c) :c 2) => error: unbound variable C
The reason is just simply:
(defmacro make-foo (&key a b c)
  (let* ((a ,a)    ; 1
         (b ,b)    ; (+ a c) <- A is known, C is still unknown
         (c ,c))
    (funcall *make-foo* :a a :b b :c c)))
For finding a LET* binding order that doesn't produce these problems you need to investigate the values of all arguments before the LET* binding. This is possible, too, but it will take an amount of code that really makes no sense anymore.

I only wanted to demonstrate that it's more difficult to create an object system than just rewriting a simple constructor... :shock:

- edgar

Re: Variable reference in structures

Here you have something to play with until your book arrives:
J.Owlsteam wrote:I wasn't thinking to do that at instatiation time, but inside the structure, at definition time, something like:
(defstruct my-class
    (attr1 0)
    (attr2 (+ attr1 1)))
Common Lisp classes are stupid data containers like structures, the behaviour of objects is tied to the functions using the objects, not to the classes or objects themselves. This means that even with CLOS, the slot initialisation always happens at instatiation time, but I will show an example how to achieve the desired behaviour with Common Lisp and CLOS.

The CLOS class definition would look like this:
(defclass my-class ()
  ((attr1 :initarg :attr1 :initform 0 :accessor my-class-attr1)
   (attr2                 :initform 1 :accessor my-class-attr2))
  (:documentation "MY-CLASS does something."))
DEFCLASS automatically creates methods for the MAKE-INSTANCE and INITIALIZE-INSTANCE functions, that therefore are called a "generic functions" because they can have methods (in contrast to to an ordinary function defined by DEFUN, that can have no methods). See DEFGENERIC how to define your own generic functions.

:initarg :attr1 means that an instance of MY-CLASS is created by:
(make-instance 'my-class :attr1 <value>)
:initform 0 means that if MAKE-INSTANCE is called without the :attr1 keyword argument, the default value for the ATTR1 slot in the new instance shall be 0 (zero).

:accessor my-class-attr1 means that a MY-CLASS-ATTR1 function is automatically created to get read/write access to the ATTR1 slot (like the slot accessor of a structure). In CLOS classes there also can be :reader or :writer functions for read-only or write-only access.

Initializing the ATTR2 Slot

The ATTR2 slot has no :initarg option because its value shall be computed automatically by an :AFTER method of the INITIALIZE-INSTANCE generic function, that you must define yourself. The method definition looks like this:
(defmethod initialize-instance :after ((obj my-class) &key)
  (setf (my-class-attr2 obj) (+ (my-class-attr1 obj) 1)))
:after means that this method shall be called after INITIALIZE-INSTANCE is finished with calling all other methods.

In (obj my-class) the obj is the argument variable like in (defun foo (obj) ...), and my-class means that this method only shall be called if obj is an object of class my-class. In Common Lisp this is called "the OBJ argument is specialized on the MY-CLASS class".

The strange-looking &key at the end of the argument list is necessary because the :AFTER method gets called with all arguments given to the original MAKE-INSTANCE call:
(make-instance 'my-class :attr1 <value>)
This means that :attr1 <value> (if specified) is ignored in the :AFTER method.

The :AFTER method is called after INITIALIZE-INSTANCE is finished with initializing a newly created instance. This means that in contrast to the DEFSTRUCT examples above, at this point the new instance already exists, so we have access to all slots of the new instance. The ATTR1 slot is already initialized, first by the :initform <value> argument in the DEFCLASS definition, then optionally by an :attr1 <value> argument to INITIALIZE-INSTANCE that overwrites the :initform <value> from the DEFCLASS definition.

Now that a new instance is created and the ATTR1 slot in the new instance is initialized, the value for the ATTR2 slot can be computed from the value of the initialized ATTR1 slot without producing an error. The accessor functions work exactly like you already know from DEFSTRUCT, the new instance can be referenced by the obj argument variable of the :AFTER method:
(setf (my-class-attr2 obj) (+ (my-class-attr1 obj) 1))
Writing a Constructor Function

In contrast to DEFSTRUCT, a DEFCLASS definition does not automatically generate a constructor function because there are too many possibilities how classes can be used. So if you don't want to write the full MAKE-INSTANCE call including all keyword arguments every time anew you can write a constructor function like this:
(defun make-my-object (attr1)
  (make-instance 'my-class :attr1 attr1))
It'a good idea to add some code to the constructor function to make sure that the ATTR1 argument has a correct value before a new instance is created.

Now if you call the MAKE-MY-OBJECT constructor function:
(make-my-object 123) => #<MY-CLASS {1005E36E93}>
Hmm, this is not very informative, but you can use DESCRIBE to see that really works:
CL-USER> (describe (make-my-object 123))
#<MY-CLASS {1005E03533}>
  [standard-object]

Slots with :INSTANCE allocation:
  ATTR1  = 123
  ATTR2  = 124
Yeah! :D

- edgar

Re: Variable reference in structures

Because it's raining all day long and because I'm obviously bored to death, here is one of the most ridiculous programs I ever wrote. It's a four-slots mini spreadsheet that is displayed in the REPL's return value. It uses CLOS and :AFTER methods to update the result whenever one of the input slots gets changed.

The class definition has not even :initarg values:
(defclass minisheet ()
  ((op     :initform '+ :accessor minisheet-op)
   (v1     :initform 0  :accessor minisheet-v1)
   (v2     :initform 0  :accessor minisheet-v2)
   (result :initform 0  :accessor minisheet-result))
  (:documentation "Super mini toy spreadsheet."))
I wanted the spreadsheet to be displayed in the REPL's return value, so I added a new method to the built-in Common Lisp PRINT-OBJECT generic function, specialized on MINISHEET objects:
(defmethod print-object ((obj minisheet) stream)
  "Print a minisheet object to the STREAM."
  (format stream "#<MINISHEET ~s ~s ~s = ~s>"
                 (minisheet-v1 obj) (minisheet-op obj)
                 (minisheet-v2 obj) (minisheet-result obj)))
The main property of a spreadsheet program is that the result gets updated as soon as one of the input fields changes:
(defun minisheet-update (obj)
  "Compute and update the minisheet result."
  (setf (minisheet-result obj)
        (funcall (minisheet-op obj) (minisheet-v1 obj)
                                    (minisheet-v2 obj))))
Here is how to add :AFTER methods to the slot-writer methods of the MINISHEET class definition to call the MINISHEET-UPDATE function after a new value has been written into one of the minisheet's OP, V1, or V2 slots:
(defmethod (setf minisheet-op) :after (value (obj minisheet))
  (minisheet-update obj))

(defmethod (setf minisheet-v1) :after (value (obj minisheet))
  (minisheet-update obj))

(defmethod (setf minisheet-v2) :after (value (obj minisheet))
  (minisheet-update obj))
The mini User Inferface

A global variable holds the *mini* spreadsheet:
(defparameter *mini* (make-instance 'minisheet))
The MINI-V1 and MINI-V2 functions change the values of the input variables:
(defun mini-v1 (&optional number)
  "Change or return the first input value in the minisheet."
  (if number
      (progn
        (check-type number real)
        (setf (minisheet-v1 *mini*) number)
        *mini*)
      (minisheet-v1 *mini*)))
(defun mini-v2 (&optional number)
  "Change or return the second input value in the minisheet."
  (if number
      (progn
        (check-type number real)
        (setf (minisheet-v2 *mini*) number)
        *mini*)
      (minisheet-v2 *mini*)))
The MINI-OP function changes the spreadsheet operator:
(defun mini-op (&optional op)
  "Change or return the minisheet operator."
  (if op
      (progn
        (assert (find op (list '+ '- '* '/))
                (op)
                "OP ~s must be one of '+, '-, '*, or '/." op)
        (setf (minisheet-op *mini*) op)
        *mini*)
      (minisheet-op *mini*)))
All three functions return the respective value from the spreadsheet if no argument is given. Note that I do not consider this change of setter/getter behaviour depending on the existence of an argument as really good program design, but it's definitely easier to type interactively in the REPL.

And here is how it works:
CL-USER> *mini*
#<MINISHEET 0 + 0 = 0>

CL-USER> (mini-v1 1)
#<MINISHEET 1 + 0 = 1>

CL-USER> (mini-v2 2)
#<MINISHEET 1 + 2 = 3>

CL-USER> (mini-op '*)
#<MINISHEET 1 * 2 = 2>
I don't know if it's really worth to continue or extend this program, but it was a lot of fun to write. I also wanted to show how to add methods to built-in generic functions and to slot-writer functions that were automatically generated by the DEFCLASS definition.

- edgar

Re: Variable reference in structures

Very very interesting... thanks master :D This CLOS syntax seems to be quite scaring, but also more compact than java in the creation of accessors.... while this concept of methods related to functions... I don't know if it has a java counterpart, but actually I can't get it, I need to investigate these generic functions I guess. Except of that, I've carefully read your fun of the sunday ( :D ) and found it very useful to have a first taste of how CLOS works. Interesting the use of :after... If I saw right, if you have attributes related to each other, you can use it to keep safe the structure of an object if it has been modified withouth the "setter" defined from the programmer. Is that correct?

Maybe I will go through the tutorials you suggested to me to find more about generic functions, as it seems that the word "method" hasn't the same meaning here that it had in my "javaed" mind. In the meantime, I'm playing around trying to develop your idea of macro-building a new constructor, maybe I can get to the point macroing a macro to define generic constructors :D I'm stuck on a little issue but maybe I will open a new topic for that, as a nested parenthesis of this one ( :D ) and then come back here to continue the discussion.

As ever, thanks!

Re: Variable reference in structures

Ok I give up :D The trying was instructive but probably doesn't worth to have more time spent on it, Sonja Keene has arrived, I'll come back to your samples with the new baggage of knowledge!

Re: Variable reference in structures

I only wanted to say: The Keene book explains CLOS *much* better than I can do it here in the limited space of a Lisp-forum text box. I have a copy of that book, too. So if there still are questions...

Re: Variable reference in structures

I appreciate that, your help was much valuable also in this short space so, I'll come back soon to learn from your wisdom :D

Re: Variable reference in structures

Good evening Goheeca and thank for the links. It's quite a coincidence that I came to read your post now, as it's just ten minutes that I've finished to read Sonja about multimethods :D Well, I have to admit to be a little doubtful about them... on the one hand, I like to have the possibility to do the same thing in different ways and so, I'm glad to add this new tool to my collection, however on the other... actually I also think I'll try my best to avoid to use it :D
Maybe it's just due to my habit to java or to my sloppy experience, so please correct me if I'm in error, but I feel much more comfortable with the (I guess) traditional approach: if I have to model the relationship between two objects of different classes, I like to think that there fits the introduction of a third class, whose slots I can fill with the objects. Then, I can define a single method in wich I can call the other objects's methods, comparing them, etc. Finally I will have a single structurated stream and an additional level of abstraction representing the relationship I was modeling.

What do you think about it? Is it just a matter of sake or are there circumstances under wich one approach is preferrable to the other?

Re: Variable reference in structures

Well, don't avoid anything, you'll just use it anyway in a limited way (as "singlemethods") most of the time so let yourself make a decision later on (per project/module). The thing is that Java implements OOP in its own way and from it follows that there are Java specific design patterns, CLOS is different so as the usage.

I don't mean you should abandon what you're familiar with, but rather to acquaint with a different implementation of OOP thoroughly.
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

Re: Variable reference in structures

Sure sure you're right, at this purpose I'm seeking for different opinions. On a first look, it seems that as the number of specializers will increment, I'll also have an exponential growth of the methods to define but, probably, it is possibile to more efficently combine the two approaches and avoid it. Well, in any case to not fossilize on Java patterns is a good advice, I'll contine my way trough the book ;)

Re: Variable reference in structures

There are other possibilities, you can write a macro which would cover the combinations or create your own subclass of generic-function* which would deal with the arguments by sorting of those or whatever. I'd like just to point out another angle of view.

* With the help of MOP and closer-mop is the library unificating that API among the CL implementations.
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

Re: Variable reference in structures

In Common Lisp, using multimethods is only one option of many. You do not need to use them if you don't want, but they can help in some situations. In contrast to most other programming languages, in Common Lisp there is no paradigm like "you must solve things in one specific way because there is no other", instead there are often many different ways how the same problem can be solved, what makes learning Lisp a bit complicated.
J.Owlsteam wrote:It seems that as the number of specializers will increment, I'll also have an exponential growth of the methods to define.
That's correct and also the more specialized arguments per method, the slower the entire dispatch machinery will become. Both are things to consider when you need to decide what's the best way to solve a problem. But usually, what's the "best way" depends on the problem, not so much on the programming language, where languages that offer more ways to do things have better chances to solve a broader range of problems.

It takes some time (usually several years) and lots of exploration and experimentation until you find your way how *you* would like to work with Lisp. This is the reason for the partially justified myth that "the Common Lisp community never ever can agree on anything at all", just simply because many people have "their own way" how they use Lisp. The more ways exist how a problem could be solved, the more disagreemant about the "best way" will be the inevitable consequence.

I'm not such a nerd who thinks that Common Lisp is the one and only programming language and all other languages are sh*t. But unfortunately my Java knowledge is rather poor, Goheeca seems to know Java better than me.

- edgar

Last edited by edgar-rft on , edited 1 time in total.

Re: Variable reference in structures

I'd say Java made a big step with Java 8 and loosened the paradigm.
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

Re: Variable reference in structures

Well I have to admit I yet have a little experience in Java too... but while it could seem to be a contradiction, at the moment I'm loving both the languages for quite opposite reasons, one for the rigid structure and the other for the freedom of actually "do what you think", or almost... maybe, my concern begins when this freedom becomes "too much", and some apects are left to be defined by the implementation, while I wuold ever like to have a standard reference... well, but maybe this will be the matter for an other topic :)

Re: Variable reference in structures

edgar-rft wrote:
J.Owlsteam wrote: ...

It's possible to overwrite the constructor for a self-defined structure to implement LET* behaviour:
(defstruct foo a b c)

;; IMPORTANT: save the original MAKE-FOO constructor FIRST!
(defparameter *make-foo* (symbol-function 'make-foo))

(defmacro make-foo (&key a b c)
  ;; create local variables for the EVALUATED arguments
  ;; that SHADOW the parameter variables with the same name
  `(let* ((a ,a)
          (b ,b)
          (c ,c))
     ;; call the original constructor
     (funcall *make-foo* :a a :b b :c c)))
Now you can write things like:
(make-foo :a 1 :b 2 :c (+ a b)) => #S(FOO :A 1 :B 2 :C 3)
But you still run into problems if you reference variables that have not specified before. For example, this still won't work:
(make-foo :a 1 :b (+ a c) :c 2) => error: unbound variable C
The reason is just simply:
(defmacro make-foo (&key a b c)
  (let* ((a ,a)    ; 1
         (b ,b)    ; (+ a c) <- A is known, C is still unknown
         (c ,c))
    (funcall *make-foo* :a a :b b :c c)))
For finding a LET* binding order that doesn't produce these problems you need to investigate the values of all arguments before the LET* binding. This is possible, too, but it will take an amount of code that really makes no sense anymore.

I only wanted to demonstrate that it's more difficult to create an object system than just rewriting a simple constructor... :shock:

- edgar
Actually, without going the CLOS route, DEFSTRUCT gives you plenty of leeway to achieve what the OP asked for, without "saving" the constructor.
CL-USER 1 > (defstruct (foo (:constructor make-foo (a b &aux (c (+ a b)))))
   a
   b
   c)
FOO

CL-USER 2 > (make-foo 2 3)
#S(FOO :A 2 :B 3 :C 5)
The :constructor option to DEFSTRUCT is quite flexible. Not only it allows you to bypass the standard constructor generation, it allows you to have different (more than one) constructors for your struct. I blogged about it some time ago http://within-parens.blogspot.it/2011/0 ... ricks.html.

Cheers

Marco
Marco Antoniotti