More Mutable FOOP

Started by m i c h a e l, July 09, 2009, 08:50:40 PM

Previous topic - Next topic

m i c h a e l

In my continued experiments with mutability in FOOP, I've discovered another technique that simplifies and unifies the previous system. It's done without the use of symbols stored in the MAIN context and doesn't require two types of objects (mutable and immutable).



The earlier technique required both kinds of objects because the contained objects needed to be immutable. Only the outside object could be mutable. This outer object controlled all of the little immutable objects within itself. If you wanted your outer object to become a part of another object—thus becoming an inner object—you would need to convert the formerly mutable object into an immutable one.



While not egregious, this complexity does make that technique less attractive. So on I continued into the mutable FOOP unknown.



Then one day, I began to store the object results of all methods (including constructors) in a variable within the appropriate classes. For example, sending :+ to two Points would not only give you the result, but it would store that result in Point:obj, as well. This would also allow multiple objects of different classes to be returned (removing a jar lid would leave the jar in Jar:obj and the lid in Lid:obj).



So how does this new way work? Like this:



First, we extend . . . oh wait. I haven't shown you the extend macro yet.


(set (global 'extend) (lambda-macro (sig)
(eval
(cons 'define (cons sig
(expand (args) (list (list (sig 0) (eval (sig 0)))))
))
)
))


So what does this code allow us to do? Here's an example:


> (new Class 'Point)
Point
> (Point)
(Point)
> (extend (Point:Point (x 0) (y 0)) (Point:Point x y))
(lambda ((x 0) (y 0)) ((lambda () (cons (context) (args))) x y))
> (Point)
(Point 0 0)
> (Point 10)
(Point 10 0)
> (Point 10 20)
(Point 10 20)
> (Point 10 20 30)
(Point 10 20)
> ; or just regular functions (not built-ins)
> (define (f n) (* n n))
(lambda (n) (* n n))
> (f 5 5)
25
> (extend (f n (n2 0)) (+ (f n) n2))
(lambda (n (n2 0)) (+ ((lambda (n) (* n n)) n) n2))
> (f 5 5)
30
> ; you can keep going, but be careful
> (extend (f n (n2 0) (n3 0)) (+ (f n n2) (* n3 2)))
(lambda (n (n2 0) (n3 0)) (+ ((lambda (n (n2 0)) (+ ((lambda (n) (* n n)) n) n2))
   n n2)
  (* n3 2)))
> (f 5 5)
30
> (f 5 5 5)
40
> (f 5)
25
> _


With this piece in our toolbox, we can continue on.



First, we extend . . . just a minute. We need one more function before we can extend Class:Class. A function called obj that gets and sets a class's obj variable:


(set (global 'obj) (fn (class that) (if that (setq class:obj that) class:obj)))


While this function is not strictly necessary, it does combine getting and setting, so it hides a little bit of detail (and as we'll see later, obj can be extended to handle multiple named objects).



With this, we can finally extend Class:Class:


(extend (Class:Class) (obj (context) (apply Class:Class (args))))


Now, we can use objects this way:


> (new Class 'Author)
Author
> (extend (Author:Author (last-name "") (first-name "")) (Author:Author last-name first-name))
(lambda ((last-name "") (first-name "")) ((lambda () (obj (context) (apply (lambda
      ()
      (cons (context) (args)))
     (args)))) last-name first-name))
> (Author)
(Author "" "")
> (Author "Heller" "Joseph")
(Author "Heller" "Joseph")
> (obj Author)
(Author "Heller" "Joseph")


Just one more piece needs to be redefined for the whole thing to work easily, and that's the access method we defined previously for the now-abandoned use of symbols:


(define (Class:access it idx value allow-nil?)
(if (or value allow-nil?)
(begin (setf (it idx) value) (obj (it 0) it))
(it idx)
)
)


There are only subtle changes made to :access to account for the new technique.



With this final piece in place, we can write Author's accessors:


(define (Author:last-name it last-name) (:access it 1 last-name))

(define (Author:first-name it first-name) (:access it 2 first-name))


Now, as long as we use the object stored in the class variable obj (through the matching obj function), we can treat objects as though they are mutable:


> (Author "" "Jerome")
(Author "" "Jerome")
> (:last-name (object Author) "Salinger")
(Author "Salinger" "Jerome")
> (:first-name (obj Author) "J.D.")
(Author "Salinger" "J.D.")
> (define (Author:string it) (string (:first-name it) " " (:last-name it)))
(lambda (it) (string (: first-name it) " " (: last-name it)))
> (:string (obj Author))
"J.D. Salinger"
> _


So far, I have found this to be quite natural in use.



For working with multiple objects, as mentioned earlier, the obj function could be extended to accommodate named multiple objects. In the following scheme, we use a Tree called Obj to store the objects:


(new Tree 'Obj)

(set (global 'obj) (fn (a b)
(if
(string? a) (if (object? b) (Obj a b) (Obj a))
(object? a) (Obj b a)
(string? b) (Obj b a:obj)
(a:? b) (setq a:obj b)
(nil? b) a:obj
)
))


Now we can do the following:


> (Author "Heller" "Joseph")
(Author "Heller" "Joseph")
> (obj Author "jh")
(Author "Heller" "Joseph")
> (obj (Author "Serling" "Rod") "rs")
(Author "Serling" "Rod")
> (obj "rs")
(Author "Serling" "Rod")
> (obj "jh")
(Author "Heller" "Joseph")
> _


There are two ways to work with the named objects: in one step or two.



Two steps:


> (obj (Author "Salinger" "Jerome") "jds")
(Author "Salinger" "Jerome")
> (:first-name (obj "jds") "J. D.")
(Author "Salinger" "J. D.")
> (obj Author "jds")
(Author "Salinger" "J. D.")
> (obj "jds")
(Author "Salinger" "J. D.")
> _


Now, one step:


> (obj (Author "Salinger" "Jerome") "jds")
(Author "Salinger" "Jerome")
> (obj "jds" (:first-name (obj "jds") "J. D."))
(Author "Salinger" "J. D.")
> (obj "jds")
(Author "Salinger" "J. D.")
> _


I've only started using this modified version of obj recently, so we'll see how it turns out.



That's it for this report. Stay tuned for further developments.



m i c h a e l

m i c h a e l

#1
I forgot to include the object? predicate used by the second version of the obj function:


(set (global 'object?) (fn (it)
(and
(list? it)
(not (empty? it))
(context? (it 0))
)
))


m i c h a e l

cormullion

#2
This is getting difficult... Good stuff, though! :)

m i c h a e l

#3
Quote from: "cormullion"This is getting difficult...


Uh-oh, I thought I was making it easier! I've abandoned using named objects for now (the updated obj function), but my current experiments have been very promising.



I'm also working on a useable define-keyword macro. It functions the way I would want keyword arguments to work, but it still has a long way to go before it can be used in real code.


Quote from: "cormullion"Good stuff, though! :)


Thank you. I hope to model something really complex in FOOP someday. I've already managed to dwarf all of the models I made in other languages BnL (Before newLISP). But I still have more to discover before I can model even a simple kitchen appliance with any degree of realism. My true interest in OOP is simulation, for which it is best-suited. newLISP is proving an amazing platform for programming paradigm exploration. I'm very encouraged by the progress I've made.



m i c h a e l

itistoday

#4
I love these experiments, and hopefully I will get some time to play with this soon... Thanks!
Get your Objective newLISP groove on.