Challenged by Common Lispers.

; Trying to answer some criticals on Newlisp in Usenet newsgroup
; comp.lang.lisp I was challenged to "show some code" and demonstrate
; my claims that Newlisp macros have some advantages over CL macros on
; examples.
;
; I defined macro at-least, generalized version of the operator or.
; Macro call (at-least expr1 ... exprn) should return true if
; expr2 ... exprn evaluate to true - at least expr1 times. As a special
; case, (at-least 0 ...) should always return true. This is example
; of how code that tests at-least could look alike.



;; Both Newlisp and Common Lisp
(let ((x 1) (y 2) (z 3) (n 3))
   (print (at-least n
                 (at-least (- n 1) (= x 7) (= y 2) (= z 3))
                 (at-least (- n n) nil nil nil nil)
                 (at-least (* 1 z) 1 (= 2 2) (let ((z 100))
                                                  (= z 1000))))))


; The result of this test is nil, and if 1000 is replaced with 100, true.
; My Newlisp definition was:



;;Newlisp
(define-macro (at-least atn)
      (let ((aten (eval atn)))
           (doargs(ati (zero? aten))
                  (when (eval ati)
                        (dec aten)))
           (zero? aten)))


; The best definition Common Lispers managed to do appears to be
; Raffael Cavallaro's definition:



