Destructive set-assoc with FOOP

Started by Jeff, June 03, 2008, 09:50:11 AM

Previous topic - Next topic

Jeff

I have a foop class that uses an association list instead of positional indexes for its slots.  I am attempting to write a context macro to destructively set the value of one of those slots using set-assoc, like:


(define-macro (Foo:set)
  (letex ((inst (args 0))) (set-assoc (inst 'key) (args 1))))


When I evaluate (:set Foo-instance), it appears to be passing a copy of the list to the macro, rather than the symbol, Foo-instance, as would happen typically with a macro.  Any ideas on how to get around this?  I don't want to have to make every destructive operation require the caller to remember to save the value of the return.
Jeff

=====

Old programmers don\'t die. They just parse on...



http://artfulcode.net\">Artful code

m i c h a e l

#1
Hi Jeff,



I gave up on mutable objects in the very early stages of my FOOP explorations. The implementations I tried were complicated and failed to work. In time, I came to appreciate the simplicity of immutable objects.



Still, I know what you mean by not wanting to be forced to remember to set the result each time. This is something I forget to do often, in fact. One technique I found useful was to set the objects returned by methods to a global variable (I use $obj).


(define (Class:Class) (cons (context) (args)))

(new Class 'Book)

(define (Book:check-out b) (set-assoc (b 'status) '(status "Out")) (set '$obj b))

(define (Book:check-in b) (set-assoc (b 'status) '(status "In")) (set '$obj b))

(:check-out (Book "Catcher in the Rye" "J.D. Salinger" '(status "In")))

$obj => (Book "Catcher in the Rye" "J.D. Salinger" (status "Out"))

(:check-in $obj)

$obj => (Book "Catcher in the Rye" "J.D. Salinger" (status "In"))


We're really just moving the set into the method itself. Also, we're limited to working with only one object at a time, and we have to remember to set the global object in all our methods. But it does remove the need to set the result each time.



m i c h a e l

Jeff

#2
I can't easily use a global variable in my code.  The code will be operating on many thousands of instances all at once.



Tell me, is the context the same for all instances?  Or does it clone the context as in 'new with each instance?
Jeff

=====

Old programmers don\'t die. They just parse on...



http://artfulcode.net\">Artful code

m i c h a e l

#3
Quote from: "Jeff"Tell me, is the context the same for all instances? Or does it clone the context as in 'new with each instance?


I'm not completely sure I understand your question, but I'll just say something and hope it ends up being an answer :-)



Instances are lists[1]. There are two ways contexts relate to objects: as an object's method holder, and as literal containers of objects:


(define (Class:Class) (cons (context) (args)))

(new Class 'Book)

(context 'Library)

(set 'book (Book "Catch 22" "Joesph Heller"))

(context MAIN)

Library:book => (Book "Catch 22" "Joesph Heller")


We've created a context named Book and Library. Library holds a Book object in the book variable. At this point, we can say the book is in the context of Library and the book's methods are in the context of Book.



Does this help you at all?



m i c h a e l



[1] In FOOP, the term instance is probably a misnomer. The reason is that, unlike OOP, FOOP separates the methods and data representation. So a class isn't a template for making objects but is simply a method container. Our objects still encapsulate both methods and data (through its pointer to its class context), but they are still structurally different from classes.

Jeff

#4
No, you didn't understand my question :)



You can create a prototype of a context by doing:


(new Class 'Foo)

That copies Class' tree to Foo.  If a FOOP method were to do:


(define (Foo:bar inst) (context (first inst) "bar" "baz"))

...that method would overwrite itself.  What I wanted to know was if the context that is the car of the instance list is a copy of the Foo instance, with its own internal tree, or the Foo instance itself.  One would mean that all variables stored in the context itself are static/class variables.  Changes to one are reflected in all instances immediately.



The other option is that they are anonymous prototypes of the original context, and like the previous, prototype-based OOP in newLISP using contexts, changes to one don't affect the others.
Jeff

=====

Old programmers don\'t die. They just parse on...



http://artfulcode.net\">Artful code

m i c h a e l

#5
Quote from: "Jeff"What I wanted to know was if the context that is the car of the instance list is a copy of the Foo instance, with its own internal tree, or the Foo instance itself.




Careful, it sounds like you're confusing classes with objects here. Foo is a class (a collection of methods). Class is also a class, which is defined so we can inherit its default constructor. When we write (new Class 'Foo), we're defining a new class, not an object.


Quote from: "Jeff"One would mean that all variables stored in the context itself are static/class variables. Changes to one are reflected in all instances immediately.




Yes, variables within classes act like class variables. Objects contain only their own data; they share the methods (and class variables) through their class pointer.



The uniqueness is contained within the object while the general, shared behavior and state are contained inside the class. The only thing that distinguishes a class from a regular context is that you intend to use the context with objects.


(define (Foo:bar? f) (= (f 1) "bar"))

(:bar? '(Foo "bar" "baz")) => true  ; notice the use of the quote. necessary without a constructor

(:bar? '(Foo "baz")) => nil


I probably haven't answered your question again ;-)



m i c h a e l

Jeff

#6
No, and I think you need some education on what contexts used to be in newlisp.



Previously, if I wanted to write in an object-oriented style:


(context 'Point)
(setq x 0 y 0) ; default slot values

(define (print-point)
  (println (format "(%d, %d)" x y)))

(context MAIN)
(new Point 'my-point)
(setq my-point:x 4 my-point:y 12)
(my-point:print-point) ; => prints "(4, 12)"


Now, if something changed in the context Point, it would not change in my-point.  This is prototype-based OO, just as in Javascript.



Therefore, if I wanted to implement a counter using a closure, I could do so like this:


(context 'Counter)

(define (Counter:Counter ctx (start-from 0))
  (new (context) ctx)
  (setq ctx (eval ctx))
  (setq ctx:current start-from))

(define (increment (n 1)) (inc 'current n))

(define (decrement (n 1)) (dec 'current n))

(context MAIN)

(Counter 'counter 100)
(counter:increment 10)
(counter:increment 12)
(println counter:current) ; => 122


I'm not confusing classes and instances :).  I was just wondering if the context call in the Class functor was in fact cloning the context.  I had once seen a post by Lutz hinting at (context) creating anonymous contexts in local scopes that got me wondering.
Jeff

=====

Old programmers don\'t die. They just parse on...



http://artfulcode.net\">Artful code

Lutz

#7
QuoteI was just wondering if the context call in the Class functor was in fact cloning the context.


Yes 'new' makes a copy of a context, a new namespace with a new set of new symbols in it, which have the same name and a copy of the contents of the original. If symbols already exist, their context will not be overwritten.


Quotehad once seen a post by Lutz hinting at (context) creating anonymous contexts in local scopes that got me wondering


No anonymous context creation is possible. In older versions you could create a context on a local symbol (local, let, letn, lambda), but I am in the process of eliminating this possibility. Currently it will hang in many situations or give an error message. It should not be done.



Contexts are quick to create, but expensive to delete in larger programs, because of reference checking of symbols in the whole cell space. Because of this, contexts should be only used as fairly static objects, created once and living until the program exits.



Michael's FOOP solves this dilemma by putting the object into a normal lisp list and reserving the class as a method collector. The first member of the object list is the symbol of the class it belongs too (*). This way objects can be anonymous and are efficiently memory-managed like any other newLISP object. Objects come and go but the class as a method container stays.



Putting class variables into that class context, e.g. to keep track of created objects is a method Java and many other OO systems use too. That is what class variables are for, to keep track of attributes of the whole class.





(*) I am repeating what you already know, but I am trying to make this  post to a wider audience.

pda

Re:
#8
Sorry to bring up to life such an older post but I think it's interesting to discuss it a bit further


Quote from: Jeff post_id=13069 time=1212583186 user_id=354


Previously, if I wanted to write in an object-oriented style:


(context 'Point)
(setq x 0 y 0) ; default slot values

(define (print-point)
  (println (format "(%d, %d)" x y)))

(context MAIN)
(new Point 'my-point)
(setq my-point:x 4 my-point:y 12)
(my-point:print-point) ; => prints "(4, 12)"


Now, if something changed in the context Point, it would not change in my-point.  This is prototype-based OO, just as in Javascript.


No, an 'object' defined in such a way is not a prototype-based OO at all.  That my-point 'object' has not a refererence to its class neither its prototype, not only it is not a prototyped object but it is not even an object,  by definition an object knows the class it belongs to and this object doesn't know so it's not an object,  in prototype-based OO there's no class, everyting is an object and you create a new object from another object as prototype, so the new created object has a reference to its prototype, the object used as model for creation,  this my-point 'object' has no reference to its prototype so it is not a protoype-based object.  The only reason to call it an object is by the fact it's a data structure that holds data and behaviour.



By contrast, a FOOP object is a real object since it has a reference to its class and encapsulates data and behaviour.


Quote from: Jeff post_id=13069 time=1212583186 user_id=354
Therefore, if I wanted to implement a counter using a closure, I could do so like this:


(context 'Counter)

(define (Counter:Counter ctx (start-from 0))
  (new (context) ctx)
  (setq ctx (eval ctx))
  (setq ctx:current start-from))

(define (increment (n 1)) (inc 'current n))

(define (decrement (n 1)) (dec 'current n))

(context MAIN)

(Counter 'counter 100)
(counter:increment 10)
(counter:increment 12)
(println counter:current) ; => 122



with this code you are using a context to represent an object, that's ok, the problem is you are pretending the object is also an instance of a class, and that's not the case, you are using context to represent both classes and objects, an pretending Counter is a (class) constructor for instance objects of that class, which is not. With


(Counter 'counter 100)

you're pretending to create a new instance counter of class Counter ,  both (class and instance) represented as contexts.  

Pretending this means instance counter knows about its class, Counter, but this is not the case because context counter is a copy of context Counter (thus including all its symbols, same data and functions) but have no reference to Counter context and no idea about a Counter (i.e. a symbol with its class name).



In other ways, let's suppose we have a function returning the class name:


(define (classname) (context))

this function works for the class:  (Counter:classname)  => Counter  but not for the instance  (counter:classname) => counter  when it should return Counter as well, and there's no way to write a function in the instance (context counter) that returns its classname because it has no way to know what class it was copied from


Quote from: Jeff post_id=13069 time=1212583186 user_id=354
I'm not confusing classes and instances :).


yes I think you are ;-)





Anyway as you sure know, if you want to get a closure you don't need all that structure, simply use the context as is:



(context 'Counter)

(define (start  (n 0))  (set 'current n))
(define (increment (n 1)) (inc current n))
(define (decrement (n 1)) (dec current n))

(context MAIN)
(Counter:start 100)
(Counter:increment 10)
(Counter:increment 12)
(println Counter:current) ; => 122