My recent challenge for Common Lispers.

Started by Kazimir Majorinc, January 29, 2009, 10:44:04 AM

Previous topic - Next topic

Kazimir Majorinc

Trying to answer some criticals on Newlisp in Usenet newsgroup comp.lang.lisp I was "called out" to "show some code" and demonstrate my claims that Newlisp macros have advantages over CL macros on examples.



I defined macro at-least, generalized version of operator or. (at-least expr1 ... exprn) should return true if expr2 ...  exprn evaluate to true - at least expr1 times. Also, evaluation should be "lazy", i.e. if it is discovered that true already occurred expr1 times, additional evaluations shouldn't be performed, as typical for or. The test should be:


;; 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))))))
-> nil
change 1000 to 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) (,carsym ,(car es)))
      (cond ((zerop ,nsym) t)
            ((= ,nsym 1) (or ,carsym ,@`,(cdr es)))
            (t (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. This problem cannot be resolved in Common Lisp. Other, less important problem with CL definition is that it requires quadratic macroexpansion time. I believe this limitation can be removed, but not without use of eval, the feature Common Lispers try to avoid (I'll say why they do that later.)



To be fair, CL definition is safer, i.e. it is harder to shoot oneself into foot. This problem can be routineously fixed in Newlips - by use of the lexical scope features in Newlisp - contexts or applying techniques I described on my blog, in 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.  



Beside obvious technical inferiority of not being the first class, the main problem with Common Lisp macro is its complexity. Cavallaro is obviously talented and experienced programmer, able to write complex code. His macro has 57 tokens vs my 18 tokens - so it is 3.5 times longer. Relatively advanced techniques, 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, 31 tokens, but it is related to the simpler version of the problem. Still, his code was more complicated than Newlisp. Newlisp macro, is, for a contrast just one loop - nothing remotedly advanced here.



Is there any reasons one might prefer CL macros over Newlisp macros? Yes, if use of the 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 ...)) - in such situations, CL macros allow 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 these expressions, then Newlisp version is either only possible, or it allows faster evaluation. Yes, in some cases interpreters are faster than compilers. If time is not critical - Newlisp version is just simpler.



---



I'll publish this on my blog, but I thought it might be of interest for those who collect first impressions on Newlisp on this forum as well.
http://kazimirmajorinc.com/\">WWW site; http://kazimirmajorinc.blogspot.com\">blog.

cormullion

#1
Wow, Kazimir, you crazy dude, flaunting newLISP's abilities in the faces of  comp.lang.(common)lisp guys!  :)



But seriously, a seriously good job - I hope that you provided any of the more open-minded of the cll-ers with, at least, some food for thought.



I like to avoid these confrontations, as they stir too many negative thoughts. Besides, newLISP can easily win most of the challenges that I could think of to set (easy to install, easy to learn, easy to use, ideal for scripting and CGI, full of features, friendly support, guided by simplicity, etc. etc.). But I'm full of admiration for your challenge on more traditional territory!

DrDave

#2
I think the CL club has totally forgotten about ...



...it is better to first strive for clarity and correctness and to make programs efficient only if really needed.



"Getting Started with Erlang" version 5.6.2
...it is better to first strive for clarity and correctness and to make programs efficient only if really needed.

\"Getting Started with Erlang\"  version 5.6.2

xytroxon

#3
Nice work!!!



And "Well done!!!"...



IMHO, the greatest problem with the CL syntax is that code, such as that above, looks about as understandable as so called "write-only" languages such as Forth... Maybe even less intelligible ;)



This functional programming "nerd" mindset is hard to understand why, since there is almost no penalty to use descriptive names today... (Save for spelling errors, which any good text editor will clearly show ;) ...compared to when LISP was born in the age of Hollerith punch cards, magnetic core memory and reel to reel tape forced out of pure necessity, the use of terse, compact syntax... The only reason to do so today is to blindly follow "The SOW"... i.e. The Same Old Way... I want to write a program, functional or imperative, that just works, not train to be a Jedi LISP knight or become a Lord of the Scheme Sith!!!



Paul Graham's famed "Yahoo Store" LISP code was a triumph of functional programming and made him rich when he sold out...



But, Yahoo immediately rewrote the code in C++ so that "mere mortal" maintainers could efficiently function within the code base in a constantly changing business environment...



Paul Graham, chagrined (or highly peeved), and to his credit, tried to remedy the shortcomings of LISP/Scheme with  ARC... A lofty goal, the development of which seems to have been abandoned by Graham the last time I checked a few months ago... Most likely from the continued scorn and indifference of the LISP/Scheme communities...



------------------



Here is another "nasty arrow" shot into CL's (SBCL) side...



Hello World Executable Sizes:

http://mbishop.esoteriq.org/hellosize.html">//http://mbishop.esoteriq.org/hellosize.html
QuoteThis is a collection of "Hello World" programs and their resulting executable sizes. I've noticed people asking "Why is Hello World in Foo bigger than Hello World in Bar", and I wanted to see just how big Hello World executables are in different languages.


reddit comments:

http://www.reddit.com/r/programming/comments/7swzs/hello_world_executable_sizes_in_17_languages/">//http://www.reddit.com/r/programming/comments/7swzs/hello_world_executable_sizes_in_17_languages/



Results:
QuoteExecutable Size



Here is a list of the executable sizes for each language:



    * Ada - 19K

    * Asm - 432B

    * C - 8.9K

    * C++ - 9.5K

    * C# - 11K

    * Common Lisp - 25MB

    * D - 230K

    * F# - 11K

    * Fortran - 9.3K

    * Haskell - 358K

    * Java - 12K

    * Modula-3 - 11K

    * Oberon-2 - 13K

    * Objective-C - 8.9K

    * OCaml - 121K

    * Pascal - 107K

    * Scheme - 15K

    * Standard ML - 166K



Conclusion



Common Lisp has the largest executable file (because to create an executable, you have to dump a "core image") at 25MB, while Asm has the smallest executable at 432B.


25MB??? LOL!!!



-- xytroxon
\"Many computers can print only capital letters, so we shall not use lowercase letters.\"

-- Let\'s Talk Lisp (c) 1976

Ryon

#4
Quote ... code, such as that above, looks about as understandable as so called "write-only" languages such as Forth... Maybe even less intelligible ;)

If dolphins can understand RPN, why can't you?



I'd be interested to see some well-written Forth code included in your comparison. I believe that many of the features of the above languages have been emulated in less than a page or two. In Pygmy Forth, that can't be more than ten or twenty K.
\"Give me a Kaypro 64 and a dial tone, and I can do anything!\"

anta40

#5
Quote from: "xytroxon"Nice work!!!

25MB??? LOL!!!


I just recently made a simple "Hello world" app in SBCL 1.0.25.

Yeah, the executable is about 25 MB, probably because the Lisp system itself is included.



Using strip, the size reduced to 92 KB, but of course don't work properly anymore.