;;Common Lisp
(defmacro at-least (n &rest es &aux (nsym (gensym)) (carsym (gensym)))
 (if (null es) nil
   `(let ((,nsym ,n))
      (if (zerop ,nsym) t
        (let ((,carsym ,(car es)))
          (if (= ,nsym 1) (or ,carsym ,@`,(cdr es))
            (at-least (if ,carsym (1- ,nsym) ,nsym) ,@`,(cdr es))))))))


; How that CL definition compares with my Newlisp definition?
;
; The first and most important, CL macro is not the first-class
; object, so Cavallaro cannot, for example, map or apply his at-least,
; pass it as an argument, return from the function (or other macro) etc.
; This cannot be improved in Common Lisp, macros are not
; the first class citizens.
;
; Other, less important problem with Cavallaro's definition is that
; it requires quadratic macroexpansion time. It can be reduced to
; linear, and in some, special situations it can be the problem.
;
; To be fair, CL definition is safer, i.e. it is harder to accidentally
; shoot oneself in the foot. For programs small enough, it is not
; the problem. But, it might become the problem if program grows
; significantly. However, this problem can be routinely fixed in
; Newlisp - by use of the Newlisp lexical scope features named
; contexts or applying techniques I described on this blog. The contexts
; are general mechanism designed to prevent accidental name clashes
; not reserved for macros, but also functions, variables - all symbols.
; It this case, applying naming convention once code is written
; and tested solves the problem completely. For example,


;;Newlisp
(define-macro (at-least at-least_n)
      (let ((at-least_en (eval at-least_n)))
           (doargs(at-least_i (zero? at-least_en))
                  (when (eval at-least_i)
                        (dec at-least_en)))
           (zero? at-least_en)))


; is equally "safe" code as CL macro. Newlispers, however, typically
; use contexts for that purpose.
;
; One of the problems with Common Lisp macros is their complexity.
; Cavallaro is obviously good programmer. His macro has 57 tokens
; vs my 18 tokens - so it is 3.5 times longer. Relatively advanced
; features, like gensym  and list splicing (,@) are used. I can
; safely say, according to my experience, that not many people are
; able - or motivated - to write such code.

; Rainer Joswig wrote shorter macro that should be slightly
; changed to return (at-least 0 ...) to be always true. To be fair,
; he wrote it before I specifically mentioned that (at-least 0 ...)
; should always be true (although it is logical, isn't it?)


; Common Lisp
(defmacro at-least (n &rest es &aux (c (gensym)))
  `(let ((,c 0))
      (or ,@(loop for e in es collect `(and ,e (= (incf ,c) ,n))))))


; Still, my Newlisp macro has some advantages:
;
;   *   Joswig's CL macro has 31 token, Newlisp macro has 18 tokens.
;
;   *   Joswig's CL macro has 11 nesting levels, Newlisp macro has 5.
;
;   *   Joswig's CL macro uses some advanced features like backquote and
;       macro LOOP with irregular syntax. Newlisp macro is just one
;       loop - there is nothing remotedly advanced here.
;
;   *   Finally, Joswig's macro is not the first class citizen
;       while Newlisp macro is.
;
; However, is there any reason one might prefer CL over Newlisp macros?
;
; Yes, there is. If use of at-least is simple, i.e. at-least is
; mentioned only in expressions like in my test, but never anything
; like (map at-least ...), (apply at-least ...), (eval (at-least ...))
; - CL macro allows compilation and compiled code can run significantly
; faster. This is the main reason Common Lispers avoid eval. Newlisp
; code presented here, even if, theoretically, compiled (Newlisp
; actually has no compiler) wouldn't run that fast. However, if code
; contains the expressions like those mentioned above then Newlisp
; version is either only possible, or it allows significantly faster
; evaluation, because macroexpansion can be avoided - In Common Lisp,
; macroexpansion during runtime is very slow.
;
; As conclusion, I'll cite Alexander Burger, the author of Pico Lisp
; who wrote in 2004:
;

"The Lisp community seems to suffer from a paranoia of “unefficient”
Lisp. This is probably due to the fact that for decades they had
to defend their language against claims like “Lisp is slow” and
“Lisp is bloated”. Partly, this used to be true. But on today’s
hardware raw execution speed doesn’t matter for many practical
applications. "
Related post: On Macro Expansion, Evaluation and Generated Code.

7 comments:

  1. Please respond to killerstorm's comment here, to wit, that your macro isn't apply-able either. Perhaps he did it wrong?

    ReplyDelete
  2. can you map or apply your at-least so it will retain its short-circuiting feature in Newlisp?

    if it doesn't retain short-circuiting, you can get same behaviour in Common Lisp by automatically translating macro into a closure:

    (defmacro macrofn (mname)
    `(lambda (&rest rest)
    (eval `(,',mname ,@rest))))

    then you can use it in apply:

    CL-USER> (apply (macrofn or) '(nil 1 2 3 nil 4))
    1

    and map:

    CL-USER> (mapcar (macrofn or) '(1 nil 3) '(nil 2 nil))
    (1 2 3)

    but i doubt anyone needs this, as macros that should be called as functions should be functions.

    ReplyDelete
  3. It retains short circuiting, but if you call normal apply, then macro
    will see list of elements - all of them containing one extra quote.

    In my opinion it is logical, since in Lisp, only difference between macros and functions is that macro call does not evaluate its arguments. Apply is the same in both cases, hence, macros get one apostrophe extra on each argument.

    If you want that it works as you expect, you need bit of work (but really bit only) to define different apply, one that will strip extra quotes:

    (define (apply-macro m L)(eval (cons m L)))

    Now that's it:

    (println (apply-macro at-least '(2 (print "a") (print "b") (print "c"))))

    =>abtrue

    If you want apply-general, that works like default apply on functions, and apply-macro on macros, you can define it easily.

    (define (applyg x L)
    (cond ((or (primitive? x) (lambda? x))(apply x L))
    ((macro? x)(apply-macro x L))
    (true (println "don't ** with me!"))))

    (println (applyg sin '(1)))
    (println (applyg at-least '(2 (print "a") (print "b") (print "c"))))
    (exit)

    ReplyDelete
  4. but do you actually use this apply-macro in your programs? i betcha you do not, and all this first-classness is totally worthless.

    by the way, it is possible to write apply-macro in Common Lisp too and it will work not worse (if you use only special variables), so Newlisp is not unique in this, and macros in CL can be as first-class as in Newlisp from practical standpoint, if one really needs it.

    ReplyDelete
  5. I never applied macro yet, but I use to pass it as variable.

    ReplyDelete
  6. For me, macro in CL is a hell. It has so much of exceptions and rules which it is difficult to apply and remember.

    ReplyDelete
  7. Make me wonder how much "easier" it might be just to write it with lazy evaluation and a normal function.

    ReplyDelete