A simple minimal OO system for newLISP

Started by Lutz, October 23, 2007, 03:57:40 PM

Previous topic - Next topic

Lutz

(1) An object is represented by a list in which the first member is a symbol identifying the class of which this object is an instance.



(2) Methods are defined in a context with the class name.


(define (rectangle:area p)
(mul (p 3) (p 4)))

(define (circle:area c)
(mul (pow (c 3) 2) (acos 0) 2))

(set 'myrect '(rectangle 5 5 10 20)) ; x y width height
(set 'mycircle '(circle 1 2 10)) ; x y radius


Note, that the methods should be defined first to establish 'rectangle' and 'circle' as context symbols. In a similar fashion 'rectangle:move' and 'circle:move' could be defined.



(3) we define the : (colon) as a function which extracts the class name from the object, constructs the class:method symbol and applies it to the object:


(define-macro (: _func _obj)
(let (_data (eval _obj))
(apply (sym _func (_data 0)) (list _data))))


now we can code:


(:area myrect) => 200
(:area mycircle) => 314.1592654


Visually and in an intuitive manner this looks like the type polymorphic application of a generic functor ':area' to an object 'myrect' or 'mycircle', both of different types. But in reality this is the ':' function taking the symbol 'area' as the method name and extracting the correct class from the object. The newLISP parser allows the colon : to be attached directly to the following symbol.



The above macro will be a built-in primitive in the next development version for maximum speed an minimum overhead. The : function solves the polymorphism problem. Objects defined as in rule (1) can be anonymous and can be nested.



Lutz



ps:



- defining the : colon doesn't disturb any existing functionality related to contexts



- subclassing is done using (new 'Class 'Subclass) and adding or overwriting methods in 'Subclass' doing: (define (Subclass:some-method) ...)



- multiple inheritance is possible using repeated 'new' (new 'Aclass 'Subclass), (new 'Bclass 'Subclass). This can add methods from 'Aclass and 'Bclass to an already existing 'Subclass.



- 'def-new' can be used to do mix-ins of single methods.



- a 'def-type' macro could be used to define <class-name>:<class-name> as a default functor to construct objects (<class-name> p1 p2 ...), but isn't really essential although convenient.

m i c h a e l

#1
Hi Lutz,



Thank you! I know you're not overly fond of OOP, so for you to spend time on it means a lot to me.



What a great idea to use :. I didn't even know you could define it as a function! And you're right, it is intuitive.



m i c h a e l

Jeff

#2
Can a Shape type contain multiple Side objects?
Jeff

=====

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



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

Lutz

#3
Nothing new here really ;-), you can do whatever you can do when nesting functions and lists in newLISP. The : colon macro is just a special 'apply' which constructs the functor on the fly with info found in the object. It helps creating type polymorphism used in OO. (:area ...) will call two different functions depending on the object type following.



Lutz

Jeff

#4
So these are not being created as contexts then?
Jeff

=====

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



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

Lutz

#5
contexts are only created to hold the methods. The context symbol in the object as in 'rectangle' (rectangle x y width height) is just like pointer indicating where to look up the method.



Lutz

Jeff

#6
So the built-in def-type macro will internally store these references and simulate context symbols pointing to other contexts?  Or must all types be declared in the main namespace?  So that, for example, first I must define Side, and then Shape can use Sides?
Jeff

=====

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



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

Lutz

#7
There is no built-in 'def-type'. The 'def-type' macro can be used to spit out a constructor and basic accessors, but is not necessary. The only thing really necessary is what you see in my post at the beginning of this thread. The contexts only hold methods. There is no nesting of contexts only nesting of classic Lisp lists. The lists are the objects. The first field in the object list is the symbol of the method context.



All types (represented by contexts) are part of the main name space, but don't need to be declared there. You can declare them inside a module/context as shown in the following code:


(define-macro (: _func _obj)
(let (_data (eval _obj))
(apply (sym _func (_data 0)) (list _data))))
(global ':)

(context 'Foo)

(define (rectangle:area p)
(mul (p 3) (p 4)))

(set 'myrect '(rectangle 5 5 10 20)) ; x y width height

(:area myrect) => 200

(context MAIN)

(:area Foo:myrect) => 200

Quotefirst I must define Side, and then Shape can use Sides?


normally yes, but you can even define the 'rectangle:area' function after using 'rectangle' in an object, but then you must predeclare 'rectangle:area' as in here:


(define-macro (: _func _obj)
(let (_data (eval _obj))
(apply (sym _func (_data 0)) (list _data))))
(global ':)

(context 'Foo)

(define rectangle:area) ; pre declaration

(set 'myrect '(rectangle 5 5 10 20)) ; x y width height

(define (rectangle:area p)
(mul (p 3) (p 4)))

(:area myrect) => 200

(context MAIN)

(:area Foo:myrect) => 200


The pre declaration can be inside or outside of Foo.



Lutz



ps: all this code runs now in the normal release, does not need new built-in primitives, but the colon : definition will be built-in in the future for speed and convenience.

m i c h a e l

#8
Hi Jeff!



Note: I wrote the following before Lutz responded. I'll post it anyway, but there is one confusing part in Lutz's answer that I wanted to clear up first:


Quote from: "Lutz"There is now nesting of contexts only nesting of classic Lisp lists.


I think he meant to say: "There is no nesting of contexts, only nesting of classic Lisp lists".



----



Objects are lists. The first element in the list is its type. The object's methods are stored in a context. The name of the context is the same as the type.



Here is a minimal example using the new : (colon) macro:


> (define (side:string) "a side")
(lambda () "a side")
> (define (shape:string sh) (string "a shape with " (length (1 sh)) " sides"))
(lambda (sh) (string "a shape with " (length (1 sh)) " sides"))
> (set 's '(shape (side) (side) (side)))
(shape (side) (side) (side))
> (:string s)
"a shape with 3 sides"
> (:string (s 1))
"a side"
> _


The def-type macro is really just for convenience (notice I didn't use it in the above example). It makes a constructor and accessors for the type being defined (in a context with the same name) but is itself totally optional (at least for now ;-)



You must create the context before making the first object, as I did with side:string and shape:string, which created the side and shape contexts.



Hopefully I answered your question in there somewhere :-)



m i c h a e l

Lutz

#9
Quote from: "michael"I think he meant to say: "There is no nesting of contexts, only nesting of classic Lisp lists"


yes, I meant "no nesting of contexts", sorry for the confusion (corrected my last post)



Lutz

Jeff

#10
Gotcha.  That's what I was asking.
Jeff

=====

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



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

Lutz

#11
Quote from: "michael"You must create the context before making the first object, as I did with side:string and shape:string, which created the side and shape contexts.


yes, and there are many ways to do it, the important thing is: the first time newLISP  sees the symbol in question it must know "this is a context symbol, not a normal symbol". All of the following methods are Ok:



(context 'Foo)     ; 1
(set 'Foo:bar)      ; 2
(define Foo:bar)  ; 3
(define (Foo:bar x y) ....) ; 4


the 1 might be dangerous because it also switches to Foo, which we only want when writing modules.



2 and 3 are actually exactly the same thing, but using (define Foo:bar) doesn't need the quote and it also shows better our intention, which is "define"ing something.



4 is defining the symbol right away as a function as as used in our program.



All of the above establish Foo as a context symbol when newLISP reads in the code and parses/translates it.

Lutz

Dmi

#12
Wow!



I like it!
WBR, Dmi

Fanda

#13
Quote from: "Lutz"In a similar fashion 'rectangle:move' and 'circle:move' could be defined.


I believe that at this moment, we cannot define these methods, because macro ':' doesn't allow for any additional parameters. Also any set-x,y,width,height,radius methods cannot be created.



Also, any access to inner attributes has to be done using indexing. We cannot use x, y, width, p1, p2 or other symbols as attribute names. For example, line defined as:
(line (point x y) (point x y))
will have line:length for example defined as:
(define (line:length l)
  (sqrt (apply add (map pow (map sub (rest (l 1)) (rest (l 2)))))))

Because of this we cannot change position of attributes in an object list and we always have to add new attributes at the end, so that there is a bigger chance that we don't break existing functionality.



Fanda

m i c h a e l

#14
Need to map a polymorphic function over a list of objects? Use curry:


> ; Note: The following uses the ':' macro.
(define (point:point (x 0) (y 0)) (list point x y))
(lambda ((x 0) (y 0)) (list point x y))
(define (point:print pt) (string (pt 1) "@" (pt 2)))
(lambda (pt) (string (pt 1) "@" (pt 2)))
> (map (curry :print) (map point '(23 54 76 34) '(43 76 34 23)))
23@43
54@76
76@34
34@23
("23@43" "54@76" "76@34" "34@23")
> ; now for some polymorphism . . .
> (define (pixel:print px) (println (string "x" (px 1) ":y" (px 2))))
(lambda (px) (println (string "x" (px 1) ":y" (px 2))))
> (map (curry :print) '((point 23 54) (pixel 76 34) (point 43 76) (pixel 34 23)))
23@54
x76:y34
43@76
x34:y23
("23@54" "x76:y34" "43@76" "x34:y23")
> _


:-)



m i c h a e l