Variable reference in structures

Discussion of Common Lisp
J.Owlsteam
Posts: 21
Joined: Wed Jul 29, 2015 7:25 am

Variable reference in structures

Post by J.Owlsteam » Fri Aug 14, 2015 4:18 pm

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!

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: Variable reference in structures

Post by edgar-rft » Sat Aug 15, 2015 6:11 am

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:

Code: Select all

(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:

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)
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

J.Owlsteam
Posts: 21
Joined: Wed Jul 29, 2015 7:25 am

Re: Variable reference in structures

Post by J.Owlsteam » Sat Aug 15, 2015 4:01 pm

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:

Code: Select all

(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

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: Variable reference in structures

Post by edgar-rft » Sun Aug 16, 2015 4:27 am

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:

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)))
Now you can write things like:

Code: Select all

(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:

Code: Select all

(make-foo :a 1 :b (+ a c) :c 2) => error: unbound variable C
The reason is just simply:

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)))
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

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: Variable reference in structures

Post by edgar-rft » Sun Aug 16, 2015 5:03 am

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:

Code: Select all

(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:

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."))
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:

Code: Select all

(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:

Code: Select all

(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:

Code: Select all

(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:

Code: Select all

(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:

Code: Select all

(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:

Code: Select all

(make-my-object 123) => #<MY-CLASS {1005E36E93}>
Hmm, this is not very informative, but you can use DESCRIBE to see that really works:

Code: Select all

CL-USER> (describe (make-my-object 123))
#<MY-CLASS {1005E03533}>
  [standard-object]

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

- edgar

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: Variable reference in structures

Post by edgar-rft » Sun Aug 16, 2015 9:31 am

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:

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."))
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:

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)))
The main property of a spreadsheet program is that the result gets updated as soon as one of the input fields changes:

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))))
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:

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))
The mini User Inferface

A global variable holds the *mini* spreadsheet:

Code: Select all

(defparameter *mini* (make-instance 'minisheet))
The MINI-V1 and MINI-V2 functions change the values of the input variables:

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*)))
The MINI-OP function changes the spreadsheet operator:

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*)))
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:

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>
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

J.Owlsteam
Posts: 21
Joined: Wed Jul 29, 2015 7:25 am

Re: Variable reference in structures

Post by J.Owlsteam » Sun Aug 16, 2015 10:59 am

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!

J.Owlsteam
Posts: 21
Joined: Wed Jul 29, 2015 7:25 am

Re: Variable reference in structures

Post by J.Owlsteam » Mon Aug 17, 2015 8:15 am

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!

edgar-rft
Posts: 226
Joined: Fri Aug 06, 2010 6:34 am
Location: Germany

Re: Variable reference in structures

Post by edgar-rft » Mon Aug 17, 2015 6:50 pm

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...

J.Owlsteam
Posts: 21
Joined: Wed Jul 29, 2015 7:25 am

Re: Variable reference in structures

Post by J.Owlsteam » Tue Aug 18, 2015 9:57 am

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

Post Reply