FOOP and macros

Started by Jeff, June 21, 2008, 07:57:32 AM

Previous topic - Next topic

Jeff

Lutz,



Macros defined as methods (:foo) are currently passed the quoted instance list, rather than the unevaluated argument.  This is not consistent with newlisp macros generally and does not allow us to create destructive methods.  I understand that the argument must be evaluated to see if it is a context instance, but that doesn't mean that the evaluated argument should then be quoted and passed to the macro.  That seems broken.  Can you change the way that is handled so that macros work the same way across the board?



Jeff
Jeff

=====

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



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

Lutz

#1
I cannot un-evaluate what is already evaluated, because the evaluation could have had side effects not related to what is done with the object. I have to evaluate to extract the class from the object for composing the function or macro to apply.



What you have seen and what prompted your question is this:


(define-macro (rect:showarg obj) obj)
(define-macro (showarg obj) obj)

(set 'myrect '(rect 3 4))

(:showarg myrect) => '(rect 3 4)
(showarg myrect)  => myrect


For FOOP 'myrect' is not available any more, because of the necessary evaluation of myrect to access the object class.



The important point is: that in both cases the showarg macro shows the object inside a one-level evaluation envelope. So that operations on the object can be the same in FOOP and out-side FOOP function or macro operation, and you don't see a difference between both:


; as FOOP function and macro
(define (rect:area obj) (mul (obj 1) (obj 2)))
(define-macro (rect:aream obj) (mul ((eval obj) 1) ((eval obj) 2)))

; as normal function and macro
(define (area obj) (mul (obj 1) (obj 2)))
(define-macro (aream obj) (mul ((eval obj) 1) ((eval obj) 2)))

(set 'myrect '(rect 3 4))

(:area myrect) => 12
(:aream myrect) => 12

(area myrect) => 12
(aream myrect) => 12


In both cases, when leaving out the evaluation of obj in the macros you get the same error-message.



If I would pass the un-evaluated argument again to the function, which would evaluate again its argument, side effects would happen twice, once in the colon operator and the second time in the function or in the macro if the macro also evaluates.

Jeff

#2
This makes FOOP much less useful.  There is no way to hide implementation from the client of a class.



You can't even create a simple counter without the programmer being required to know the internals:


(define (Counter:Counter (n 0)) (cons (context) n))

(define (Counter:inc inst n) (nth-set (inst 1) (+ $ n)))


In order to use it:


(setq c (Counter 0))
(setq c (:inc c 4))
(setq c (:inc c 2))


The user of a class must know the implementation to know which functions have side effects pertinent to the object, and therefore when to save the result of a function.



With a context-based module, I can implement it however I like and the programmer need not worry about the internals:


(context 'Counter)

(define (Counter:Counter ctx (x 0))
  (new Counter ctx)
  (setq ctx (eval ctx))
  (setq ctx:n x)
  ctx)

(define (inc (x 1))
  (inc 'n x))

(context MAIN)

(Counter 'c 0)
(c:inc 4)
(c:inc 2)




When using functional and reentrant techniques, side effects should be avoided when possible.  However, OO is imperative; one of its advantages is that the implementation can be hidden.  That means that the programmer does not need to comb through a module's code just to be able to use it; he or she only needs to know the API.



There is not a truly compelling advantage to using this over the old method of prototyping contexts.  In fact, those had several advantages.  1) instances are passed by reference; 2) instances may be destructively modified.



The only disadvantages: 1) altering a class does not affect already-created instances; 2) FOOP syntax is prettier; 3) instantiation speed (creation is faster, but calling methods is much slower since each instance must be deep-copied when passed as an argument to the method).



The only way it can be gotten around is to use stateful constructs as members, such as a shares, semaphores, or contexts.
Jeff

=====

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



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

Lutz

#3
Both, newLISP OO prototyping and newLISP FOOP based models serve different needs:



