Gerry Quinn said:
Gerry Quinn said:
I disagree - it is in fact rare for code to be changed per se. What
would be the point in writing code whose only purpose is to be written
over? Whether original code is erased or not is completely irrelevant
to the issue of whether code is self-modifying. The *program* is still
self-modifying.
Because I might get tired of typing something like:
(let ((addr-1 (get-html-value "addr-1"))
(addr-2 (get-html-value "addr-2")) [--]
so I write a macro to transform some code and now I can
write thusly:
(with-form-values (addr-1 addr-2 city state zip (name "username")
(other-value "other" some-function))
...some code...)
which is transformed into the top code.
Now I have eliminated a source of typos and made the code much more
readable. Note that a function will not work for this because I am
establishing lexical bindings for "...some code..."
Sure, that's a reason why you might want to use this. And I think, if
you are making the point that this should not be called 'self-modifying
code' in the proper sense, you do have a point - you are manipulating
strings in a way that might be handled elsewise in another language. If
you look at the above, it's really data you are manipulating, not code.
No, I am not manipulating strings or data, I am manipulating code.
Although C macros work on strings, Lisp macros do not, despite the
fact that the Lisp source code does reside in a file. This is because
the Lisp reader parses the expressions for me and hands my macro a
data structure. This particular example is rather simple, but there
are plenty of other uses of macros in which a code 'tree' is
traversed, examined and transformed. This is far more powerful than
simple string substitution as C has.
Another example:
(ITER (WITH specials = (get-sorted-specials unsorted-list))
(WITH result = '())
(FOR (sym . str) IN specials)
(FOR char0 = (char str 0))
(FOR char1 = (and (= 2 (length str)) (char str 1)))
(FOR lchar0 PREVIOUS char0 INITIALLY nil)
(FOR pair = (assoc char0 result :test #'char=))
(if (null pair)
(push (setf pair (cons char0 nil)) result)
(case lchar0
(#\a (COLLECT char0 into a-list))
(#\b (COLLECT char0 into b-list))))
(FOR new = (cons char1 sym))
(push new (cdr pair))
(FINALLY (return result)))
ITER is a macro, and it does not get a big string to process. It gets
a forms (fyi a form is either an atom or a list of atoms and/or
forms), which it examines and transforms to build the resulting code.
Here, some of those forms contain symbols defined by ITER, some are
'ordinary' lisp. The ITER symbols can be anywhere in the code block,
ITER will find them and handle them properly in the result. I
capitalized them above. Notice the COLLECT words down in the IF's
else form. Lisp macros are so powerful because the macro operates on
the parse tree of the code. It is trivial for ITER to locate the
COLLECT words and insert the code for them.
Again I also want to stress that I can't use a function to do this type
of abstraction; I want all of the resulting code (including transformed
bits and other bits just used as is) to be in the same lexical scope.
Below is what the ITER macro expands into for the above code. I show
you all this to give you an idea of the amazing amount of crap that
can be cut out of your programs if you use macros to abstract away the
scaffolding.
(LET* ((SPECIALS (GET-SORTED-SPECIALS UNSORTED-LIST))
(RESULT 'NIL)
(#:LIST36 NIL)
(SYM NIL)
(STR NIL)
(CHAR0 NIL)
(CHAR1 NIL)
(LCHAR0 NIL)
(PAIR NIL)
(A-LIST NIL)
(#:END-POINTER37 NIL)
(#:TEMP38 NIL)
(B-LIST NIL)
(#:END-POINTER39 NIL)
(NEW NIL)
(#
OST-SAVE-CHAR0-40 NIL))
(BLOCK NIL
(TAGBODY
(SETQ #:LIST36 SPECIALS)
(SETQ LCHAR0 NIL)
(SETQ #
OST-SAVE-CHAR0-40 NIL)
LOOP-TOP-NIL
(IF (ATOM #:LIST36) (GO LOOP-END-NIL))
(LET ((#:G3840 (CAR #:LIST36)))
(SETQ SYM (CAR #:G3840))
(SETQ STR (CDR #:G3840))
#:G3840)
(SETQ #:LIST36 (CDR #:LIST36))
(PROGN (SETQ LCHAR0 #
OST-SAVE-CHAR0-40))
(SETQ CHAR0 (CHAR STR 0))
(PROGN (SETQ #
OST-SAVE-CHAR0-40 CHAR0))
(SETQ CHAR1 (IF (= 2 (LENGTH STR)) (CHAR STR 1)))
(SETQ PAIR (ASSOC CHAR0 RESULT :TEST #'CHAR=))
(IF (NULL PAIR)
(SETQ RESULT (CONS (SETQ PAIR (CONS CHAR0 NIL)) RESULT))
(LET ((#:G3841 LCHAR0))
#:G3841
(IF (EQL #:G3841 '#\a)
(PROGN
NIL
(PROGN
(SETQ #:TEMP38 (LIST CHAR0))
(SETQ #:END-POINTER37
(IF A-LIST
(SETF (CDR #:END-POINTER37) #:TEMP38)
(SETQ A-LIST #:TEMP38)))
A-LIST))
(IF (EQL #:G3841 '#\b)
(PROGN
NIL
(PROGN
(SETQ #:TEMP38 (LIST CHAR0))
(SETQ #:END-POINTER39
(IF B-LIST
(SETF (CDR #:END-POINTER39) #:TEMP38)
(SETQ B-LIST #:TEMP38)))
B-LIST))))))
(SETQ NEW (CONS CHAR1 SYM))
(LET* ((#:G3844 NEW)
(#:G3843 PAIR)
(#:G3842 (CONS #:G3844 (CDR #:G3843))))
(COMMON-LISP::%RPLACD #:G3843 #:G3842))
(GO LOOP-TOP-NIL)
LOOP-END-NIL
(RETURN RESULT))
NIL))
However, there are those who feel that defining new syntax is too
powerful a feature for a language, they should stay clear of this sort
of thing.
Downloaded your 'UltraFractal'. I was curious to see if it was written
in Lisp, but it doesn't appear so. [Not that that invalidates anything
you say.]
Actually Ultra Fractal was written by Frederik Slijkerman and is available
(as you must have found) at
www.ultrafractal.com. I just used it to create
the various images and animations on my site. I believe UF is written in
Delphi.