OOP using nested association lists and lets

Started by Fanda, October 17, 2007, 08:21:29 AM

Previous topic - Next topic

Fanda

Because I don't like contexts too much, I implemented a basic OOP framework using nested association lists and lets.



Source code for oop.lsp:

http://www.intricatevisions.com/source/newlisp/oop.lsp">http://www.intricatevisions.com/source/newlisp/oop.lsp



Implementation of http://onestepback.org/articles/poly/">Shapes

http://www.intricatevisions.com/source/newlisp/oop-shapes.lsp">http://www.intricatevisions.com/source/ ... shapes.lsp">http://www.intricatevisions.com/source/newlisp/oop-shapes.lsp



Output:
Drawing a Rectangle at (10,20), width 5, height 6
Drawing a Rectangle at (110,120), width 5, height 6
Drawing a Circle at (15,25), radius 8
Drawing a Circle at (115,125), radius 8
Drawing a Rectangle at (0,0), width 30, height 15


Examples:

http://www.intricatevisions.com/source/newlisp/oop-examples.lsp">http://www.intricatevisions.com/source/ ... amples.lsp">http://www.intricatevisions.com/source/newlisp/oop-examples.lsp



Output:
Setting and getting values:
o1:a = 1
o1:a = 100

Calling functions:
(100 3)
(1 3)
(5 10)
o1:a + o1:b = 103

Composing objects:
Point:  (0 0)
Circle: (0 0 0)
Line:   ((0 0) (3 4))
Line length: 5

Inline composition:
Myobj: ("Hello!" (3.14 -1))


Fanda

Lutz

#1
here is an optimization for your replacement routine in oop.lsp:


(define (replace-element lst e1 e2)
(set-nth (lst (ref e1 lst)) e2))


instead of pop/push with the same index vector you can do a 'set-nth' returning the entire list or 'nth-set' returning the old element.



Lutz

Fanda

#2
Thank you for optimization!



I found some bugs and I will try to fix them...



Fanda

cormullion

#3
Wow Fanda - more fine stuff! I've got lots to look at when I get time, what with michael's stuff as well...



In the meantime, can I ask - why do you say:


QuoteBecause I don't like contexts too much


As I'm struggle to master them, I'm interested...

Fanda

#4
Bug fixed ;)



I meant: "I don't like using contexts for objects." It is more of a personal feeling or preference - they are not compatible with my thought patterns.



I like them quite a lot for modules.



Fanda

Fanda

#5
I am currently testing some ideas:



