; Trying to answer some criticals on Newlisp in Usenet newsgroupRelated post: On Macro Expansion, Evaluation and Generated Code.
; 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. "
Challenged by Common Lispers.
Two Definitions of IF Function.
;---------------------------------------------------------------
; Last few days I played with definition of IF as function.
; It is typically not possible in Lisp family, but Newlisp
; can do that. I present here two definitions of IF that are
; specific not only because of that, but also because the
; definitions do not use any equivalent built in (like if, cond,
; case) and also, definitions do not use any variables.
;---------------------------------------------------------------
(set 'IF (lambda()
(eval
((args)
(- 5 (length
(string
(true?
(eval (first (args)))))))))))
(let ((x 2)(y 3))
(IF '(< x y)
'(println x " is less than " y)
'(println x " is not less than " y))
(IF '(> x y)
'(println x " is greater than " y)
'(println x " is not greater than " y)))
;---------------------------------------------------------------
(set 'IF (lambda()
(eval
((args)
(find (true? (eval (first (args))))
'(* true nil))))))
(let ((x 2)(y 3))
(IF '(< x y)
'(println x " is less than " y)
'(println x " is not less than " y))
(IF '(> x y)
'(println x " is greater than " y)
'(println x " is not greater than " y)))
(exit)
Avoiding Function Names Clashes.
My "default-library" has grown to define 464 new symbols, mostly functions. Most of these are generated, so they are not interesting. Nevertheless, there is significant probability that I'll try to use same name twice - especially if I use abbreviations.
That's why I defined version of "set" named set-undefined - if symbol already has value, set-undefined throws an error.
Ops. I didn't syntax-colored this post. OK, we can live with that.
That's why I defined version of "set" named set-undefined - if symbol already has value, set-undefined throws an error.
SU is especially useful.(set 'set-undefined
(lambda-macro(var-expr val-expr)
(let ((evar-expr (eval var-expr)))
(when (eval evar-expr)
(throw-error (append (char 8)
(char 8)
(char 8)
": "
"(SU " (string var-expr)
" " (string val-expr)
") where "
(string var-expr)
" => "
(string evar-expr)
" => "
(string (eval evar-expr)))))
(set 'toeval (list 'set
(list 'quote evar-expr)
val-expr))
(eval toeval)
nil)))
(set-undefined 'SU set-undefined)
Ops. I didn't syntax-colored this post. OK, we can live with that.
Short or Long Function Names?
I am frequently in doubt should I use short or long variable names. In some circumstances, long names look better - for example, when I start using some function, it is easier to remember long name. And it is especially easier to explain programs to other people if functions have long, descriptive names.
On the other side, after some use, long names are counterproductive - code is longer, and it takes more time to write it.
Recently, I came to idea to define functions with two names, long and short one. I think it is not accidental that we usually have two names - original, long name - and then shorter name, nickname, acronym or abbreviation. Look at this:
Philosophy Doctor --> Ph.D.
United States of America --> USA
Robert --> Bob
In my humble opinion --> IMHO
It cannot be accidental, right? So, now, I'm in experimental period of defining double names. For example, I have Lispy name string-append and Perly $+, identity-function (lambda(x)x) and IF, identity-macro (lambda-macro(x)x) and IM.
So far, so good. I'll report experiences in future. Try it, maybe you'll like it also.
On the other side, after some use, long names are counterproductive - code is longer, and it takes more time to write it.
Recently, I came to idea to define functions with two names, long and short one. I think it is not accidental that we usually have two names - original, long name - and then shorter name, nickname, acronym or abbreviation. Look at this:
Philosophy Doctor --> Ph.D.
United States of America --> USA
Robert --> Bob
In my humble opinion --> IMHO
It cannot be accidental, right? So, now, I'm in experimental period of defining double names. For example, I have Lispy name string-append and Perly $+, identity-function (lambda(x)x) and IF, identity-macro (lambda-macro(x)x) and IM.
So far, so good. I'll report experiences in future. Try it, maybe you'll like it also.
The Results of "Your Favorite Lisp Dialect" Poll.
The poll was started soon after blog. The results are:
So, more than half of the Lisp users in the world prefer Newlisp. OK, more than half readers of this blog. OK, more than half readers of this blog interested enough to contribute to the poll.
Of course, I expected that Newlisp is the most popular in this poll, because almost all posts are about that dialect, but I expected nearly even distribution between R6RS Scheme and Common Lisp with marginal acceptance of other dialects, but it is not the case. I regret that I didn't made separate item for Software Lab's Pico Lisp, my favorite dialect beside Newlisp.
Related: Lisp and Artificial Intelligence Poll, 2009.
---
Newlisp: 25 votes (53%) Common Lisp: 12 votes (25%) Clojure: 4 votes (8%) R5RS Scheme: 3 votes (6%) R6RS Scheme: 1 vote (2%) Other: 2 votes (4%) ============================ Total: votes (100%) |
So, more than half of the Lisp users in the world prefer Newlisp. OK, more than half readers of this blog. OK, more than half readers of this blog interested enough to contribute to the poll.
Of course, I expected that Newlisp is the most popular in this poll, because almost all posts are about that dialect, but I expected nearly even distribution between R6RS Scheme and Common Lisp with marginal acceptance of other dialects, but it is not the case. I regret that I didn't made separate item for Software Lab's Pico Lisp, my favorite dialect beside Newlisp.
Related: Lisp and Artificial Intelligence Poll, 2009.
---
Happy New Year!
===============
I wish HAPPY NEW YEAR to all readers of this blog. One of my New Year resolutions is to update it more frequently.
I wish HAPPY NEW YEAR to all readers of this blog. One of my New Year resolutions is to update it more frequently.
Subscribe to:
Posts (Atom)