Variable reference in structures
-
- Posts: 21
- Joined: Wed Jul 29, 2015 7:25 am
Variable reference in structures
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!
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:
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:
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
If I have understood right then your problem looks like this:
Code: Select all
(defstruct foo a b c)
(make-foo :a 1 :b 2 :c (+ foo-a foo-b)) => error: unbound variable FOO-A
But you can do things like:
Code: Select all
(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)
- edgar
-
- Posts: 21
- Joined: Wed Jul 29, 2015 7:25 am
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:
If I can't do it I wuold have methods incapable of manipulating attributes, so the whole idea wuold fade in forgetfulness 

Code: Select all
(defstruct my-class
(attr1 0)
(attr2 (+ attr1 1)))

Re: Variable reference in structures
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.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.
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:
Code: Select all
(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)))
Code: Select all
(make-foo :a 1 :b 2 :c (+ a b)) => #S(FOO :A 1 :B 2 :C 3)
Code: Select all
(make-foo :a 1 :b (+ a c) :c 2) => error: unbound variable C
Code: Select all
(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)))
I only wanted to demonstrate that it's more difficult to create an object system than just rewriting a simple constructor...

- edgar
Re: Variable reference in structures
Here you have something to play with until your book arrives:
The CLOS class definition would look like this:
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:
: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:
: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:
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:
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:
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:
Hmm, this is not very informative, but you can use DESCRIBE to see that really works:
Yeah! 
- edgar
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.J.Owlsteam wrote:I wasn't thinking to do that at instatiation time, but inside the structure, at definition time, something like:
Code: Select all
(defstruct my-class (attr1 0) (attr2 (+ attr1 1)))
The CLOS class definition would look like this:
Code: Select all
(defclass my-class ()
((attr1 :initarg :attr1 :initform 0 :accessor my-class-attr1)
(attr2 :initform 1 :accessor my-class-attr2))
(:documentation "MY-CLASS does something."))
:initarg :attr1 means that an instance of MY-CLASS is created by:
Code: Select all
(make-instance 'my-class :attr1 <value>)
: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:
Code: Select all
(defmethod initialize-instance :after ((obj my-class) &key)
(setf (my-class-attr2 obj) (+ (my-class-attr1 obj) 1)))
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:
Code: Select all
(make-instance 'my-class :attr1 <value>)
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:
Code: Select all
(setf (my-class-attr2 obj) (+ (my-class-attr1 obj) 1))
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:
Code: Select all
(defun make-my-object (attr1)
(make-instance 'my-class :attr1 attr1))
Now if you call the MAKE-MY-OBJECT constructor function:
Code: Select all
(make-my-object 123) => #<MY-CLASS {1005E36E93}>
Code: Select all
CL-USER> (describe (make-my-object 123))
#<MY-CLASS {1005E03533}>
[standard-object]
Slots with :INSTANCE allocation:
ATTR1 = 123
ATTR2 = 124

- 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:
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:
The main property of a spreadsheet program is that the result gets updated as soon as one of the input fields changes:
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:
The mini User Inferface
A global variable holds the *mini* spreadsheet:
The MINI-V1 and MINI-V2 functions change the values of the input variables:
The MINI-OP function changes the spreadsheet operator:
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:
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
The class definition has not even :initarg values:
Code: Select all
(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."))
Code: Select all
(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)))
Code: Select all
(defun minisheet-update (obj)
"Compute and update the minisheet result."
(setf (minisheet-result obj)
(funcall (minisheet-op obj) (minisheet-v1 obj)
(minisheet-v2 obj))))
Code: Select all
(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))
A global variable holds the *mini* spreadsheet:
Code: Select all
(defparameter *mini* (make-instance 'minisheet))
Code: Select all
(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*)))
Code: Select all
(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*)))
Code: Select all
(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*)))
And here is how it works:
Code: Select all
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>
- edgar
-
- Posts: 21
- Joined: Wed Jul 29, 2015 7:25 am
Re: Variable reference in structures
Very very interesting... thanks master
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 (
) 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
I'm stuck on a little issue but maybe I will open a new topic for that, as a nested parenthesis of this one (
) and then come back here to continue the discussion.
As ever, thanks!


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


As ever, thanks!
-
- Posts: 21
- Joined: Wed Jul 29, 2015 7:25 am
Re: Variable reference in structures
Ok I give up
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...
-
- Posts: 21
- Joined: Wed Jul 29, 2015 7:25 am
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