The old OO prototype based model in newLISP is closer to classic OO but objects are always bound to a namespace and namespaces are slow to delete in a loaded system which contains a lot of Lisp cells. Although deleting namespaces has been made a lot speedier in upcoming 9.3.17, it still is magnitudes slower then deleting objects which are not namespace based as FOOP.



In FOOP you can have anonymous objects and they are much quicker deleted. FOOP also melts better into Lisp functional style of programming, which is why it is perceived as prettier. But yes, its functional, and no destructive functions on FOOP objects.



To sum it up:



When using few and less volatile but big objects, use the classic newLISP prototype based OO with namespaces holding both data and methods. Because the namespace can be passed by reference, this approach is good for big objects.



When using many, small and volatile objects then FOOP is the way to go. Objects are quick to, delete and because there are small, destructive functions are not necessary and not wanted in a functional style of programming.

Jeff

#4
Lutz - I think it's pretty not because of the technique, but because the colon operator is pleasant.  It makes it obvious that a function is a method.



Et al - for those who disagree with Lutz, here is a way to add destructively settable sate to your instances.  It slows object creation down, but it makes FOOP usable:


;;; naive implementation of gensym; creates a unique symbol up to the maximum integer. note, this should be implemented more carefully if you plan to create large numbers of instances.

(setq gensym:counter 0)

(define (gensym:gensym)
  (inc 'gensym:counter)
  (sym (string "gensym-" gensym:counter) MAIN))

;;; Hash is a generic new-style context hash.  It doesn't matter if you use it as a hash; we just need an empty context to prototype.

(define Hash:Hash)

(define (Counter:Counter (n 0))
  (let ((ctx (gensym)))
    (new Hash ctx)
    (setq ctx (eval ctx))
    (set (default ctx) n)
    (list (context) ctx)))

;;; To destructively alter state in instance

(define (Counter:inc inst (n 1))
  (letn ((c (inst 1)))
    (set (default c) (+ n (eval (default c))))))


This is somewhat complicated for integers, since for whatever reason only strings and lists are automatically evaluated when default functors.  But for strings or lists, they work fine.
Jeff

=====

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



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

m i c h a e l

#5
Jeff,



You seem to be trying to take the 'F' out of FOOP :-) As Lutz said, FOOP uses side-effect—free programming in order to harmonize with newLISP's natural argument-passing style (by value). If you prefer using contexts to do prototype-based OOP, you should. For my purposes, FOOP works great. This isn't an either-or proposition. Both ways (or neither way) are open to us.



m i c h a e l

Jeff

#6
Functional doesn't have to be "pure".  The purest functional languages are the least used because they are so impractical.  Which is why newLISP is not purely functional.
Jeff

=====

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



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

Jeff

#7
Additionally, to have a purely functional language with any level of efficiency, you really need to be able to pass by reference.
Jeff

=====

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



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

Jeff

#8
Also, is there any way to functionally compose a method call in newLISP?  I can curry, ie (curry : method-name), but then I get a one-argument function.
Jeff

=====

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



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

Lutz

#9
Since 9.3.10/12 we have general reference passing to any function (user-defined or bullt-in) via default functors packaging data:



http://www.newlisp.org/download/development/newlisp_manual.html#pass_big">http://www.newlisp.org/download/develop ... l#pass_big">http://www.newlisp.org/download/development/newlisp_manual.html#pass_big





Hashing got a lot simpler since 9.3.5:



http://www.newlisp.org/download/development/newlisp_manual.html#hash">http://www.newlisp.org/download/develop ... .html#hash">http://www.newlisp.org/download/development/newlisp_manual.html#hash





See also the 9.4.0 release notes:



http://www.newlisp.org/download/development/newLISP-9.4-Release.html">http://www.newlisp.org/download/develop ... lease.html">http://www.newlisp.org/download/development/newLISP-9.4-Release.html





Here are some examples for multi-arg currying, left/right curry etc., by Jeremy:



http://newlisp-on-noodles.org/wiki/index.php/Functional_Programming">http://newlisp-on-noodles.org/wiki/inde ... rogramming">http://newlisp-on-noodles.org/wiki/index.php/Functional_Programming