Types of objects:
(set 'Complex
  (object nil
    re 0 im 0
    type 'complex
   
    same-type? (fn (o) (= (object-get o 'type) 'complex))
))

(set 'c Complex)

(define (type o)
  (object-get o 'type))

(define (complex? o)
  (= (type o) 'complex))


(type c) => complex
(complex? c) => true
(object-do 'Complex (same-type? c)) => true
(object-do 'c (same-type? c)) => true


Nested objects:
> (data-type (person last first))
person
> (data-type (book title author isbn))
book
> (set 'catch-22 (book "Catch-22" (person "Heller" "Joseph") "0-684-83339-5"))
("Catch-22" ("Heller" "Joseph") "0-684-83339-5")
> (person:first (book:author 'catch-22))


(set 'Person
  (object nil
    first-name ""
    last-name ""
   
    init (fn (l f) (set 'last-name l 'first-name f))
))
           
(set 'Book
  (object nil
    title ""
    author (object Person)
    isbn ""
   
    init (fn (it ia ii) (set 'title it 'author ia 'isbn ii))
))

(set 'catch-22 (object-init Book "Catch-22" (object-init Person "Heller" "Joseph") "0-684-83339-5"))

(object-get (object-get catch-22 'author) 'first-name)  ; => "Joseph"

(object-set 'catch-22 'author 'first-name "Dweezil")
(object-get (object-get catch-22 'author) 'first-name)  ; => "Dweezil"
(object-get catch-22 'author 'first-name)               ; => "Dweezil"


PS: I updated and changed oop.lsp and both examples. Download them again before you play with it.

m i c h a e l

#6
Fanda!



Thanks for writing and posting this object system to the club. I've been test-driving it since yesterday. Unfortunately, your last update seems to have broken the following:


newLISP v.9.2.4 on OSX UTF-8, execute 'newlisp -h' for more info.

> (set 'person (object nil first-name "" last-name "" full-name (fn () (string first-name " " last-name))))
'(object (first-name "") (last-name "") (full-name (lambda () (string
    first-name " " last-name))))
> (set 'p (make person))
'(object
 (first-name "")
 (last-name "")
 (full-name (lambda () (string first-name " " last-name))))
> (oset 'p 'first-name "Lutz")
'(object
 (first-name "Lutz")
 (last-name "")
 (full-name (lambda () (string first-name " " last-name))))
> (oset 'p 'last-name "Mueller")
'(object
 (first-name "Lutz")
 (last-name "Mueller")
 (full-name (lambda () (string first-name " " last-name))))
> (odo p (full-name))

symbol expected in function set : ''(object
 (first-name "Lutz")
 (last-name "Mueller")
 (full-name (lambda () (string first-name " " last-name))))
> _


Am I doing this correctly?



m i c h a e l

Fanda

#7
There was an error in object-do (odo). It's fixed now.



Also, there was a change to evaluate the object symbol, so please quote it:

(odo 'p (full-name)) => "Lutz Mueller"



Evaluation of symbol is useful in polymorphic calling - see oop-shapes.lsp for (do-something-with-shape s) function.



There are other problems which I am working through...

Fanda

#8
Core changes:

- object list isn't quoted anymore

- reworked, added, removed functions



Added new example:

http://www.intricatevisions.com/source/newlisp/oop-elica.lsp">http://www.intricatevisions.com/source/ ... -elica.lsp">http://www.intricatevisions.com/source/newlisp/oop-elica.lsp



Fanda

Lutz

#9
Perhaps we could define some basic rules, everybody could agree upon, i.e. the basic structure of an object (obj-name data-fied-1 data-field-2 ...) as in: (point x y), or the basic form of a message (class:msg obj param1 param2 ...) as in: (rectangle:rotate myrect x y angle) etc..



This way we still leave open the implementation but agree on some basics for object and method representation and usage?



Lutz

Fanda

#10
My OOP framework is written along the way REBOL does it:

http://www.rebol.com/docs/core23/rebolcore-10.html">http://www.rebol.com/docs/core23/rebolcore-10.html



It works more or less similar up to a chapter 6: Prototype Objects. Chapter 9: Reflective Properties are simulated using function 'object-symbols'.



Referring to Self and Encapsulation (where function 'make-account' is both local to object Bank and global to context!!!) are not implemented.



My recommendation is to study and borrow more from REBOL ;-)



Fanda

m i c h a e l

#11
Quote from: "Fanda"Added new example:

http://www.intricatevisions.com/source/newlisp/oop-elica.lsp">http://www.intricatevisions.com/source/ ... -elica.lsp">http://www.intricatevisions.com/source/newlisp/oop-elica.lsp


Wow, you even implemented the rules! Good job. When I began to work on the rules, I realized a simple reference to the points would alleviate the need for them altogether. As long as our objects remain isolated from one another, they're nothing but glorified records. No object is an island ;-)


Quote from: "Lutz"Perhaps we could define some basic rules, everybody could agree upon


That seems unlikely ;-) While there's no reason to forbid having multiple OOPs, by creating an official version, we can begin to depend on it just as we now do with the language. To write intros and tutorials using it. To help attract OO programmers to newLISP.



I've used languages with no official object system, but many unofficial ones. Rather than improve the situation, it leads to simplistic, watered-down, unfinished OOPs.



I appreciate the simplicity of def-type and feel it blends well with the language. I tried four or five object systems on my own, and none of them amounted to much. Fanda's OOP framework has progressed further than any of mine did (I really like how object-do works), but as Fanda said himself, it's modeled on the way REBOL does objects. I would like an object system different from all the rest. I've not been truly satisfied with any OOP language (Smalltalk being the exception), so I'd rather try something that fits perfectly with the existing language, while improving upon the poor OO systems of the past.


Quote from: "Lutz"This way we still leave open the implementation but agree on some basics for object and method representation and usage?


I like the idea of working in an idealized form before committing to any one implementation. Here is an example of my own, with object references using symbols:


(load "def-type.lsp")

(def-type (point (x 0) (y 0)))
(define (point:string pt) (string (point:x pt) "@" (point:y pt)))
(define (point:xy pt x y) (point:x pt x) (point:y pt y))
(define (point:move pt dx dy)
(point:x pt (+ dx (point:x pt)))
(point:y pt (+ dy (point:y pt)))
)

(def-type (segment (a (point)) (b (point)))
(define (segment:string sg)
(string (point:string (segment:a sg)) " to " (point:string (segment:b sg)))
)
(define (segment:move sg ax ay bx by)
(point:move (segment:a sg) ax ay)
(point:move (segment:b sg) bx by)
)

(def-type (triangle (ab (segment)) (bc (segment)) (ca (segment))))
(define (triangle:string tr)
(string
(segment:string (triangle:ab tr)) ", "
(segment:string (triangle:bc tr)) ", "
(segment:string (triangle:ca tr))
)
)

;; S A M P L E   R U N

(set 'a (point))
(println (point:string a))

(set 'b (point 20 0))
(println (point:string b))

(set 'c (point 10 5))
(println (point:string c))

(set 'ab (segment 'a 'b)) ; notice that this and the following are passing references.
(println (segment:string ab))

(set 'bc (segment 'b 'c))
(println (segment:string bc))

(set 'ca (segment 'c 'a))
(println (segment:string ca))

(set 'tri (triangle 'ab 'bc 'ca))
(println (triangle:string tri))

(println "Move segment 'bc':")
(segment:move (triangle:bc tri) 30 5 20 10)
(println (triangle:string tri))

;; E N D


Should output:



0@0
20@0
10@15
0@0 to 20@0
20@0 to 10@15
10@15 to 0@0
0@0 to 20@0, 20@0 to 10@15, 10@15 to 0@0
0@0 to 50@5, 50@5 to 30@25, 30@25 to 0@0


That's it :-)



m i c h a e l

m i c h a e l

#12
I forgot to mention in my above post the idea of default values for data type fields. This allows for default constructors (constructors needing no arguments). This way, we can simply write (point) to create a point at 0@0 (great as arguments to other default constructors):


> (def-type (point (x 0) (y 0)))
point
> (def-type (sequence (a (point)) (b (point))))
sequence
> (def-type (triangle (ab (sequence)) (bc (sequence)) (ca (sequence))))
triangle
> (set 'p (point))
(point 0 0)
> (set 's (sequence))
(sequence (point 0 0) (point 0 0))
> (set 't (triangle))
(triangle (sequence (point 0 0) (point 0 0)) (sequence (point 0 0) (point 0 0)) (sequence (point 0 0) (point 0 0)))
>


Now for something completely different. I wonder if it will ever be possible to go from this:


((context (obj 0) 'some-method) args)


. . . to this:


((obj 0):some-method args)


(obj 0) is getting the type of the object obj.



We could define functions to return the type and fields of an object:


(define (type obj) (obj 0))
(define (fields obj) (1 obj))

((context (type obj) 'some-method) args)
; vs.
((type obj):some-method args)


BTW: Did you know you could do this?


> (set 'LIFE: meaning 42) ; notice the space after the colon
42
> LIFE:         meaning          ; it doesn't matter how many spaces
42
> ; But the same is not true the other way round:
> LIFE :meaning                  ; notice the space before the colon
LIFE
nil
nil
> _


Interesting.



m i c h a e l

Fanda

#13
New updated version can be found here:

http://intricatevisions.com/source/newlisp/oop.lsp">http://intricatevisions.com/source/newlisp/oop.lsp



- added 'type' and 'type?' functions

- shorter syntax: instead of obj-do, obj-set, obj-get use .do, .set, .get



Fanda