<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-19684405</id><updated>2011-11-14T16:49:46.274Z</updated><title type='text'>(newLISPer)</title><subtitle type='html'>(fun with newLISP - an easy-to-use and powerful Lisp(TM)-like scripting language)</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default?start-index=101&amp;max-results=100'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>126</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-19684405.post-3284114712281589654</id><published>2010-07-12T22:50:00.000+01:00</published><updated>2011-02-07T12:13:47.031Z</updated><title type='text'>Balance those parentheses</title><content type='html'>&lt;p&gt;I called this site "unbalanced parentheses" not just because the phrase had a Lisp-oriented slant but also because it hinted at a lightweight, off-the-wall attitude to the subject, rather than a serious analytical perspective. I spent all of 10 seconds thinking of the name while staring at a registration form with a mind suddenly gone blank, and it probably wasn't the perfect choice. &lt;/p&gt;

&lt;p&gt;Looking at the logs recently, I noticed that some visitors actually appear to be looking for help balancing their unbalanced parentheses! I suspect, though, that they're not newLISPers, and probably not even Lisp users (who presumably don't need much help in that department anyway, and have their own opinions about editing code - see &lt;a href="http://gregslepak.posterous.com/on-lisps-readability"&gt;Greg's post&lt;/a&gt; for an interesting discussion). It occurred to me to try and write something about the subject. And it's high time I wrote something - anything - for this site.&lt;/p&gt;

&lt;p&gt;The parenthesis is normally employed in written language to indicate something less than essential or subordinate - material that can be omitted without major damage to the sentence. In mathematics and programming, though, parentheses are more likely to be used for organizing selected items into groups or larger entities, and also for controlling the order of evaluation. Like legs, or trousers, they nearly always go around in pairs, so that a left, opening, parenthesis should always be accompanied by a matching right, closing, parenthesis following not far behind.&lt;/p&gt;

&lt;p&gt;You know all this, of course.&lt;/p&gt;

&lt;p&gt;The problems start when you start using lots of pairs of parentheses. Take this piece of simple mathematics:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(a * (b + ((c * d) / e) - f)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As every schoolboy and schoolgirl knows, to check parentheses in your algebra homework, you read from left to right and count out loud, going up one whenever you see an opening parenthesis, and down one for each closing one. Thus the counting for the above expression would sound like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;"1 a * 2 b + 3 4 c * d 3 / e 2 - f 1"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and, since you started at 0 but ended on 1, you can conclude that your parentheses are unbalanced. Too many lefts, not enough rights, in this case.&lt;/p&gt;

&lt;p&gt;Like me, you can probably do a simple example like this without counting, just by looking at it and mentally matching lefts and rights. But you don't want to have to do this for long Lisp expressions, or expressions that span a number of lines. Your preferred text editor should help you match the parentheses - each editor offers a few great tools, but I've found that few offer everything you want.&lt;/p&gt;

&lt;p&gt;A little newLISP script can do the job automatically:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;source-code-list&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;explode&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;read-file&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;nth&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;main-args&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;source-code-list&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;cond&lt;/span&gt;
     &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt;     &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;)&lt;/span&gt;
                    &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="quoted-string"&gt;"  "&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;)&lt;/span&gt;     &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;)&lt;/span&gt;
                    &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="quoted-string"&gt;"  "&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt;)&lt;/span&gt;    &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="quoted-string"&gt;""&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;true&lt;/span&gt;          &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which, when given a piece of source code like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;mandelbrot&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="float"&gt;0.02&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="float"&gt;0.02&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;counter&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;complex&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="integer"&gt;126&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;a&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;while&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;and&lt;/span&gt;
                     &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;abs&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;:rad&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren10"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren11"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt;
                     &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;32&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;println&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;outputs something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; 
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;mandelbrot&lt;/span&gt;)&lt;/span&gt;

  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; 
    &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="float"&gt;0.02&lt;/span&gt;)&lt;/span&gt;

    &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; 
      &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="float"&gt;0.02&lt;/span&gt;)&lt;/span&gt;

      &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;counter&lt;/span&gt;)&lt;/span&gt;

      &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; 
        &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;complex&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="integer"&gt;126&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;a&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt;

      &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;while&lt;/span&gt; 
        &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;and&lt;/span&gt;                    
          &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; 
            &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;abs&lt;/span&gt; 
              &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;:rad&lt;/span&gt; 
                &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; 
                  &lt;span class="open-paren10"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; 
                    &lt;span class="open-paren11"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt;
                   &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;
                )&lt;/span&gt;
              )&lt;/span&gt;
            )&lt;/span&gt;
           &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt;

          &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&gt;&lt;/span&gt; 
            &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="integer"&gt;32&lt;/span&gt;)&lt;/span&gt;
        )&lt;/span&gt;
      )&lt;/span&gt;

      &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; 
        &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;)&lt;/span&gt;
      )&lt;/span&gt;
    )&lt;/span&gt;

    &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;println&lt;/span&gt;)&lt;/span&gt;
  )&lt;/span&gt;
)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's a curious and spacious layout not to everyone's taste, but the parentheses line up vertically, so it's easy to see what's going on. You can get even closer to the simple counting idea by replacing the parentheses with snazzy Unicode symbols, producing results like this (which won't look right if your browser isn't Unicode-friendly):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;➀define 
  ➁mandelbrot❷

  ➁for 
    ➂y -2 2 0.02❸

    ➂for 
      ➃x -2 2 0.02❹

      ➃inc counter❹

      ➃set 'z 
        ➄complex x y❺ 'c 126 'a z❹

      ➃while 
        ➄and

          ➅&lt; 
            ➆abs 
              ➇:rad 
                ➈set 'z 
                  ➉:add 
                    ➊:mul z z➀ a❿❾❽❼ 2❻

          ➅&gt; 
            ➆dec c❼ 32❻❺❹

      ➃print 
        ➄char c❺❹❸

    ➂println❸❷❶
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;using code like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;level&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;open-list&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; 
    &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="quoted-string"&gt;"  "&lt;/span&gt; &lt;span class="sym"&gt;level&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="quoted-string"&gt;"0x"&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="integer"&gt;2780&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;16&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;level&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;level&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;close-list&lt;/span&gt;)&lt;/span&gt;      
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="sym"&gt;level&lt;/span&gt;)&lt;/span&gt;      
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="quoted-string"&gt;"0x"&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="integer"&gt;2776&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;16&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;level&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="sym"&gt;source-code-list&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;cond&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt;  &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;open-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;)&lt;/span&gt;  &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;close-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;true&lt;/span&gt;       &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This technique is useful for analysing big SXML lists, too.&lt;/p&gt;

&lt;p&gt;All this is fun, but you might be thinking that there's a much quicker way to simply find out whether the parentheses are balanced. Why not just count them?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;parenthesis-count&lt;/span&gt; &lt;span class="sym"&gt;txt&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;count&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren3"&gt;(&lt;span class="quoted-string"&gt;"("&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;explode&lt;/span&gt; &lt;span class="sym"&gt;txt&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;apply&lt;/span&gt; &lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;r&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;parenthesis-count&lt;/span&gt; &lt;span class="sym"&gt;test-code&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
             &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="quoted-string"&gt;"good code! "&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt;)&lt;/span&gt;
             &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="quoted-string"&gt;"bad code!  "&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which returns something like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bad code!  (22 23)
; or
good code! (22 22)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The world, or at least your source code, might well be harmonious and balanced when there are equivalent numbers of left and right parentheses.&lt;/p&gt;

&lt;p&gt;But... you're too smart to be easily fooled by this glibness. You know better than I do that none of the code written thus far will operate perfectly on itself, let alone on the sort of code that crazy newLISPers can come up with. Obviously, the parentheses inside the strings are going to upset the counting. And when source code is formatted with comments and documentation markup, it's unlikely that these simple tricks are going to give accurate results. I'll have to analyze source code more carefully.&lt;/p&gt;

&lt;p&gt;In my view, one of the few features that newLISP currently lacks is the ability to read its own code into its own nested list format. The powerful list referencing functions such as &lt;code&gt;ref-all&lt;/code&gt; and &lt;code&gt;set-ref&lt;/code&gt; are unable to operate on source code stored in strongly hierarchical lists or S-expressions, simply because there's no obvious way to convert source to that form. I use a temporary work round, in the form of Nestor (a utility that you'll find mentioned on these pages, although the code is unlikely to work on more recent versions on newLISP). Nestor also adds the colours to the source listings you see here, too, by converting raw code into an intermediate form that can then be converted to HTML with lots of CSS SPAN tags to colourize parenthesis-enclosed strings.&lt;/p&gt;

&lt;p&gt;Here's what a piece of source code looks like after Nestor's manipulations:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="open-paren2"&gt;(&lt;span class="open-paren3"&gt;(&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"define"&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt; 
   &lt;span class="open-paren4"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"mandelbrot"&lt;/span&gt;)&lt;/span&gt; 
   &lt;span class="open-paren4"&gt;(&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt; 
   &lt;span class="open-paren4"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"for"&lt;/span&gt;)&lt;/span&gt; 
   &lt;span class="open-paren4"&gt;(&lt;span class="open-paren5"&gt;(&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren5"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"y"&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren5"&gt;(&lt;span class="quoted-string"&gt;"integer"&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren5"&gt;(&lt;span class="quoted-string"&gt;"integer"&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren5"&gt;(&lt;span class="quoted-string"&gt;"float"&lt;/span&gt; &lt;span class="quoted-string"&gt;"0.02"&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren5"&gt;(&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
   &lt;span class="comment"&gt;; ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now I can ask for all opening parentheses and obtain accurate references to them. I've put the tokenized and deformatted source in `s-list':&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;op-refs&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;ref-all&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren6"&gt;(&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt;  &lt;span class="quoted-string"&gt;"("&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;s-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;cp-refs&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;ref-all&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren6"&gt;(&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;s-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="sym"&gt;op-refs&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; &lt;/span&gt;
&lt;span class="open-paren4"&gt;(&lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
 &lt;span class="open-paren5"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
 &lt;span class="comment"&gt;;...&lt;/span&gt;

&lt;span class="sym"&gt;cp-refs&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt;&lt;/span&gt;
&lt;span class="open-paren5"&gt;(&lt;span class="open-paren6"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren6"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren6"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren6"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren6"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren6"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;11&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="comment"&gt;;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The parentheses can now be counted simply and with more confidence, knowing that strings and comments are not going to give false positives:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;op-refs&lt;/span&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; 22&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;cp-refs&lt;/span&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; 22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now it's possible to see each expression separately. I can work through every reference to an open parenthesis, and for each one, chop the end of the address off to get a reference to the expression as a whole:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;a-ref&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;sort&lt;/span&gt; &lt;span class="sym"&gt;op-refs&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren10"&gt;(&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren10"&gt;(&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren11"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren11"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;output-code&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;s-list&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="sym"&gt;a-ref&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;;-&gt;&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;:rad&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;abs&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;:rad&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren10"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;; ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This particular code sorts the expressions according to their depth (the most deeply nested first) and then displays them. It's kind of like how newLISP evaluates expressions, I fancy. Each of these expressions could be checked for unbalanced parentheses or dubious syntax, too.&lt;/p&gt;

&lt;p&gt;Alternatively, it's possible to examine particular constructions occurring in code. For example, I could look at each use of `set', to check for dodgy assignations:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;setrefs&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;ref-all&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren8"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"set"&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;s-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;setref&lt;/span&gt; &lt;span class="sym"&gt;setrefs&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;output-code&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;s-list&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="sym"&gt;setref&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;;-&gt;&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;complex&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="integer"&gt;126&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;a&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren8"&gt;( &lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can also chain these types of queries together, or look for one inside another:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;references-to-while&lt;/span&gt; 
    &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;ref&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren8"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"while"&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;s-list&lt;/span&gt; &lt;span class="built-in"&gt;match&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;references-to-set&lt;/span&gt; 
    &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;ref&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren8"&gt;(&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"set"&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;s-list&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="sym"&gt;references-to-while&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="built-in"&gt;match&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;output-code&lt;/span&gt; 
    &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;nth&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="open-paren10"&gt;(&lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="sym"&gt;references-to-while&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;references-to-set&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;s-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;;-&gt;&lt;/span&gt;
&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;:add&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;:mul&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; &lt;span class="sym"&gt;z&lt;/span&gt; )&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;However, I think I'm now barking up the wrong tree. It's relatively easy to find out whether your parentheses are balanced - but it can be much harder to notice when one or more of them are in the wrong position. This is, perhaps, a paradoxical result of Lisp's powerful yet simple syntax. Here's a typical example of what I mean:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;test&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;let&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt;
         &lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;d&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;
         &lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;result&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;result&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt; &lt;span class="sym"&gt;d&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="sym"&gt;result&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;test&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;;-&gt; &lt;/span&gt;
&lt;span class="built-in"&gt;nil&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I was hoping to see 4, but I see &lt;code&gt;nil&lt;/code&gt; instead. The parentheses are balanced, and the syntax is correct. But one of the parentheses is in the wrong place, and I doubt whether any script or tool could easily identify which one or tell you where it should be located. (Perhaps the mistake is too stupid for that!) Can you see the mistake? And can you imagine a tool or editor that could detect or prevent it happening?&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3284114712281589654?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3284114712281589654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3284114712281589654' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3284114712281589654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3284114712281589654'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2010/07/balance-those-parentheses.html' title='Balance those parentheses'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-4770724075067422739</id><published>2010-03-02T21:50:00.000Z</published><updated>2011-02-07T12:19:00.001Z</updated><title type='text'>newLISP tackles global warming</title><content type='html'>&lt;p&gt;At Armagh Observatory, they've been keeping records of the weather since the late 18th century, and detailed records since 1843. The datasets, which are freely available to all, via their web site, are considered to be high quality and very useful, suffering from few of the problems that bedevil other sets of weather observations. There are hardly any problems with gaps in the records (which would have to be filled in by a technique that scientists call 'sparse data infill' and which I would call 'making stuff up'). There are no nearby airport runways or industrial complexes that could upset the microclimates. And, because the information is published openly, there's little chance that modifications or corrections can be applied without anyone noticing. &lt;/p&gt;

&lt;p&gt;Armagh itself is a smallish town in Northern Ireland, less than 1000 miles south of the Arctic Circle although bathed in the warm currents of the Gulf stream. The name is familiar to most UK residents mainly as the place where, during the 1970s and 1980s, it was possible to witness gun, bomb, and grenade attacks in the streets, symptoms of the long-running conflict between Catholic and Protestant extremists. This was the time of "the Troubles", as they became known.&lt;/p&gt;

&lt;p&gt;I decided to attempt some simple analysis of one of the sets of weather records that the Armagh Observatory has posted on its website, using newLISP as my magnifying glass and explorer's machete. &lt;/p&gt;

&lt;p&gt;I started at &lt;a href="http://climate.arm.ac.uk/calibrated/airtemp/index.html"&gt;http://climate.arm.ac.uk/calibrated/airtemp/index.html&lt;/a&gt;, and downloaded the 'corrected daily maximum temperature' file.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;source-file&lt;/span&gt; 
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;get-url&lt;/span&gt; &lt;span class="braced-string"&gt;{http://badc.nerc.ac.uk/browse/badc/armagh/data/air_temperature/corrected_daily_max_temp/tccmax1844-2004.txt}&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I parsed this into separate lines.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;raw-data&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;source-file&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Looking at the result, there are three different types of line to process. After the initial comment lines, there are either year indicators or space-separated lists of values (in degrees Centigrade) for every month for a particular day. For example, the line starting with "1" contains the maximum recorded temperatures for the first of January, February, March, etc. up to and including the first of December. Lines starting with 29 to 31 contain a few odd-looking entries such as "-999" that indicate that there was no such day for certain months.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;("Daily maxmimum temperature at Armagh Observatory compiled and calibrated by John Butler and" 
 "Ana Garcia Suarez and Alan Coughlin, Armagh Observatory, August 2003 " 
 "Reference: Meteorological Data recorded at Armagh Observatory: Volume II - Daily, Monthly and" 

" 1844" 
"     1    1.9    6.2    6.8   16.3   19.2   18.7   17.7   16.7   24.8   15.6   10.3    6.6" 
"     2   -0.2    5.1    6.4   12.0   22.7   16.7   16.7   18.8   23.4   16.1    9.2    6.0" 

"    31    5.3 -999.0   13.3 -999.0   20.0 -999.0   15.3   22.3 -999.0   12.6 -999.0    6.3" ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To parse the raw data I used the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;let&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;values&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren4"&gt;()&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;monthly&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren4"&gt;()&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="sym"&gt;raw-data&lt;/span&gt;)&lt;/span&gt;
      &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;cond&lt;/span&gt; 
        &lt;span class="open-paren4"&gt;(&lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;6&lt;/span&gt;)&lt;/span&gt; 
                        &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&gt;=&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;1844&lt;/span&gt;)&lt;/span&gt; 
                        &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&lt;=&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;2004&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="comment"&gt;; a year start?&lt;/span&gt;
            &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="comment"&gt;; a data row?&lt;/span&gt;
        &lt;span class="open-paren4"&gt;(&lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;nil?&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;find&lt;/span&gt; &lt;span class="quoted-string"&gt;"[A-Za-z]"&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;values&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="built-in"&gt;float&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;when&lt;/span&gt; &lt;span class="sym"&gt;values&lt;/span&gt;
               &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;day&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;values&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
               &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;rest&lt;/span&gt; &lt;span class="sym"&gt;values&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;monthly&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;)&lt;/span&gt;
               &lt;span class="comment"&gt;; after 31, start a new month&lt;/span&gt;
               &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;when&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;day&lt;/span&gt; &lt;span class="integer"&gt;31&lt;/span&gt;)&lt;/span&gt; 
                   &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;cons&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;transpose&lt;/span&gt; &lt;span class="sym"&gt;monthly&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;yearly&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;)&lt;/span&gt;
                   &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;monthly&lt;/span&gt; &lt;span class="built-in"&gt;nil&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As usual, my code is a quick hack; ungraceful yet yielding a practical solution. It runs just once, because the resulting data are collected in the &lt;em&gt;yearly&lt;/em&gt; list, which is then more efficiently accessed when saved in a file and reloaded as needed:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;save&lt;/span&gt; &lt;span class="braced-string"&gt;{/Users/me/armagh-data.lsp}&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;yearly&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; later: &lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;load&lt;/span&gt; &lt;span class="braced-string"&gt;{/Users/me/armagh-data.lsp}&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;yearly&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The data list &lt;em&gt;yearly&lt;/em&gt; holds all the data in a simple hierarchical list structure. I used the &lt;em&gt;transpose&lt;/em&gt; function to 'flip' the monthly data lists as they were being processed, thus converting the unusual "first day of every month" order into what seemed a more reasonable chronological "month by month" sequence. The data is now in the following form:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(
    (1844 
      (1.9 -0.2 6.7 11.1 11.7 8.9 6.1 6.9 10.3 8.2 8.9  ...
      (6.2 5.1 4.8 6.2 6.2 6.2 4.5 4.5 5.2 4.8 6.7 8.4  ...
      (6.8 6.4 8.2 6.6 3.4 5.7 7.9 11.5 10.4 9.3 8.7 6. ...
      (16.3 12 11.2 10.8 10.7 12.1 14 14.6 17.9 15.7 13 ...
      (19.2 22.7 16.8 16 19.9 17.9 17.1 16.9 15.4 14.4  ...
      (18.7 16.7 20.1 20.8 18.6 20.2 19.1 20.1 19.1 18  ...
      (17.7 16.7 19.2 19.3 16.9 16.8 19.2 19.5 18.6 18. ...
      (16.7 18.8 17.2 18.4 19.9 15.3 16.4 16.8 16.8 18. ...
      (24.8 23.4 20.9 21.6 21.4 20.9 20.4 16.9 18.2 15. ...
      (15.6 16.1 17.3 15.1 13.4 13.3 11.8 14.8 14.9 15. ...
      (10.3 9.2 9.4 8.6 9.4 8.3 8.3 9.4 10 9.4 7.3 8.9  ...
      (6.6 6 6.3 5.6 4.2 5.4 6 2.4 3.5 3.7 3.8 3.2 2.1  ...
    (1845 
      (5 5.8 6.7 9.7 10.2 8.3 6.7 7.8 9.4 10.6 6.7 6.1  ...
      (2.6 6.7 9.2 5.9 7.5 4.5 1.7 4.5 7.5 7.3 5.3 9.5  ...
      (9 9 9.2 6.5 4 2.9 5.5 7.9 8.6 9.7 5.7 4.1 1.8 1. ...
      (15.4 12.9 15.9 15.4 9.6 13.4 15.1 11.2 10.1 9.6  ...
      (16 14.4 12.7 12.7 12.7 10.4 10.4 11.8 12.4 13.9  ...
      (19.9 19.7 14.6 15.8 16.3 16 16.1 16 16.6 18 24.2 ...
      (16.1 18.8 14.7 17.9 17.7 18.1 19.6 18.3 16.4 18. ...
      (17.7 17.3 17.8 18.9 19.2 18.7 18.9 19.5 16.7 18. ...
      (17.6 18.8 18.7 18.2 17.2 15.1 16.5 19.1 20.6 18. ...
      (13.6 12.9 13.4 10.4 11.3 13.4 11.8 12 12.3 12.3  ...
      (12.8 11.9 12.6 11.7 13.9 15.1 11.7 10.9 9.7 11 1 ...
      (8.2 5.7 2.6 7.9 6.6 6 7.1 9.9 8.7 10.4 6.6 5.1 6 ...
    (1846 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and so on.&lt;/p&gt;

&lt;p&gt;To access the data for a specific year, I can use &lt;em&gt;ref&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;let&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;year-ref&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;ref&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="sym"&gt;yearly&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;rest&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;yearly&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="sym"&gt;year-ref&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;such that:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="integer"&gt;1845&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;returns a list of 12 lists. And to access data for a specific month, I can use this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="sym"&gt;month-number&lt;/span&gt; &lt;span class="sym"&gt;year-data&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="comment"&gt;; month-number:  Jan = 1, not Jan = 0 :)&lt;/span&gt;
   &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;clean&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;n&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;-30&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;year-data&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;month-number&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So I can, for example, find data for February 1900 like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="comment"&gt;;-&gt;&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="float"&gt;3.4&lt;/span&gt; &lt;span class="float"&gt;3.3&lt;/span&gt; &lt;span class="float"&gt;2.8&lt;/span&gt; &lt;span class="float"&gt;4.4&lt;/span&gt; &lt;span class="float"&gt;3.4&lt;/span&gt; &lt;span class="float"&gt;3.3&lt;/span&gt; &lt;span class="float"&gt;3.9&lt;/span&gt; &lt;span class="float"&gt;1.6&lt;/span&gt; &lt;span class="float"&gt;4.9&lt;/span&gt; &lt;span class="float"&gt;1.1&lt;/span&gt; &lt;span class="float"&gt;2.8&lt;/span&gt; &lt;span class="float"&gt;2.4&lt;/span&gt; &lt;span class="float"&gt;4.1&lt;/span&gt; &lt;span class="float"&gt;4.4&lt;/span&gt; &lt;span class="float"&gt;7.9&lt;/span&gt; &lt;span class="float"&gt;7.7&lt;/span&gt; &lt;span class="float"&gt;7.2&lt;/span&gt; &lt;span class="float"&gt;6.3&lt;/span&gt; &lt;span class="float"&gt;3.8&lt;/span&gt; &lt;span class="float"&gt;6.2&lt;/span&gt; &lt;span class="float"&gt;5.7&lt;/span&gt; &lt;span class="float"&gt;9.9&lt;/span&gt; &lt;span class="float"&gt;11.7&lt;/span&gt; &lt;span class="float"&gt;12.8&lt;/span&gt; &lt;span class="float"&gt;9.3&lt;/span&gt; &lt;span class="float"&gt;6.8&lt;/span&gt; &lt;span class="float"&gt;5.1&lt;/span&gt; &lt;span class="float"&gt;5.6&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;em&gt;clean&lt;/em&gt; function removes those spurious -999s for February 29, February 30, and February 31.&lt;/p&gt;

&lt;p&gt;Another simple function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; &lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;average&lt;/span&gt; &lt;span class="sym"&gt;lst&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;div&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;apply&lt;/span&gt; &lt;span class="built-in"&gt;add&lt;/span&gt; &lt;span class="sym"&gt;lst&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;lst&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;calculates averages, so I can ask for the average (maximum) temperature:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;average&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
 &lt;span class="comment"&gt;;-&gt; 5.421428571&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To find years with high average maximum temperatures, I can run the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="integer"&gt;1844&lt;/span&gt; &lt;span class="integer"&gt;2004&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;year-data&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;month-average&lt;/span&gt; &lt;span class="built-in"&gt;nil&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;12&lt;/span&gt;)&lt;/span&gt;
          &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;average&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="sym"&gt;year-data&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;month-average&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;average&lt;/span&gt; &lt;span class="sym"&gt;month-average&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;year-average&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;sort&lt;/span&gt; &lt;span class="sym"&gt;year-average&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;a&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;with the following results:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;((1959 14.20715118) 
 (1846 14.12936444) 
 (1949 14.11140297) 
 (1921 13.9449232) 
 (1857 13.88233359) 
 (1945 13.87162442) 
 (2003 13.84484575)
 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and so on. If you google the year 1846, you'll find references to the typhoid outbreak that followed the long hot summer, which claimed upwards of 30 000 lives. But that summer of 1959 has been described as a 'benchmark' summer; that special kind of warm and dry golden summer that ageing Brits like to look back on nostalgically when faced with another of the wet and miserable days that they usually see so many of in July and August.&lt;/p&gt;

&lt;h2&gt;A picture is worth a 1000 words&lt;/h2&gt;

&lt;p&gt;The plain numeric data is interesting, but I like exploring using visual techniques too.&lt;/p&gt;

&lt;p&gt;My first attempts at visualizing the data used the HTML5 Canvas, but for this task I ended up switching back to PostScript. Both have their advantages: Canvas can do semi-transparency, but PostScript can't; PostScript can produce PDFs which are high resolution, and easily converted to other formats. Bear with me for a moment while I define a short library of PostScript functions.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="sym"&gt;str&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;write-line&lt;/span&gt; &lt;span class="sym"&gt;*buffer*&lt;/span&gt; &lt;span class="sym"&gt;str&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;render&lt;/span&gt; &lt;span class="sym"&gt;filename&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;let&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;fname&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;env&lt;/span&gt; &lt;span class="quoted-string"&gt;"HOME"&lt;/span&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"/Desktop/"&lt;/span&gt; &lt;span class="sym"&gt;filename&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;write-file&lt;/span&gt; &lt;span class="sym"&gt;fname&lt;/span&gt; 
                &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="quoted-string"&gt;"%!PS-Adobe-3.1"&lt;/span&gt; 
                        &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; 
                        &lt;span class="quoted-string"&gt;"%%Creator: newLISP "&lt;/span&gt; 
                        &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;sys-info&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt;)&lt;/span&gt; 
                        &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; 
                        &lt;span class="sym"&gt;*buffer*&lt;/span&gt; 
                        &lt;span class="quoted-string"&gt;"%%showpage"&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="comment"&gt;; on MacOS X, this runs the PostScript to PDF converter &lt;/span&gt;
     &lt;span class="comment"&gt;; and opens the resulting PDF file in Preview&lt;/span&gt;
     &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;exec&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="quoted-string"&gt;"open "&lt;/span&gt; &lt;span class="sym"&gt;fname&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="comment"&gt;; on other platforms, the PostScript file needs to be &lt;/span&gt;
     &lt;span class="comment"&gt;;converted with, eg, GhostScript...&lt;/span&gt;
     )&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps-prolog&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="braced-string"&gt;{%}&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;date&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="comment"&gt;; hack for big paper size&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="braced-string"&gt;{
%%BeginFeature: *PageSize Default
&lt;&lt; /PageSize [ 5000 500 ] /ImagingBBox null &gt;&gt; setpagedevice
%%EndFeature
  }&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="comment"&gt;; default font&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="braced-string"&gt;{/Helvetica-Bold findfont 12 scalefont setfont}&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="braced-string"&gt;{1 setlinewidth}&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;transform-x&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;mul&lt;/span&gt; &lt;span class="sym"&gt;x-scale&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;add&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;x-offset&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;transform-y&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;mul&lt;/span&gt; &lt;span class="sym"&gt;y-scale&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;add&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="sym"&gt;y-offset&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;line-to&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="quoted-string"&gt;"%f %f lineto\n"&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;transform-x&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;transform-y&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="quoted-string"&gt;"%f %f moveto\n"&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;transform-x&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;transform-y&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="braced-string"&gt;{newpath }&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;fill-path&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="braced-string"&gt;{fill }&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;stroke-path&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="braced-string"&gt;{stroke }&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt; &lt;span class="sym"&gt;g&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="braced-string"&gt;{%f %f %f setrgbcolor}&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt; &lt;span class="sym"&gt;g&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;set-stroke-colour&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt; &lt;span class="sym"&gt;g&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="braced-string"&gt;{%f %f %f setrgbcolor}&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt; &lt;span class="sym"&gt;g&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;dot&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="quoted-string"&gt;"%f %f %f 0 360 arc "&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;transform-x&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;transform-y&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;fill-path&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="sym"&gt;d&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;format&lt;/span&gt; &lt;span class="braced-string"&gt;{(%s) show}&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="sym"&gt;d&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I usually just copy and paste these from script to script, adapting them when necessary to the task in hand. There's a hack in the &lt;em&gt;ps-prolog&lt;/em&gt; function to force a very wide paper size. This graph is going to be huge - I'm going to attempt to plot every single data point on this graph.&lt;/p&gt;

&lt;p&gt;For colours, I define a simple colour map that goes from red to blue:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;generate-colour-list&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="float"&gt;0.001&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;sub&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;colour-map&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and a function that takes a temperature value from between -10 and 35 (they're safely below and above the range of values in the dataset) and selects an entry in the colour map. (I shouldn't hard-wire all these values in, should I?!)&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;colour-for-temp&lt;/span&gt; &lt;span class="sym"&gt;t&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;colour-map&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;mul&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;colour-map&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;div&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;add&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="sym"&gt;t&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;45&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now I'm ready to initialize:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;init&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;*buffer*&lt;/span&gt; &lt;span class="braced-string"&gt;{}&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;generate-colour-list&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;ps-prolog&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt;  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;x-offset&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;y-offset&lt;/span&gt; &lt;span class="integer"&gt;25&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;x-scale&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;y-scale&lt;/span&gt; &lt;span class="integer"&gt;6&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;x-step&lt;/span&gt; &lt;span class="float"&gt;0.02&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;start-year&lt;/span&gt; &lt;span class="integer"&gt;1844&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;end-year&lt;/span&gt; &lt;span class="integer"&gt;2004&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;graph-width&lt;/span&gt; &lt;span class="integer"&gt;2000&lt;/span&gt;
        &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;graph-height&lt;/span&gt; &lt;span class="integer"&gt;400&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and - finally - I'm ready to start drawing.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;init&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; legend&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="float"&gt;0.0&lt;/span&gt; &lt;span class="float"&gt;0.0&lt;/span&gt; &lt;span class="float"&gt;0.0&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="integer"&gt;44&lt;/span&gt; &lt;span class="braced-string"&gt;{Maximum temperatures recorded at Armagh Observatory, Degrees Centigrade}&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="integer"&gt;42&lt;/span&gt; &lt;span class="braced-string"&gt;{Monthly averages shown as grey circles}&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="integer"&gt;40&lt;/span&gt; &lt;span class="braced-string"&gt;{Daily maximum temperatures marked as small dots}&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="integer"&gt;38&lt;/span&gt; &lt;span class="braced-string"&gt;{Records shown in red/blue}&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;set-stroke-colour&lt;/span&gt; &lt;span class="float"&gt;0.7&lt;/span&gt; &lt;span class="float"&gt;0.7&lt;/span&gt; &lt;span class="float"&gt;0.7&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; x-axis&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;line-to&lt;/span&gt; &lt;span class="sym"&gt;graph-width&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;stroke-path&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; y-axis&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;-5&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;line-to&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;graph-height&lt;/span&gt; &lt;span class="integer"&gt;30&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;stroke-path&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; y rules &lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="integer"&gt;-5&lt;/span&gt; &lt;span class="integer"&gt;35&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;set-stroke-colour&lt;/span&gt; &lt;span class="float"&gt;0.9&lt;/span&gt; &lt;span class="float"&gt;0.9&lt;/span&gt; &lt;span class="float"&gt;0.9&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;line-to&lt;/span&gt; &lt;span class="sym"&gt;graph-width&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;stroke-path&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;    
     &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="integer"&gt;-7&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The way I've set this up, I'm actually adding the graph's 'furniture' by specifying vertical coordinates in degrees Celsius! It's weird, but it kind of makes sense to say "I want this line to appear at the -5° line".&lt;/p&gt;

&lt;p&gt;The graph is drawn in two passes, partly because there's no other way to get layering. In the first pass, I'm drawing grey circles to indicate the average maximum temperature for each month. In the second pass, I go back and plot each day's value as a dot:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;; first pass; draw monthly average maximums as grey dots&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="sym"&gt;start-year&lt;/span&gt; &lt;span class="sym"&gt;end-year&lt;/span&gt;)&lt;/span&gt;
      &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;year-data&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;        
      &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;12&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="float"&gt;0.7&lt;/span&gt; &lt;span class="float"&gt;0.7&lt;/span&gt; &lt;span class="float"&gt;0.7&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;monthly-average&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;average&lt;/span&gt; 
                &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="sym"&gt;year-data&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;            
           &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;y-pos&lt;/span&gt; &lt;span class="sym"&gt;monthly-average&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;dot&lt;/span&gt;  &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;monthly-average&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;mul&lt;/span&gt; &lt;span class="sym"&gt;x-step&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; 
                &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="sym"&gt;year-data&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
      &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;x-step&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; second pass; draw daily maximums&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="braced-string"&gt;{/Helvetica-Bold findfont 15 scalefont setfont}&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;hottest-day-so-far&lt;/span&gt; &lt;span class="integer"&gt;25&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;coldest-day-so-far&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="sym"&gt;start-year&lt;/span&gt; &lt;span class="sym"&gt;end-year&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="comment"&gt;; year markings&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;when&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;%&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
       &lt;span class="comment"&gt;; sometimes, 5 yearly ticks&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;set-stroke-colour&lt;/span&gt; &lt;span class="float"&gt;0.8&lt;/span&gt; &lt;span class="float"&gt;0.8&lt;/span&gt; &lt;span class="float"&gt;0.8&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;-10&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;line-to&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;-12&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;stroke-path&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="comment"&gt;; year: black&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;-14&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

    &lt;span class="comment"&gt;; always, 1 year ticks&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;set-stroke-colour&lt;/span&gt; &lt;span class="float"&gt;0.8&lt;/span&gt; &lt;span class="float"&gt;0.8&lt;/span&gt; &lt;span class="float"&gt;0.8&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;begin-path&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;move-to&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;-10&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;line-to&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="integer"&gt;-11&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;stroke-path&lt;/span&gt;)&lt;/span&gt;

    &lt;span class="comment"&gt;; get data for year&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;year-data&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;data-for-year&lt;/span&gt; &lt;span class="sym"&gt;year&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;        
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;12&lt;/span&gt;)&lt;/span&gt;

         &lt;span class="comment"&gt;; every day&lt;/span&gt;
         &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;daily-temp&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;data-for-month&lt;/span&gt; &lt;span class="sym"&gt;month&lt;/span&gt; &lt;span class="sym"&gt;year-data&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;hottest-day-so-far&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;max&lt;/span&gt; &lt;span class="sym"&gt;hottest-day-so-far&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;coldest-day-so-far&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;min&lt;/span&gt; &lt;span class="sym"&gt;coldest-day-so-far&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="comment"&gt;; lower alpha for this set&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;cond&lt;/span&gt; 
                &lt;span class="open-paren5"&gt;(&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;hottest-day-so-far&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt;)&lt;/span&gt; 
                      &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;
                      &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;dot&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                &lt;span class="open-paren5"&gt;(&lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;coldest-day-so-far&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt;)&lt;/span&gt;
                      &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;
                      &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;dot&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;true&lt;/span&gt;
                      &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;apply&lt;/span&gt; &lt;span class="sym"&gt;set-fill-colour&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;colour-for-temp&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                      &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;dot&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;daily-temp&lt;/span&gt; &lt;span class="float"&gt;0.5&lt;/span&gt;)&lt;/span&gt;
                      )&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;x-step&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="comment"&gt;; next year&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;x-pos&lt;/span&gt; &lt;span class="sym"&gt;x-step&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;; finish drawing&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;ps&lt;/span&gt; &lt;span class="braced-string"&gt;{showpage}&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;render&lt;/span&gt; &lt;span class="quoted-string"&gt;"graph.ps"&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The result is a PostScript image that is easily converted to a PDF image by the Mac's pstopdf program (or by GhostScript or Adobe Distiller). The result is too wide for publication in a typical journal, and strains at the edges of typical web pages. But it's easy to explore the PDF image using Preview or Acrobat Reader.&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/armagh-graph.png"&gt;&lt;img src="/images/armagh-graph.png" alt="Armagh max temperature PNG" width="2000" height="120" title="Armagh max temperatures PNG " /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PDF is &lt;a href="/images/armagh-graph.pdf" /&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One thing I noticed (apart from the apparent lack of much significant change during the period) is that those averages seem oddly unrepresentative of the actual daily weather. There's no disputing the arithmetic (I hope). And yet, for example, look at the summers of 1870 or 1995: daily temperatures look hot, with many above 25°, but the monthly averages appear cooler.&lt;/p&gt;

&lt;h2&gt;Every picture tells a story&lt;/h2&gt;

&lt;p&gt;This type of graphic visualization of a dataset is designed to tell a story visually, the story being, presumably, the one told by the surrounding text. However, it's possible for the author to make the storytelling even more effective by tweaking aspects of the presentation. For example, authors can change or move the axes, adjust the horizontal or vertical scales, introduce colours to influence perception, combine two or more datasets as if they were one, start or stop the graphing at a more 'telling' point in the data, or even combine two or more graphs on a single display to imply connections. For more on this fascinating subject, see the books and writings of &lt;a href="http://www.edwardtufte.com/"&gt;Edward Tufte&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I don't have a scientific story to tell, here. Rather, I'm telling a meta-story. I've made a number of small mistakes and inappropriate design decisions in this post (some deliberate, or at least, some I'm aware of, others are accidental). But, given the published and freely downloadable weather data, the code listed here, and - of course - the excellent free and open source newLISP language, it should be possible for anyone to retrace my steps, find my mistakes, and present a more credible or compelling view of the same dataset. I like to think that, if I was a scientist, I'd be happy for that to happen. But how many of us are either willing or able to do the same forensic work for other - and probably much more important - graphical visualizations of specific datasets? Currently the world of climate science seems to be in disarray, with allegations of data tampering, fraud, and much else besides. It appears that we should all be a lot more critical of the way information is presented to us.&lt;/p&gt;

&lt;h2&gt;Addendum&lt;/h2&gt;

&lt;p&gt;Thanks to Kazimir, this post was mentioned at &lt;a href="http://news.ycombinator.com/item?id=1167421"&gt;Hacker News&lt;/a&gt; - and this site had 8000+ more page views than usual that day... :) I was pleasantly surprised that quite a few of the commenters seemed to have understood that this wasn't literally an attempt at serious scientific analysis, despite the teasing headline. It was merely an observation or two from a non-scientific layman about the process of drawing pictures and conclusions from sets of numbers, while at the same time exploring some simple graph drawing using newLISP (rather than proprietary software packages such as Excel or Mathematica).&lt;/p&gt;

&lt;p&gt;I'm intrigued by the heavy data compression that's going on in much of the graphs I see on the net. A huge amount of juicy data is squeezed until dry enough to be summarized by a single wavy line. In this particular example I tried to avoid dropping any data points, but the resulting graph is, I freely admit, too hard to 'read'. When you're far enough away to see it, you can't see anything worth seeing.&lt;/p&gt;

&lt;p&gt;Kazimir also suggested that I add yearly averages. Easy enough to do, although I'm reluctant to cloud the graph with even more information:&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/armagh-graph-average.png"&gt;&lt;img src="/images/armagh-graph-average.png" alt="Armagh max temperature PNG" width="2000" height="120" title="Armagh max temperatures PNG with yearly averages" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And about this 'average' thing: talking about the average temperature for 'a decade' begs the question of why starting with a year ending with 0 is any better than starting with any other number. For example, why not compare the decade 1853-1862 with 1927-1936? And for that matter, why are we using intervals of 10 years, rather than 5 or 14? &lt;/p&gt;

&lt;p&gt;But there's an easy way to answer this particular conundrum:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;20&lt;/span&gt;)&lt;/span&gt;
   &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;yearly-averages&lt;/span&gt;)&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;     
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;s&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;slice&lt;/span&gt; &lt;span class="sym"&gt;yearly-averages&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;     
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;s&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;s&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;average&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;s&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="sym"&gt;results&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="braced-string"&gt;{ }&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;first&lt;/span&gt;  &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;sort&lt;/span&gt; &lt;span class="sym"&gt;results&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;a&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;a&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;b&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;results&lt;/span&gt; &lt;span class="built-in"&gt;nil&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

    &lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;2002&lt;/span&gt; &lt;span class="float"&gt;13.74500614&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;2001&lt;/span&gt; &lt;span class="float"&gt;13.60712154&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;5&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;2000&lt;/span&gt; &lt;span class="float"&gt;13.58417734&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;6&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1999&lt;/span&gt; &lt;span class="float"&gt;13.56492782&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;7&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1998&lt;/span&gt; &lt;span class="float"&gt;13.51859569&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;8&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1997&lt;/span&gt; &lt;span class="float"&gt;13.53881651&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;9&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1995&lt;/span&gt; &lt;span class="float"&gt;13.44358625&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1995&lt;/span&gt; &lt;span class="float"&gt;13.47582925&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;11&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1994&lt;/span&gt; &lt;span class="float"&gt;13.41562109&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;12&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1993&lt;/span&gt; &lt;span class="float"&gt;13.33442216&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;13&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1992&lt;/span&gt; &lt;span class="float"&gt;13.29284527&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;14&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1846&lt;/span&gt; &lt;span class="float"&gt;13.32015829&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;15&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1845&lt;/span&gt; &lt;span class="float"&gt;13.2821639&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;16&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1989&lt;/span&gt; &lt;span class="float"&gt;13.29859146&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;17&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1988&lt;/span&gt; &lt;span class="float"&gt;13.28216522&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;18&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1987&lt;/span&gt; &lt;span class="float"&gt;13.24236811&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;19&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1986&lt;/span&gt; &lt;span class="float"&gt;13.17982611&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="integer"&gt;20&lt;/span&gt; &lt;span class="open-paren1"&gt;(&lt;span class="integer"&gt;1985&lt;/span&gt; &lt;span class="float"&gt;13.13376373&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which tells us that, for this data set at least, the warmest periods between 3 and 13 years long, and between 16 and 20 years long, were all in the late 1980s and 1990s, but the warmest 14 and 15 year long periods were in the 1840s and 1850s. Change the sorting function to &lt;em&gt;&lt;&lt;/em&gt;, and the coldest (or, to be more precise, the least warm, on average) periods were all in the late 1890s. Tentatively, I could suggest that - at Armagh - there was a slight dip in average temperatures towards the turn of the 20th century. and that average temperatures now are equalling and surpassing the early Victorian readings by a tenth of a degree or so. But I said I was trying not to do any science here, so that's enough of that.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-4770724075067422739?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/4770724075067422739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=4770724075067422739' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/4770724075067422739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/4770724075067422739'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2010/03/newlisp-tackles-global-warming.html' title='newLISP tackles global warming'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-3108715172995552134</id><published>2010-01-10T18:00:00.000Z</published><updated>2011-02-07T12:19:06.525Z</updated><title type='text'>Tweeting frequency</title><content type='html'>&lt;p&gt;I enjoyed the blog post by taoeffect/itistoday/greg at &lt;a href="http://www.taoeffect.com/blog/2010/01/how-newlisp-took-my-breath-and-syntax-away/"&gt;Tao Effect Blog&lt;/a&gt;; a good story, well told, and full of enthusiasm (increasingly a scarce commodity in online communities). I noticed that there was a slight increase in activity in the newLISP corner of the Twitterverse as a result: up from one or two twitters a week to 12 in one day.&lt;/p&gt;

&lt;p&gt;I thought it would be nice to graph the tweet frequency. I added a short function to the Dragonfly Twitter module to draw a simple bar chart - see it &lt;a href="dragonfly_twitter"&gt;here&lt;/a&gt;. However, I'm not too impressed with the effect. I'm not convinced that the choice of bar graph is correct either.&lt;/p&gt;

&lt;p&gt;What I'm looking for is some kind of 'bar code' type of graph, where each vertical line represents a point in time to mark each tweet, and any increase in frequency shows up as a cluster of lines closer together. I don't know what this type of graph is called, or how to produce it using Google's chart API, though. If it's not possible, I'll think about drawing one using the HTML5 Canvas element. Help wanted!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: I wrote a new graph plug-in, using the HTML canvas. It looks more like the bar-code thing now. Opera doesn't draw the text, but it's OK in FireFox, Safari, and Chrome.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3108715172995552134?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3108715172995552134/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3108715172995552134' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3108715172995552134'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3108715172995552134'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2010/01/tweeting-frequency.html' title='Tweeting frequency'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-726933122875481543</id><published>2009-12-12T09:17:00.000Z</published><updated>2011-02-07T12:19:10.334Z</updated><title type='text'>Seasonal greetings</title><content type='html'>&lt;p&gt;Seasonal greetings from Unbalanced Parentheses Headquarters!&lt;/p&gt;

--- 
(for (i 0 255) 
    (push (list (rand 250) (rand 250) (rand 250) (max 0.3 (random 0 1))) colour-map -1)) 

(define (greet text x y) 
    (local (colour) (set 'colour (colour-map (rand 100)))
(write-buffer page (string (format {a_context.fillStyle = 'rgba(%d, %d, %d, %f)'; } colour) "\n"
(format {a_context.font = '%dpx sans-serif'; } (max 10 (rand 24))) "\n"
(format {a_context.fillText ('%s', %d, %d); } text x y) "\n" )))) 

(set 'page {})

(define (make-canvas nm width height)
    (write-buffer page 
        (format (string (char 60) {canvas id="%s" width="%d" height="%d"} (char 62) (char 60) {/canvas} (char 62)) nm width height)))

(seed (date-value)) 

(set 'width 800 'height 1200) 

(make-canvas "a" width height)

(write-buffer page 
    (string 
        (char 60) 
        {script type="text/javascript" language="javascript" charset="utf-8"} 
        (char 62) 
        {var a_canvas = document.getElementById("a"); } "\n" 
        {var a_context = a_canvas.getContext("2d"); } "\n"
        {a_context.font = "bold 12px sans-serif"; } "\n"
        "\n" )) 

(dotimes (i 200) 
(greet (amb "Hyvää joulua ja onnellista uutta vuotta"
"Joyeux Noël et bonne année"
"Fröhliche Weihnachten und ein gutes neues Jahr"
"聖誕節同新年快樂"
"Nollick Ghennal as Blein Vie Noa"
"圣诞节快乐"
"Buon Natale e felice anno nuovo"
"Veselé vánoce a šťastný nový rok"
"God jol og godt nyttår"
"Linksmų Kalėdų ir laimingų Naujųjų Metų"
"Crăciun fericit şi un An Nou Fericit"
"Natale hilare et annum faustum"
"Merry Christmas"
"Zorionak eta urte berri on"
"Gleðileg jól og farsælt nýtt ár"
"(println {Happy newLISPing})"
"Seasons Greetings"
"शुभ क्रिसमस"
"Καλά Χριστούγεννα!"
"Bonan Kristnaskon kaj feliĉan novan jaron"
"Prettige kerstdagen en een Gelukkig Nieuwjaar!"
"Sretna Nova godina!"
"Rõõmsaid Jõule ja Head Uut Aastat"
"Nollaig chridheil agus bliadhna mhath ùr"
"Nadolig llawen a blwyddyn newydd dda"
"明けましておめでとうございます"
"Priecīgus Ziemassvētkus un laimīgu Jauno gadu"
"Веселого Різдва і з Новим Роком"
"С наступающим Новым Годом")
(rand (- width 100)) (rand (+ height 200))))
(write-buffer page (string (char 60) {/script} (char 62)))
page
---

&lt;p&gt;This post uses the HTML 5 Canvas, and should work properly on recent standards-compliant browsers such as Safari, Firefox, and Google Chrome. The Opera browser can't handle this, which surprised me. As for Internet Explorer ... I suspect you won't see anything. Also, even if the canvas works well, there's still the problem of all those Unicode fonts. We haven't completely left behind the early days of the web, when every other page had a "Best viewed in browser X" banner.&lt;/p&gt;

&lt;p&gt;The image is generated afresh each time you load the page, so the colours and positions of the various greetings are different each time. This is because the image is generated by embedded newLISP code in the HTML database which is evaluated only at browse time. The only tricky part of the operation is to make sure the code survives being translated by Markdown into HTML, then being uploaded via xmlrpc to be stored in the newLISP database ready for being processed by Dragonfly.&lt;/p&gt;

&lt;p&gt;If you want a challenge, see how many different languages you can identify (without cheating)!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-726933122875481543?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/726933122875481543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=726933122875481543' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/726933122875481543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/726933122875481543'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/12/seasonal-greetings.html' title='Seasonal greetings'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5474815922285730017</id><published>2009-11-17T19:35:00.000Z</published><updated>2011-02-07T12:19:20.036Z</updated><title type='text'>newLISP Bayesian Comment Spam Killer</title><content type='html'>&lt;p&gt;This post describes the newLISP Bayesian Comment Spam Killer. It won't kill Bayesian comments - although it might - but it tries to kill spam comments on blogs, using Bayesian analysis.&lt;/p&gt;

&lt;p&gt;The story starts after the aspiring commenter clicks the Submit button on the comment form, and after the CGI script or web framework has extracted the information from the commenter's posted submission. To makes things easy, here are some declarations that get me quickly to the same position:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment-date&lt;/span&gt; &lt;span class="quoted-string"&gt;"20091114T163223Z"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;storyid&lt;/span&gt; &lt;span class="quoted-string"&gt;"projectnestorpart1"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment&lt;/span&gt; &lt;span class="quoted-string"&gt;"Very nice site!"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator&lt;/span&gt; &lt;span class="quoted-string"&gt;"svQrVW a href=\"http://asdfhh.com/"&lt;/span&gt;)&lt;/span&gt; 
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator-uri&lt;/span&gt; &lt;span class="quoted-string"&gt;"svQrVW a href=\"http://asdfhh.com/"&lt;/span&gt;)&lt;/span&gt; 
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;ip-address&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="quoted-string"&gt;"94.102.60.174"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first thing to do is to save this information in a file. There are many ways to do this, but I like to save data in newLISP format wherever possible, because it saves time and effort when reading it back in:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;; make a suitable path name&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;path&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="braced-string"&gt;{/Users/me/blog/comments/}&lt;/span&gt; 
    &lt;span class="sym"&gt;story-id&lt;/span&gt; &lt;span class="quoted-string"&gt;"-"&lt;/span&gt; &lt;span class="sym"&gt;comment-date&lt;/span&gt; &lt;span class="quoted-string"&gt;".txt"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;; save as association list&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment-list&lt;/span&gt; 
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment-date&lt;/span&gt; &lt;span class="sym"&gt;comment-date&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;storyid&lt;/span&gt; &lt;span class="sym"&gt;storyid&lt;/span&gt;)&lt;/span&gt; 
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator&lt;/span&gt; &lt;span class="sym"&gt;commentator&lt;/span&gt;)&lt;/span&gt; 
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment&lt;/span&gt; &lt;span class="sym"&gt;comment-text&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;ip-address&lt;/span&gt; &lt;span class="sym"&gt;ip-list&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;status&lt;/span&gt; &lt;span class="quoted-string"&gt;"spam"&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator-uri&lt;/span&gt; &lt;span class="sym"&gt;commentator-uri&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;             
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;save&lt;/span&gt; &lt;span class="sym"&gt;path&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A few weeks after opening a comments form to the intelligent citizens of cyberspace, there will be hundreds of little newLISP files in the directory, containing all kinds of comment. Each file looks something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Comments:comment-list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:comment-date&lt;/span&gt; &lt;span class="quoted-string"&gt;"20091114T163223Z"&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:storyid&lt;/span&gt; &lt;span class="quoted-string"&gt;"projectnestorpart1"&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:comment&lt;/span&gt; &lt;span class="quoted-string"&gt;"svQrVW a href=\"http://asdfhh.com/ etc etc "&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:commentator&lt;/span&gt; &lt;span class="quoted-string"&gt;"svQrVW a href=\"http://asdfhh.com/"&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:commentator-uri&lt;/span&gt; &lt;span class="quoted-string"&gt;"svQrVW a href=\"http://asdfhh.com/"&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:ip-address&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="quoted-string"&gt;"94.102.60.174"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
  &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;Comments:status&lt;/span&gt; &lt;span class="quoted-string"&gt;"spam"&lt;/span&gt;)&lt;/span&gt; 
  )&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I've added a status tag to each one, with the default value of "spam". That means that every comment so far is considered spam. That's not good (although very close to the actual truth), so I must also manually alter any genuine comments and tag them as "approved". That's a vital task, and for a while I did it by hand, until the collection of comments was large enough for me to trust the Bayesian analysis to do it automatically.&lt;/p&gt;

&lt;p&gt;Once I've got a reasonable collection of comments, I'm ready to start building the Comment Spam Killer.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;context&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Comments&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A little macro I've been using recently provides a modified &lt;em&gt;append&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define-macro&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt;)&lt;/span&gt;
  &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;eval&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;args&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;eval&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;args&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;eval&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;args&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This accepts a symbol holding a list, and a list, and adds the elements in the list at the end of the symbol's current elements. &lt;/p&gt;

&lt;p&gt;I want somewhere to store the analysis:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="sym"&gt;MAIN:spam-corpus&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This function extracts a list of the words used in all the comments:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;build-word-lists&lt;/span&gt; &lt;span class="sym"&gt;dir&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;nde&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;directory&lt;/span&gt; &lt;span class="sym"&gt;dir&lt;/span&gt; &lt;span class="braced-string"&gt;{^[^.].*txt}&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
       &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;directory?&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="sym"&gt;dir&lt;/span&gt; &lt;span class="sym"&gt;nde&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
         &lt;span class="comment"&gt;; directory, recurse&lt;/span&gt;
         &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;build-word-lists&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="sym"&gt;dir&lt;/span&gt; &lt;span class="sym"&gt;nde&lt;/span&gt; &lt;span class="quoted-string"&gt;"/"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
         &lt;span class="comment"&gt;; file: read info and make a list of its contents&lt;/span&gt;
         &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;letn&lt;/span&gt;  &lt;span class="open-paren5"&gt;(&lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;file&lt;/span&gt;            &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="sym"&gt;dir&lt;/span&gt; &lt;span class="sym"&gt;nde&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;comment-list&lt;/span&gt;    &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;load&lt;/span&gt; &lt;span class="sym"&gt;file&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;commentator&lt;/span&gt;     &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;comment&lt;/span&gt;         &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;comment-status&lt;/span&gt;  &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;status&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;commentator-ip&lt;/span&gt;  &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;ip-address&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;commentator-uri&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator-uri&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                 &lt;span class="open-paren6"&gt;(&lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren7"&gt;()&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
              &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;commentator&lt;/span&gt;       &lt;span class="quoted-string"&gt;"[^A-Za-z]"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
              &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;comment&lt;/span&gt;           &lt;span class="quoted-string"&gt;"[^A-Za-z]"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
              &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;commentator-uri&lt;/span&gt;   &lt;span class="quoted-string"&gt;"[^A-Za-z]"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
              &lt;span class="comment"&gt;; sometimes ip addresses are stored in a list...&lt;/span&gt;
              &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;list?&lt;/span&gt; &lt;span class="sym"&gt;commentator-ip&lt;/span&gt;)&lt;/span&gt;
                  &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;commentator-ip&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
              &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;cond&lt;/span&gt;
                  &lt;span class="open-paren6"&gt;(&lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;comment-status&lt;/span&gt; &lt;span class="quoted-string"&gt;"approved"&lt;/span&gt;)&lt;/span&gt;
                      &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;genuine-comments&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;clean&lt;/span&gt; &lt;span class="built-in"&gt;empty?&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                  &lt;span class="open-paren6"&gt;(&lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;comment-status&lt;/span&gt; &lt;span class="quoted-string"&gt;"spam"&lt;/span&gt;)&lt;/span&gt;
                      &lt;span class="open-paren7"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;spam-comments&lt;/span&gt; &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;clean&lt;/span&gt; &lt;span class="built-in"&gt;empty?&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the two lists can be turned into a Bayesian-ready dictionary with:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;bayes-train&lt;/span&gt; &lt;span class="sym"&gt;spam-comments&lt;/span&gt; &lt;span class="sym"&gt;genuine-comments&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;MAIN:spam-corpus&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The resulting &lt;em&gt;spam-corpus&lt;/em&gt; is a context that provides two numbers for each word in the comments. Here's an informative extract:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; &lt;span class="comment"&gt;;         &lt;/span&gt;
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"prepended"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"prescription"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;36&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"present"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"presepe"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"pretty"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"price"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"primari"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;3&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"primaria"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;6&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"primitive"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"princessdc"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"print"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"printing"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"println"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"prior"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="open-paren1"&gt;(&lt;span class="quoted-string"&gt;"priors"&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
 &lt;span class="comment"&gt;;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The contents of the &lt;em&gt;spam&lt;/em&gt; context hold a list of words and the number of times that each word occurs in the first category, the spam comments, or the second category, the genuine comments. The apparent discrepancy between &lt;em&gt;print&lt;/em&gt; and &lt;em&gt;printing&lt;/em&gt; is easily resolved once you look at the original comments - something to do with custom T-shirt printing, whereas &lt;em&gt;print&lt;/em&gt; was twice mentioned in a piece of newLISP code in a comment.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;analyse-comment&lt;/span&gt; &lt;span class="sym"&gt;file&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;letn&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;comment-list&lt;/span&gt;    &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;load&lt;/span&gt; &lt;span class="sym"&gt;file&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;commentator&lt;/span&gt;     &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;comment&lt;/span&gt;         &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;comment&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;comment-status&lt;/span&gt;  &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;status&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;commentator-ip&lt;/span&gt;  &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;ip-address&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;commentator-uri&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;commentator-uri&lt;/span&gt; &lt;span class="sym"&gt;comment-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren5"&gt;()&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;spam-comments&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren5"&gt;()&lt;/span&gt;)&lt;/span&gt;
           &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;genuine-comments&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren5"&gt;()&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;commentator&lt;/span&gt;       &lt;span class="quoted-string"&gt;"[^A-Za-z]"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;comment&lt;/span&gt;           &lt;span class="quoted-string"&gt;"[^A-Za-z]"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="sym"&gt;commentator-uri&lt;/span&gt;   &lt;span class="quoted-string"&gt;"[^A-Za-z]"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;list?&lt;/span&gt; &lt;span class="sym"&gt;commentator-ip&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;commentator-ip&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;extend&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;clean&lt;/span&gt; &lt;span class="built-in"&gt;empty?&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;spam-score&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;bayes-query&lt;/span&gt; &lt;span class="sym"&gt;word-list&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;MAIN:spam-corpus&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which returns a double-valued spam score for each comment. The two numbers are the probabilities that a comment belongs in the first or second category.&lt;/p&gt;

&lt;p&gt;It's now easy to decide whether to reject a comment based on the two numbers returned by this function. The example I started with manages to score (1 0), a clear indication that this apparently harmless phrase is, when considered as part of a comment as a whole, usually a comment from a spammer.&lt;/p&gt;

&lt;p&gt;If you're wondering where the comments form is on this site - well, there isn't one; I decided against using up disk space storing hundreds of unwanted comments!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5474815922285730017?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5474815922285730017/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5474815922285730017' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5474815922285730017'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5474815922285730017'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/11/newlisp-bayesian-comment-spam-killer.html' title='newLISP Bayesian Comment Spam Killer'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-8411109720210833090</id><published>2009-10-21T23:22:00.000+01:00</published><updated>2011-02-07T12:19:23.508Z</updated><title type='text'>Syntax matters</title><content type='html'>&lt;p&gt;My fellow newLISP blogger Kazimir &lt;a href="http://kazimirmajorinc.blogspot.com/2009/10/why-you-do-not-use-lisp-results-of-poll.html"&gt;observes in his poll analysis&lt;/a&gt; that one of the main reasons why people don't use Lisp is the syntax:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lisp syntax is the most important reason for majority of people who do not use Lisp.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The thing I don't understand, though, is what exactly is wrong with Lisp syntax that makes people avoid the language. Perhaps that's another research opportunity.&lt;/p&gt;

&lt;p&gt;Here's a simple scripting problem. Given a text file containing a series of random paragraphs, separated by percent signs, sort them so that they are ordered according to the first significant word in each paragraph. That is, words such as "a" and "the" shouldn't affect the sort order.&lt;/p&gt;

&lt;p&gt;Here's one solution:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/usr/bin/perl -w

my %sort_buckets;
my %exclusions;

my $file_to_sort = '/path/to/file_to_sort';
my $sorted_file = '/path/to/sorted_file';

while (&lt;DATA&gt;) {
    chomp;
    $exclusions{$_}++;
}

open $in, "&lt;", $file_to_sort
    or die "Can't open file: $!";

$/ = "%\n";

while (&lt;$in&gt;) {
    my $line = $_;
    my @words = split " " =&gt; $line;
    my $sort_key = '';
    for (0..$#words) {
        if ($exclusions{lc($words[$_])}) {
            next;
        } else {
            $sort_key = join "" =&gt; map { lc($_) } @words[$_..$#words];
            last;
        }
    }
    $sort_buckets{$sort_key} = $line;
}

close $in;

open $out, "&gt;", $sorted_file
    or die "Can't open file: $!";

foreach (sort keys %sort_buckets) {
    print $out $sort_buckets{$_};
}

close $out;

__END__
a
the
this
that
you
when
is
may
be
if
and
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I'm not sure I know what's going on there, but it's a typical practical (and probably quick) solution from a Perler. For comparison, here is a newLISP version:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;#!/usr/bin/env newlisp&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;common-words&lt;/span&gt; 
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="quoted-string"&gt;"a"&lt;/span&gt; &lt;span class="quoted-string"&gt;"the"&lt;/span&gt; &lt;span class="quoted-string"&gt;"this"&lt;/span&gt; &lt;span class="quoted-string"&gt;"that"&lt;/span&gt; &lt;span class="quoted-string"&gt;"you"&lt;/span&gt; &lt;span class="quoted-string"&gt;"when"&lt;/span&gt; &lt;span class="quoted-string"&gt;"is"&lt;/span&gt;&lt;span class="sym"&gt; &lt;/span&gt;&lt;span class="quoted-string"&gt;"may"&lt;/span&gt; &lt;span class="quoted-string"&gt;"be"&lt;/span&gt; &lt;span class="quoted-string"&gt;"if"&lt;/span&gt; &lt;span class="quoted-string"&gt;"and"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;remove-common&lt;/span&gt; &lt;span class="sym"&gt;text&lt;/span&gt;)&lt;/span&gt;
&lt;span class="sym"&gt; &lt;/span&gt;   &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;difference&lt;/span&gt; 
       &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;find-all&lt;/span&gt; &lt;span class="braced-string"&gt;{[a-zA-Z]+}&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;lower-case&lt;/span&gt; &lt;span class="sym"&gt;text&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
        &lt;span class="sym"&gt;common-words&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;compare&lt;/span&gt; &lt;span class="sym"&gt;text1&lt;/span&gt; &lt;span class="sym"&gt;text2&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;remove-common&lt;/span&gt; &lt;span class="sym"&gt;text1&lt;/span&gt;)&lt;/span&gt; 
       &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;remove-common&lt;/span&gt; &lt;span class="sym"&gt;text2&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;write-file&lt;/span&gt; &lt;span class="braced-string"&gt;{/path/to/sorted-file.txt}&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;join&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;sort&lt;/span&gt; 
             &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;parse&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;read-file&lt;/span&gt; &lt;span class="braced-string"&gt;{/path/to/file_to_sort.txt}&lt;/span&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"%"&lt;/span&gt;)&lt;/span&gt;
             &lt;span class="sym"&gt;compare&lt;/span&gt;)&lt;/span&gt;
     &lt;span class="quoted-string"&gt;"%"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's not clear to me why this syntax is considered unappealing, or indeed how it could be improved. I've &lt;a href="http://unbalanced-parentheses.nfshost.com/looksoktome"&gt;written about this before&lt;/a&gt;, so obviously I haven't sought hard enough for an answer.&lt;/p&gt;

&lt;p&gt;Can we measure some aspect of these two files? How about looking at the use of alphabetic characters and parentheses. For the Perl example:&lt;/p&gt;

&lt;p&gt;Parentheses: (7 7)
Braces: (11 11)
Brackets: (2 2)
Alphabetic: 403
Non-alphabetic: 642
 ratio 0.6277258567&lt;/p&gt;

&lt;p&gt;And the newLISP example:&lt;/p&gt;

&lt;p&gt;Parentheses: (17 17)
Braces: (3 3)
Brackets: (1 1)
Alphabetic: 256
Non-alphabetic: 168
 ratio 1.523809524&lt;/p&gt;

&lt;p&gt;The preponderance of the Lisp parentheses is apparent. But otherwise, the Lisp example is plainly much more alphabetic, more readable, more civilized. It's just possible that the non-mathematical syntax, and the lack of familiar and friendly "x = x + 3" forms (as learned by schoolchildren from 10 years and upwards) are enough to disarm and perplex the non-Lisper.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-8411109720210833090?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/8411109720210833090/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=8411109720210833090' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8411109720210833090'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8411109720210833090'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/10/syntax-matters.html' title='Syntax matters'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-9111544199536625000</id><published>2009-09-29T19:09:00.000+01:00</published><updated>2011-02-07T12:19:24.730Z</updated><title type='text'>newLISP at Wikibooks: volunteers wanted</title><content type='html'>&lt;p&gt;I've moved the &lt;b&gt;Introduction to newLISP&lt;/b&gt; over to Wikibooks, and you can find it here:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://en.wikibooks.org/wiki/Introduction_to_newLISP" target="_blank" title="Introduction to newLISP"&gt;http://en.wikibooks.org/wiki/Introduction_to_newLISP&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;You can find a link to the Print version on that page, which gives a complete view of the entire document, suitable for printing to PDF or saving as HTML.&lt;/p&gt;
&lt;p&gt;I've never really looked at Wikibooks before, having spent much more time using Wikipedia. Wikibooks is an attempt to build an open-source library of text books. At present there are nearly 40,000 books available to browse or edit, and I've found some quite interesting books there, although there are many others that didn't look interesting (or that needed serious editing).&lt;/p&gt;
&lt;p&gt;Although I have a few reservations about the presentation of texts in Wikibooks (I like more control over the visual appearance of documents, and the output conversions could be better), I think the Wikibooks approach is worth trying for this particular document. Like the WIkipedia and Wikibooks themselves, it's an experiment. I'm hoping that the contributions of others in the newLISP and Wikibooks communities will improve the document and make it more useful.&lt;/p&gt;
&lt;p&gt;To take part, all you have to do is visit the &lt;a href="http://en.wikibooks.org/wiki/Introduction_to_newLISP" target="_blank" title="Introduction to newLISP"&gt;home page&lt;/a&gt; and have a go at editing. One thing that really needs doing at the moment is a general check of anything where my lack of Windows- knowledge was all too obvious.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-9111544199536625000?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/9111544199536625000/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=9111544199536625000' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/9111544199536625000'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/9111544199536625000'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/09/newlisp-at-wikibooks-volunteers-wanted.html' title='newLISP at Wikibooks: volunteers wanted'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5998478784356175112</id><published>2009-09-12T16:24:00.000+01:00</published><updated>2011-02-07T12:19:29.630Z</updated><title type='text'>Updates to nldb</title><content type='html'>&lt;p&gt;I've been making some changes to the newLISP database (&lt;em&gt;nldb.lsp&lt;/em&gt;), a small set of functions that provide some rudimentary database-like tools for those occasions when even SQLITE is too heavy. I've written about it before - see &lt;a href="http://unbalanced-parentheses.nfshost.com/simpledatabaseinnewlisp"&gt;Simple database in newLISP&lt;/a&gt;; it's currently in use as the database engine for this site, although I think &lt;em&gt;engine&lt;/em&gt; is probably the wrong word (think &lt;em&gt;rubber band&lt;/em&gt; or &lt;em&gt;sail&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;The problems started when I tried to store a list in one of the columns. I had decided that, since the data was stored as a newLISP list, it ought to be able to contain nested lists too. Unfortunately, when I tried to store data using lists, some of the functions stopped working; I hadn't even considered the possibility that I might want to do that one day.&lt;/p&gt;
&lt;p&gt;And the code was already more complicated than necessary. I was jumping through hoops trying to avoid evaluating column names until the last minute. A re-write revealed that there was a simpler solution (isn't there always?). The original code at the heart of the &lt;em&gt;select-rows&lt;/em&gt; function was this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;list?&lt;/span&gt; &lt;span class="sym"&gt;selection-function&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;field&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set-ref-all&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;expand&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;field&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;field&lt;/span&gt;)&lt;/span&gt;
            &lt;span class="sym"&gt;selection-function&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;row&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;find&lt;/span&gt; &lt;span class="sym"&gt;field&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;eval&lt;/span&gt; &lt;span class="sym"&gt;selection-function&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="sym"&gt;row&lt;/span&gt; &lt;span class="sym"&gt;selection&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I really didn't like that! It's now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;list?&lt;/span&gt; &lt;span class="sym"&gt;selection-function&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;field&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;cell&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;row&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;find&lt;/span&gt; &lt;span class="sym"&gt;field&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
        &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;set-ref-all&lt;/span&gt; &lt;span class="sym"&gt;field&lt;/span&gt; &lt;span class="sym"&gt;selection-function&lt;/span&gt;
            &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;list?&lt;/span&gt; &lt;span class="sym"&gt;cell&lt;/span&gt;)&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;cell&lt;/span&gt; &lt;span class="sym"&gt;cell&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;eval&lt;/span&gt; &lt;span class="sym"&gt;selection-function&lt;/span&gt;)&lt;/span&gt;
    &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="sym"&gt;row&lt;/span&gt; &lt;span class="sym"&gt;selection&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This new syntax allows you to supply the function without having to quote every symbol. For example, here is the new simpler syntax for a simple 'and' query:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's still necessary to quote the selector function, because it can be evaluated only after the relevant data has been extracted from the data list.&lt;/p&gt;
&lt;p&gt;Another example: return a list of names and symbols and discovery dates of all elements discovered on or after 1900, sorted by year:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;
&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;&gt;=&lt;/span&gt; &lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;)&lt;/span&gt;
&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;Symbol&lt;/span&gt;)&lt;/span&gt;
&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which returns a list like this:&lt;/p&gt;
&lt;pre&gt;
&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1900&lt;/span&gt; &lt;span class="quoted-string"&gt;"Radon"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Rn"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1901&lt;/span&gt; &lt;span class="quoted-string"&gt;"Europium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Eu"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1907&lt;/span&gt; &lt;span class="quoted-string"&gt;"Lutetium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Lu"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1913&lt;/span&gt; &lt;span class="quoted-string"&gt;"Protactinium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Pa"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1923&lt;/span&gt; &lt;span class="quoted-string"&gt;"Hafnium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Hf"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1925&lt;/span&gt; &lt;span class="quoted-string"&gt;"Rhenium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Re"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1937&lt;/span&gt; &lt;span class="quoted-string"&gt;"Technetium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Tc"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1939&lt;/span&gt; &lt;span class="quoted-string"&gt;"Francium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Fr"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1940&lt;/span&gt; &lt;span class="quoted-string"&gt;"Plutonium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Pu"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1940&lt;/span&gt; &lt;span class="quoted-string"&gt;"Neptunium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Np"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="open-paren2"&gt;(&lt;span class="integer"&gt;1940&lt;/span&gt; &lt;span class="quoted-string"&gt;"Astatine"&lt;/span&gt; &lt;span class="quoted-string"&gt;"At"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;; and so on&lt;/span&gt;
)&lt;/span&gt; &lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;The functions now work with lists in columns. For example, you could add a list field that contains some keywords for each element. To build the list, push symbols onto the list for the selected rows:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;add-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;Keywords&lt;/span&gt;)&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;()&lt;/span&gt;)&lt;/span&gt;

&lt;span class="open-paren1"&gt;(&lt;span class="sym"&gt;change-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;
&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;find&lt;/span&gt;  &lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren3"&gt;(&lt;span class="quoted-string"&gt;"Iron"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Silver"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Gold"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Zinc"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Tin"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Nickel"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Lead"&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Keywords&lt;/span&gt;
&lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Metal&lt;/span&gt; &lt;span class="sym"&gt;Keywords&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This adds the keyword Metal to a few of the more obviously metallic elements. Notice that the symbol 'Metal has to be quoted here, as it usually would be in newLISP.&lt;/p&gt;
&lt;p&gt;The code lives on the Downloads page. If you need help getting it working, let me know.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5998478784356175112?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5998478784356175112/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5998478784356175112' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5998478784356175112'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5998478784356175112'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/09/updates-to-nldb.html' title='Updates to nldb'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6527147321289711175</id><published>2009-08-19T18:39:00.000+01:00</published><updated>2011-02-07T12:19:37.032Z</updated><title type='text'>End of the rainbow</title><content type='html'>&lt;p&gt;If you use Safari, the following code may look intriguingly multi-coloured.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; &lt;span class="open-paren1"&gt;(&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren2"&gt;(&lt;span class="sym"&gt;add-columns&lt;/span&gt; &lt;span class="sym"&gt;table-name&lt;/span&gt; &lt;span class="sym"&gt;new-column-list&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="sym"&gt;value&lt;/span&gt; &lt;span class="built-in"&gt;nil&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
 &lt;span class="comment"&gt;;; @syntax (add-columns table-name new-column-list value)&lt;/span&gt;
 &lt;span class="comment"&gt;;; Add more columns to table (and every row in table).&lt;/span&gt;
 &lt;span class="comment"&gt;;; You can provide a default value for them.&lt;/span&gt;
 &lt;span class="comment"&gt;;; (add-columns 'elements '(Price Postage) 0)&lt;/span&gt;
     &lt;span class="open-paren2"&gt;(&lt;span class="built-in"&gt;letex&lt;/span&gt; &lt;span class="open-paren3"&gt;(&lt;span class="open-paren4"&gt;(&lt;span class="sym"&gt;table&lt;/span&gt; &lt;span class="sym"&gt;table-name&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
         &lt;span class="open-paren3"&gt;(&lt;span class="built-in"&gt;let&lt;/span&gt; &lt;span class="open-paren4"&gt;(&lt;span class="open-paren5"&gt;(&lt;span class="sym"&gt;columns&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;table&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
              &lt;span class="comment"&gt;; mustn't duplicate columns&lt;/span&gt;
              &lt;span class="open-paren4"&gt;(&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;empty?&lt;/span&gt; &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;intersect&lt;/span&gt; &lt;span class="sym"&gt;new-column-list&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;
                  &lt;span class="open-paren5"&gt;(&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="sym"&gt;table&lt;/span&gt;
                      &lt;span class="open-paren6"&gt;(&lt;span class="built-in"&gt;cons&lt;/span&gt; &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt; &lt;span class="sym"&gt;new-column-list&lt;/span&gt;)&lt;/span&gt;
                          &lt;span class="open-paren7"&gt;(&lt;span class="built-in"&gt;map&lt;/span&gt;  &lt;span class="open-paren8"&gt;(&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren9"&gt;(&lt;span class="sym"&gt;r&lt;/span&gt;)&lt;/span&gt; 
                                   &lt;span class="open-paren9"&gt;(&lt;span class="built-in"&gt;append&lt;/span&gt; &lt;span class="sym"&gt;r&lt;/span&gt; &lt;span class="open-paren10"&gt;(&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="sym"&gt;value&lt;/span&gt; &lt;span class="open-paren11"&gt;(&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;new-column-list&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt; 
                                &lt;span class="open-paren8"&gt;(&lt;span class="built-in"&gt;rest&lt;/span&gt; &lt;span class="sym"&gt;table&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let me know if you can work out what's going on, and what you think.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6527147321289711175?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6527147321289711175/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6527147321289711175' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6527147321289711175'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6527147321289711175'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/08/end-of-rainbow.html' title='End of the rainbow'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-1345416288986845475</id><published>2009-08-15T22:59:00.000+01:00</published><updated>2011-02-07T12:19:41.070Z</updated><title type='text'>Creative functions</title><content type='html'>&lt;p&gt;There's a small family of newLISP functions that can make new lists and fill them with data. These could be described as creative functions. Let's have a quick look at some of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dup&lt;/strong&gt; is the plain but useful replicator function. It can produce a number of identical copies of an element, and it's handy in a wide variety of situations.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="braced-string"&gt;{.}&lt;/span&gt; &lt;span class="integer"&gt;20&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; "...................."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use an extra true flag to make a list of short strings, rather than a single longer string:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="braced-string"&gt;{.}&lt;/span&gt; &lt;span class="integer"&gt;20&lt;/span&gt; &lt;span class="built-in"&gt;true&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("." "." "." "." "." "." "." "." "." "." "." "." "." "." "." "." "." "." "." ".")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can use &lt;strong&gt;dup&lt;/strong&gt; when formatting text:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;space&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="braced-string"&gt;{ }&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;space&lt;/span&gt; &lt;span class="integer"&gt;20&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; "                    "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;normal&lt;/strong&gt; and &lt;strong&gt;random&lt;/strong&gt; are different in action yet - in a way - they both do similar jobs. &lt;strong&gt;random&lt;/strong&gt; creates a list of random numbers, and &lt;strong&gt;normal&lt;/strong&gt; does the same. However, the numbers returned by &lt;strong&gt;normal&lt;/strong&gt; are chosen to match a pre-determined mean and standard deviation.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;normal&lt;/span&gt; &lt;span class="integer"&gt;4&lt;/span&gt; &lt;span class="float"&gt;1.2&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="comment"&gt;; mean 4, and standard deviation 1.2&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1.87421875 4.626953125 4.88359375 1.476953125 1.573046875 2.34765625 &lt;/span&gt;
&lt;span class="comment"&gt;;  5.321875 3.84765625 4.019921875 4.10078125)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;random&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
&lt;span class="comment"&gt;;-&gt; (1.031711884 1.260753391 4.954440666 7.604752284 9.84751665 &lt;/span&gt;
&lt;span class="comment"&gt;; 9.350039866 6.844450169 3.831883312 7.497708824 3.686635417)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The ten numbers in the first list have a mean value of 4, and a standard deviation of 1.2. These numbers have a &lt;em&gt;normal&lt;/em&gt; or Gaussian distribution. The ten numbers in the second list are randomly chosen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sequence&lt;/strong&gt; is a simple but smart numerical sequence generator:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;sequence&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1 2 3 4 5 6 7 8 9 10)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;sequence&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt; &lt;span class="float"&gt;1.6&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1 2.6 4.2 5.8 7.4 9)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's also a good way of generating alphabetic sequences:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;sequence&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="quoted-string"&gt;"a"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="quoted-string"&gt;"z"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" &lt;/span&gt;
&lt;span class="comment"&gt;; "r" "s" "t"  "u" "v" "w" "x" "y" "z")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But probably the most powerful and flexible member of this creative family is &lt;strong&gt;series&lt;/strong&gt;. This has two forms, a simple numerical form that generates a geometric series, and a more general functional form.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="comment"&gt;; start at  1, factor 2&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1 2 4 8 16 32 64 128 256 512)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The functional form takes a starting expression, a function that generates the next element from the current one, and the number of elements required. &lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1 2 3 4 5 6 7 8 9 10)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, the function &lt;strong&gt;inc&lt;/strong&gt; is applied repeatedly, first to the starting element, and then to the result of each application, until the required number of elements has been generated. The result here is the same as the &lt;code&gt;sequence&lt;/code&gt; call above.&lt;/p&gt;

&lt;p&gt;The starting element doesn't have to be a number, it could be any expression. All that's required is for the function to be able to generate the 'next' element in the series given the current element.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="quoted-string"&gt;"123456789"&lt;/span&gt; &lt;span class="built-in"&gt;chop&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("123456789" "12345678" "1234567" "123456" "12345" "1234" "123" "12" "1" "")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Our code for generating the alphabet can also be written as follows. An inline anonymous function calculates the next letter given the current one:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="quoted-string"&gt;"a"&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;26&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="comment"&gt;; alphabet&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" &lt;/span&gt;
&lt;span class="comment"&gt;; "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The next element is easily found by incrementing the ASCII code of the current element.&lt;/p&gt;

&lt;p&gt;As with many of newLISP's iteration and mapping functions, you can access a counter to find out how far you've got:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;f&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;*&lt;/span&gt; &lt;span class="sym"&gt;f&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="integer"&gt;2&lt;/span&gt; &lt;span class="variable"&gt;$idx&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;20&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;  &lt;span class="comment"&gt;; factorials&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600 &lt;/span&gt;
&lt;span class="comment"&gt;; 6227020800 87178291200 1307674368000 20922789888000 355687428096000 &lt;/span&gt;
&lt;span class="comment"&gt;; 6402373705728000 121645100408832000 2432902008176640000)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;$idx returns the 'number' of the current element being calculated. It starts at 0, though, so you might want to move it up a bit, as here.&lt;/p&gt;

&lt;p&gt;To add a day - a day's worth of seconds - to a date:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;date-value&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;curry&lt;/span&gt; &lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="integer"&gt;86400&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1250112127 1250198527 1250284927 1250371327 1250457727 &lt;/span&gt;
&lt;span class="comment"&gt;; 1250544127 1250630527 1250716927 1250803327 1250889727)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice that we can't simply say &lt;code&gt;(+ 86400)&lt;/code&gt; here, because that evaluates to 86400, which makes newLISP evaluate the expression as a simple numerical series rather than a functional one. By saying &lt;code&gt;(curry + 86400)&lt;/code&gt;, we're making a new function that adds 86400 to its argument, and it's this function which is applied to each value.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="built-in"&gt;date&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;date-value&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;curry&lt;/span&gt; &lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="integer"&gt;86400&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;10&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("Wed Aug 12 22:22:24 2009" "Thu Aug 13 22:22:24 2009" &lt;/span&gt;
&lt;span class="comment"&gt;; "Fri Aug 14 22:22:24 2009" "Sat Aug 15 22:22:24 2009" &lt;/span&gt;
&lt;span class="comment"&gt;; "Sun Aug 16 22:22:24 2009" "Mon Aug 17 22:22:24 2009"&lt;/span&gt;
&lt;span class="comment"&gt;; "Tue Aug 18 22:22:24 2009" "Wed Aug 19 22:22:24 2009" &lt;/span&gt;
&lt;span class="comment"&gt;; "Thu Aug 20 22:22:24 2009" "Fri Aug 21 22:22:24 2009")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;series&lt;/strong&gt; is versatile enough that it can do the jobs of some of the other creative functions, such as &lt;strong&gt;dup&lt;/strong&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="quoted-string"&gt;"fred"&lt;/span&gt; &lt;span class="integer"&gt;6&lt;/span&gt; &lt;span class="built-in"&gt;true&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("fred" "fred" "fred" "fred" "fred" "fred")&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="quoted-string"&gt;"fred"&lt;/span&gt; &lt;span class="built-in"&gt;copy&lt;/span&gt; &lt;span class="integer"&gt;6&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("fred" "fred" "fred" "fred" "fred" "fred")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That &lt;strong&gt;copy&lt;/strong&gt; looks at home here, but in fact &lt;em&gt;any&lt;/em&gt; function that returns the same element when applied would work just as well:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="quoted-string"&gt;"fred"&lt;/span&gt; &lt;span class="built-in"&gt;begin&lt;/span&gt; &lt;span class="integer"&gt;6&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("fred" "fred" "fred" "fred" "fred" "fred")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;because:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;begin&lt;/span&gt; &lt;span class="quoted-string"&gt;"fred"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;=&gt; "fred"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Also:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="quoted-string"&gt;"fred"&lt;/span&gt; &lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="integer"&gt;6&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; ("fred" "fred" "fred" "fred" "fred" "fred")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And what about that most famous of numeric sequences, the Fibonacci series? Can &lt;strong&gt;series&lt;/strong&gt; do it? With a little thought, yes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;p&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;series&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;q&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="sym"&gt;p&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;p&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;q&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;20&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We've had to use temporary variables to store the previous values. In the inline function, notice that the finishing value, fed to the next iteration, is q.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-1345416288986845475?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/1345416288986845475/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=1345416288986845475' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1345416288986845475'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1345416288986845475'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/08/creative-functions.html' title='Creative functions'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5330388877879641167</id><published>2009-08-08T15:06:00.000+01:00</published><updated>2011-02-07T12:19:50.061Z</updated><title type='text'>The Lambdalator: RIP</title><content type='html'>&lt;p&gt;This week I killed the &lt;a href="http://unbalanced-parentheses.nfshost.com/lambdalatorthestorysofar"&gt;Lambdalator&lt;/a&gt;. The Lambdalator was my experimental public online newLISP terminal that allowed you to access a newLISP interpreter running on my server using just a web browser.&lt;/p&gt;

&lt;p&gt;It was an interesting experiment, and I made use of it on more than one occasion myself, while out on the road. (Not enough public computers run newLISP, it seems to me.) Recently, however, the Lambdalator was being visited primarily by just two groups of users: spammers, and a few Common Lisp speakers, with unsatisfactory results for all. Changes to newLISP (which is now at version 10, while the Lambdalator was written for version 9.3), and to my hosting service at NFS (who are going to charge more for dynamic sites) made me decide to kill the site. However, it may return in a future re-incarnation, as may its companion service, &lt;a href="http://unbalanced-parentheses.nfshost.com/jacktheglypher"&gt;Jack the Glypher&lt;/a&gt;, which converted text to ASCII art.&lt;/p&gt;

&lt;p&gt;I've kept the log files generated by each visitor to the site, which is how I know about the curious two groups that tried it out.&lt;/p&gt;

&lt;p&gt;The spammers simply saw an input box and a submit button, and happily wasted their time copying their bizarre web links and trying to evaluate them as newLISP. What they didn't realise was that only they could see the results of their efforts. This is the code that ran when the Evaluate button was clicked:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;CGI:get&lt;/span&gt; &lt;span class="braced-string"&gt;{evaluate}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;run-evaluator&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;CGI:get&lt;/span&gt; &lt;span class="braced-string"&gt;{expression}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;base64-dec&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;CGI:get&lt;/span&gt; &lt;span class="braced-string"&gt;{tape}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is the function that evaluated the text:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;run-evaluator&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;tape&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;unless&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;empty?&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
     &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;begin&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; 
           &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="braced-string"&gt;{&lt;pre&gt;&lt;span class="r"&gt;}&lt;/span&gt; 
             &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;escape-html&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fork-eval-string&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;trim&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
             &lt;span class="braced-string"&gt;{&lt;/span&gt;}&lt;/span&gt; 
             &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt; 
             &lt;span class="braced-string"&gt;{ &lt;span class="e"&gt;}&lt;/span&gt; 
             &lt;span class="sym"&gt;x&lt;/span&gt; 
             &lt;span class="braced-string"&gt;{&lt;/span&gt;&lt;/pre&gt;}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
           &lt;span class="sym"&gt;output&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;expression&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;log-it&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
     &lt;span class="comment"&gt;; nothing to do&lt;/span&gt;
     &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;expression&lt;/span&gt; &lt;span class="braced-string"&gt;{}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
   &lt;span class="comment"&gt;; output&lt;/span&gt;
   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="sym"&gt;tape&lt;/span&gt; &lt;span class="sym"&gt;output&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;output&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="integer"&gt;2500&lt;/span&gt; &lt;span class="sym"&gt;output&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;output&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;2500&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="braced-string"&gt;{...}&lt;/span&gt; &lt;span class="braced-string"&gt;{}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and the hidden field of the form has the following structure:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="sym"&gt;&lt;input&lt;/span&gt; &lt;span class="sym"&gt;type=&lt;/span&gt;&lt;span class="quoted-string"&gt;"hidden"&lt;/span&gt; &lt;span class="sym"&gt;name=&lt;/span&gt;&lt;span class="quoted-string"&gt;"tape"&lt;/span&gt; &lt;span class="sym"&gt;value=&lt;/span&gt; (base64-enc output) "  &lt;span class="sym"&gt;/&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where the encoded output from the evaluation is stored in the form. The effect is like a paper tape: the previous input text is sent back to the browser on the next evaluation. The invisible log file keeps a record on the server, but only the user sees what the result was. Pleasingly, only the spammer sees their own spam, and while they're staring at their own spam they're not spamming other people's blogs.&lt;/p&gt;

&lt;p&gt;The visits by Common Lisp speakers are also easy to spot in the logs. Typically they try to outsmart the interpreter, but don't know enough newLISP to do so. Sometimes they can be seen struggling to balance their parentheses without an editor to help them. Typing a few expressions, usually using &lt;em&gt;car&lt;/em&gt; and &lt;em&gt;defun&lt;/em&gt;, was often all that they tried.&lt;/p&gt;

&lt;p&gt;The Lambdalator will return soon. I'm having problems with my HTML POST parameters at the moment, and haven't had enough time or knowledge to solve them!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5330388877879641167?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5330388877879641167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5330388877879641167' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5330388877879641167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5330388877879641167'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/08/lambdalator-rip.html' title='The Lambdalator: RIP'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-9205611682699053767</id><published>2009-08-01T15:27:00.000+01:00</published><updated>2011-02-07T12:19:56.288Z</updated><title type='text'>Enter the Dragon(fly)</title><content type='html'>&lt;p&gt;I'm now running this blog on &lt;a href="http://dragonfly-newlisp.blogspot.com/"&gt;Dragonfly&lt;/a&gt;, a cool new newLISP framework written by Marc, using some code by &lt;a href="http://www.artfulcode.net/"&gt;Jeff&lt;/a&gt; (and a few minor contributions from me). It's great to have someone else in charge of all that weird web stuff such as CGI headers and .htaccessing URLs for a change. I'm hopeful that I can soon start writing some newLISP again.&lt;/p&gt;

&lt;p&gt;I've spent the last week puzzling over a bug in my newLISP XMLRPC server (which is based on Lutz' original XMLRPC code). This is designed to receive a post submitted by a desktop blogging application and enter it into the database. The obvious and visible problem I &lt;em&gt;thought&lt;/em&gt; I had was that pre-formatted text (such as a code listing) was being displayed in a web browser on a single line - the linefeeds were being removed somewhere, by something. I examined many different ways of parsing XML, and spent much time replacing carriage returns and newlines, and printing intermediate results to temporary files, but couldn't find anything that could be removing linefeeds. Many days passed...&lt;/p&gt;

&lt;p&gt;It turned out that I'd been looking in the wrong places, and had incorrectly diagnosed the problem anyway. Consider this code, which is where the server reads the incoming XML:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;not&lt;/span&gt; &lt;span class="sym"&gt;input&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;print&lt;/span&gt;
            &lt;span class="quoted-string"&gt;"Content-type: text/html\r\n\r\n"&lt;/span&gt;
            &lt;span class="quoted-string"&gt;"&lt;h2&gt;newLISP XML-RPC v."&lt;/span&gt; &lt;span class="sym"&gt;version&lt;/span&gt; 
            &lt;span class="quoted-string"&gt;": not a valid XML-RPC request&lt;/h2&gt;"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;begin&lt;/span&gt;
            &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;while&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;read-line&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;write-buffer&lt;/span&gt; &lt;span class="sym"&gt;input&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;current-line&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="comment"&gt;; &lt;-- -- -&lt;/span&gt;
            &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;process-post&lt;/span&gt; &lt;span class="sym"&gt;input&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It looks obvious now, but after I modified the code to this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;not&lt;/span&gt; &lt;span class="sym"&gt;input&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;print&lt;/span&gt;
        &lt;span class="quoted-string"&gt;"Content-type: text/html\r\n\r\n"&lt;/span&gt;
        &lt;span class="quoted-string"&gt;"&lt;h2&gt;newLISP XML-RPC v."&lt;/span&gt; &lt;span class="sym"&gt;version&lt;/span&gt; 
        &lt;span class="quoted-string"&gt;": not a valid XML-RPC request&lt;/h2&gt;"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;begin&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;while&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;read-line&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
            &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;write-buffer&lt;/span&gt; &lt;span class="sym"&gt;input&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;current-line&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="comment"&gt;; &lt;-- --&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;process-post&lt;/span&gt; &lt;span class="sym"&gt;input&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;it all started working correctly. In the entry for &lt;em&gt;read-line&lt;/em&gt;, the newLISP manual correctly states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The line always breaks on a line-feed, which is then swallowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, the newlines had been swallowed and thrown away right at the beginning of the script, and I'd noticed it only in the obvious places, where the text is pre-formatted.&lt;/p&gt;

&lt;p&gt;Finding the problem can be much harder than finding the solution!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-9205611682699053767?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/9205611682699053767/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=9205611682699053767' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/9205611682699053767'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/9205611682699053767'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/08/enter-dragonfly.html' title='Enter the Dragon(fly)'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7105229221132745297</id><published>2009-07-04T10:45:00.000+01:00</published><updated>2011-02-07T12:20:05.752Z</updated><title type='text'>newLISP and TextExpander for Mac</title><content type='html'>&lt;p&gt;A mention of &lt;a href="http://www.smileonmymac.com/TextExpander/index.html)"&gt;TextExpander&lt;/a&gt; on &lt;a href="http://www.creativityist.com/"&gt;Creativityist&lt;/a&gt;'s excellent blog reminded me that I haven't talked this cool MacOS X utility yet in relation to newLISP. So here's a quick mention.&lt;/p&gt;

&lt;p&gt;Perhaps, like me, you write text in many different applications. Sometimes you'll find me in &lt;a href="http://www.literatureandlatte.com/scrivener.html"&gt;Scrivener&lt;/a&gt;, mainly for its awesome long document handling, and other times in &lt;a href="http://illuminex.com/ecto/"&gt;ecto&lt;/a&gt;, the excellent blog post editor, ideal for WordPress. I also use &lt;a href="http://barebones.com/"&gt;BBEdit&lt;/a&gt; for all kinds of text and code, and &lt;a href="http://www.flyingmeat.com/voodoopad/"&gt;VoodooPad&lt;/a&gt;, a container for all kinds of notes and stuff. I spend quite a lot of time typing in web pages such as comment boxes and forum pages, and composing email messages, too.&lt;/p&gt;

&lt;p&gt;TextExpander works in every application you use on your Mac. The idea is simple: you type in a short abbreviation, and TextExpander expands it into a word or phrase. For example, if I want to insert a date stamp in ISO format into the current document or text area, I just type &lt;em&gt;,iso&lt;/em&gt; and TextExpander replaces it with the current date and time. (It didn't happen just then, obviously - I cheated!) Another example is 'newLISP' - it's so much quicker to type &lt;em&gt;nl&lt;/em&gt; than it is to type &lt;em&gt;newLISP&lt;/em&gt;, with that Shift key held down for the last four characters. I use various TextExpander &lt;em&gt;snippets&lt;/em&gt; when I'm writing newLISP code, such as for writing the shebang line at the beginning of every script: It's much quicker to type &lt;em&gt;nl/&lt;/em&gt; than &lt;em&gt;#!/usr/bin/env newlisp&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;There are lots of cool features in TextExpander. For example, you can control where the cursor is positioned in the expanded text - that's great with code templates. You can synchronize multiple machines across the internet, so that you always have access to your snippets, no matter which computer you're using. And you can subscribe to snippet files from an external URL, too - these are updated and maintained by the authors so you can easily benefit from their hard work. For example, there's a large database of commonly mistyped words that will catch and correct those hard-to-spot typos. And for the whimsical, feel free to choose suitable sounds that accompany the expansion of some of your abbreviations (sounds from &lt;a href="http://en.wikipedia.org/wiki/Myst"&gt;Myst&lt;/a&gt; add magic to even the most mundane memos).&lt;/p&gt;

&lt;p&gt;What makes TextExpander particularly powerful, though, is that an abbreviation can run any newLISP script. This means that a short phrase or abbreviation can launch a script and insert its standard output into the current document. There's no restriction on what you can put into the script, but it's logical to generate some useful text using &lt;em&gt;println&lt;/em&gt;, and/or use any text on the clipboard as raw input. &lt;/p&gt;

&lt;p&gt;(In fact, TextExpander is script-language-agnostic, and lets you use any shell command or AppleScript. You might even be able to run those Common Lisp scripts. But naturally, newLISP is the best choice: quick to start up, fast in operation, and as powerful as you need it to be. Whatever shell script you choose, remember to set the Content Type to &lt;em&gt;shell script&lt;/em&gt; and use the shebang line of your choice.)&lt;/p&gt;

&lt;p&gt;Here's an example of a newLISP script I use a lot. Suppose I'm writing about a web site, and viewing it in a browser window. I want to insert a link to the site in the document I'm writing. I'm probably using &lt;a href="http://daringfireball.net/projects/markdown/"&gt;Daring Fireball: Markdown&lt;/a&gt; format, so I'll want to insert both the web page title and the URL, the first enclosed in square brackets, the second in parentheses. In the document I type &lt;em&gt;,s&lt;/em&gt;, which runs the following newLISP script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;#!/usr/bin/env newlisp    &lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-safari-page-link&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;exec&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;format&lt;/span&gt; 
      &lt;span class="bracketed-string"&gt;[text]osascript -e 'tell application "Safari" to set urlLink to URL of front document
    return urlLink'[ /text]&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-safari-page-title&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;exec&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;format&lt;/span&gt; 
      &lt;span class="bracketed-string"&gt;[text]osascript -e 'tell application "Safari" to set urlTitle to name of front document
    return  urlTitle'[ /text]&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt;  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;page-url&lt;/span&gt;   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-safari-page-title&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
      &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;page-title&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-safari-page-link&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if-not&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;empty?&lt;/span&gt; &lt;span class="sym"&gt;page-url&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;print&lt;/span&gt;  &lt;span class="quoted-string"&gt;"["&lt;/span&gt;   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;page-url&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
          &lt;span class="quoted-string"&gt;"]("&lt;/span&gt;  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;page-title&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
          &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="quoted-string"&gt;""&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Yes, it's a bizarre witches' brew of newLISP and OSAscript, which is AppleScript 'typed' on the command line. I prefer to use osascript to run AppleScript commands, because it's easier to switch between languages as required. Nevertheless, it quickly does the job, inserting details of the frontmost web page into the current document, and it doesn't matter whether I'm writing in Ecto, BBEdit, or Scrivener. &lt;/p&gt;

&lt;p&gt;Using the clipboard is currently the only way you can pass information to a script invoked by a shortcut. This is restrictive, I suppose, but it provides useful functionality nevertheless. Here's a script that inserts a version of the text on the clipboard in which some HTML characters have been encoded:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;#!/usr/bin/env newlisp&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;replacements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="braced-string"&gt;{&amp;amp;}&lt;/span&gt;  &lt;span class="braced-string"&gt;{&amp;}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="braced-string"&gt;{&amp;gt;}&lt;/span&gt;   &lt;span class="braced-string"&gt;{&gt;}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="braced-string"&gt;{&amp;lt;}&lt;/span&gt;   &lt;span class="braced-string"&gt;{&lt;}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;text&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;exec&lt;/span&gt; &lt;span class="quoted-string"&gt;"pbpaste"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="sym"&gt;text&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;replacements&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
       &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;replace&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="sym"&gt;line&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;TextExpander keeps track of how many abbreviations you've used, and has a cute feature that tells you how much time you've saved, based on how many keys you haven't had to press. Apparently I've saved hours of time that I would otherwise have spent typing. In reality, of course, I've probably just spent that time playing with TextExpander...!&lt;/p&gt;

&lt;p&gt;Follow &lt;a href="http://www.creativityist.com/"&gt;this link&lt;/a&gt; to find out how you can save 20% off the price of a TextExpander licence, till the end of July 2009.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7105229221132745297?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7105229221132745297/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7105229221132745297' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7105229221132745297'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7105229221132745297'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/07/newlisp-and-textexpander-for-mac.html' title='newLISP and TextExpander for Mac'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7306346508025747760</id><published>2009-03-30T21:38:00.000+01:00</published><updated>2011-02-07T12:20:24.352Z</updated><title type='text'>Mazes</title><content type='html'>&lt;p&gt;Here's the first of what might become a series of posts about maze programming and newLISP. I've been fascinated by mazes ever since I was taken to Hampton Court Maze as a kid, and today I still occasionally write some maze-related code when I need a simple project to test out new languages or techniques. &lt;/p&gt;

&lt;p&gt;Although I wrote a lot of this code last year (or the year before), I've updated it a bit for newLISP 10, and dusted it off to keep this blog going for a while until I get some time to write some new stuff.&lt;/p&gt;

&lt;p&gt;Jumping right in: a Maze context contains the data and functions for a single maze, which is stored as a rectangular grid of squares, of which some can be solid 'walls' and the rest empty. But the maze data is stored in a flat list rather than an array. Yes, I know that arrays can be quicker, but it's often the case that lists are more versatile - more functions work on lists than on arrays - and the performance hit that I'm incurring won't really be an issue for me.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;context&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Maze&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;constant&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;wall&lt;/span&gt; &lt;span class="integer"&gt;-2&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;empty&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;    
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="sym"&gt;height&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="sym"&gt;maze-length&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;make&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;h&lt;/span&gt; &lt;span class="integer"&gt;15&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;w&lt;/span&gt; &lt;span class="integer"&gt;15&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;height&lt;/span&gt; &lt;span class="sym"&gt;h&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;width&lt;/span&gt; &lt;span class="sym"&gt;w&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;maze-length&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;*&lt;/span&gt; &lt;span class="sym"&gt;height&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;M&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="sym"&gt;wall&lt;/span&gt; &lt;span class="sym"&gt;maze-length&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This function makes a pretty solid mass of walls, with no passages or corridors. Each 'cell' of the maze &lt;em&gt;M&lt;/em&gt; can be accessed by a single index number &lt;em&gt;n&lt;/em&gt;. Using newLISP's implicit addressing, this is simply &lt;em&gt;(M n)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To make life easy for my code, the maze must be completely surrounded by a solid wall. The following simple functions test a single cell to see if it's on the outside wall:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;north-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;/&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;     
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;south-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;maze-length&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;east-perimeter?&lt;/span&gt;  &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;%&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;west-perimeter?&lt;/span&gt;  &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;%&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="sym"&gt;width&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And this is a useful test: is the cell &lt;em&gt;n&lt;/em&gt; on the outside wall?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;or&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;north-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;south-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;east-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;west-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next, to generate a maze. For now, I'm happy to have a simple randomly-generated maze. Later, I'll create mazes with a little more thought. Passages are excavated from the solid maze by the following function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;random-fill&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dotimes&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;maze-length&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
       &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;not&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;perimeter?&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
            &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;M&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;empty&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
            &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;rand&lt;/span&gt; &lt;span class="integer"&gt;3&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;      &lt;span class="comment"&gt;;  1 in 3&lt;/span&gt;
            &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;M&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;wall&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;About 1 in 3 cells are solid, which gives reasonable values. Actually, this isn't a very good function, since it sets every cell to be empty, then sets some of them back to be solid again. &lt;/p&gt;

&lt;p&gt;For simple debugging at the terminal, a simple &lt;em&gt;show&lt;/em&gt; function is useful.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;show&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;maze&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;for&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;maze-length&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;cond&lt;/span&gt;  
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;maze&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;wall&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;int&lt;/span&gt; &lt;span class="quoted-string"&gt;"\0x2588"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;maze&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;empty&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;print&lt;/span&gt; &lt;span class="quoted-string"&gt;" "&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;east-perimeter?&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;println&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That Unicode symbol is useful for printing out solid shapes. The procedure for making a maze is the following sequence of functions:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;make&lt;/span&gt; &lt;span class="integer"&gt;15&lt;/span&gt; &lt;span class="integer"&gt;15&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;random-fill&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;show&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;
███████████████
██   █  ██    █
█  █ █     ████
█ █   █    ████
█ █  █  █    ██
█ █   ███    ██
█  ██  ████ █ █
█ █ █ ██   ██ █
██ █  █ █ █   █
██ █      █ ███
█  █  █  █    █
██   █    █   █
█ █  █  █   █ █
█   █     █ █ █
███████████████
&lt;/pre&gt;

&lt;p&gt;which, although not beautiful, is at least useful. You can generate more appealing graphical displays using any technology you fancy: I've been using the new HTML Canvas module for newLISP to generate output; this is much easier than trying to get OpenGL working properly. (I drew the OpenGL graphics more or less successfully, but was unable to position the eye-point in such a way that I could actually see them.)&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/maze00001.png" alt="maze picture" title="maze" /&gt;&lt;/p&gt;

&lt;p&gt;This is a useful helper function to find the empty neighbouring cells of a given cell:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;empty-neighbours&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;clean&lt;/span&gt; &lt;span class="built-in"&gt;nil?&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;o&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
       &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;empty&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;M&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="sym"&gt;o&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
           &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="sym"&gt;o&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
       &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt; &lt;span class="sym"&gt;height&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;height&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To choose an empty cell at random:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;apply&lt;/span&gt; &lt;span class="built-in"&gt;amb&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;ref-all&lt;/span&gt; &lt;span class="sym"&gt;empty&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (69)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and to select two empty cells at random:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;start&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;apply&lt;/span&gt; &lt;span class="built-in"&gt;amb&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;ref-all&lt;/span&gt; &lt;span class="sym"&gt;empty&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; 69&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;end&lt;/span&gt;   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;apply&lt;/span&gt; &lt;span class="built-in"&gt;amb&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;ref-all&lt;/span&gt; &lt;span class="sym"&gt;empty&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; 153&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now we get to the interesting stuff. How can we find out whether there's a path from &lt;em&gt;start&lt;/em&gt; to &lt;em&gt;end&lt;/em&gt;? There are, of course, many interesting maze-solving algorithms, but I'm quite fond of the following one, which is quick, clever, and a little bit baffling too. I think it's related to an algorithm developed by someone called Bellman, but I've not traced a definitive reference yet. It's a 'tidal flooding' algorithm, and it works as follows.&lt;/p&gt;

&lt;p&gt;Before you start, you prepare a list that's the same length as the maze, but empty. This list, the &lt;em&gt;route map&lt;/em&gt;, is going to hold the solution. Then, you begin by 'standing' in the finishing cell. You look at each of the neighbouring cells in turn. If a cell is empty, you store its number in the current cell, make a note of how to get over there from where you're standing, and mark it as the current 'frontier' cell. You repeat this process until you're standing in the starting cell, or have looked at every cell. If you've reached the starting cell, you have, in the route map, values for all the cells that have been examined, with instructions (in the form of a number representing how many cells to move) on how to reach the end. It's then easy to extract a solution from this map.&lt;/p&gt;

&lt;p&gt;This is the solver function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;flood&lt;/span&gt; &lt;span class="sym"&gt;start&lt;/span&gt; &lt;span class="sym"&gt;finish&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;local&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;solution&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;frontier-cell&lt;/span&gt; &lt;span class="sym"&gt;cells&lt;/span&gt; &lt;span class="sym"&gt;route-map&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;finish&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;frontier-cell&lt;/span&gt; &lt;span class="sym"&gt;finish&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;route-map&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="built-in"&gt;nil&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;until&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;or&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;start&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="sym"&gt;cells&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;M&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;cells&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;neighbour&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;M&lt;/span&gt; &lt;span class="sym"&gt;frontier-cell&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;neighbour&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;                  
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;route-map&lt;/span&gt; &lt;span class="sym"&gt;neighbour&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;-&lt;/span&gt; &lt;span class="sym"&gt;neighbour&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;frontier-cell&lt;/span&gt; &lt;span class="sym"&gt;neighbour&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
           &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;empty-neighbours&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;M&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;start&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="sym"&gt;route-map&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run it thus:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;route-map&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;flood&lt;/span&gt; &lt;span class="sym"&gt;start&lt;/span&gt; &lt;span class="sym"&gt;end&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If there is a solution, &lt;em&gt;route-map&lt;/em&gt; contains a list of offsets for all visited cells. The following image superimposes the contents of this list on the maze: a value of 1 means move one cell to the right, a value of 15 means move down to the next row, and so on.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/maze00008.png" alt="maze picture" title="maze" /&gt;&lt;/p&gt;

&lt;p&gt;This solution can be converted into a list of North/South/East/West directions, or a list of cells to follow, by these functions:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-direction&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;cond&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;  &lt;span class="quoted-string"&gt;"E"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;  &lt;span class="quoted-string"&gt;"S"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"W"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="sym"&gt;n&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"N"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-solution&lt;/span&gt; &lt;span class="sym"&gt;route-map&lt;/span&gt; &lt;span class="sym"&gt;start&lt;/span&gt; &lt;span class="sym"&gt;finish&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;local&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;solution&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;start&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;until&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="sym"&gt;finish&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
         &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; 
          &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt; 
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-direction&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;route-map&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;solution&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
         &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;current-cell&lt;/span&gt;  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;route-map&lt;/span&gt; &lt;span class="sym"&gt;current-cell&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="sym"&gt;solution&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;em&gt;solution&lt;/em&gt; then looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; ((69 "E") 
  (70 "S") 
  (85 "E") 
  (86 "E") 
  (87 "S") 
  (102 "S") 
  (117 "S") 
  (132 "W") 
  (131 "S")  
  (146 "S")  
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and these can be displayed graphically, or written down on a piece of cheese and fed to a maze-solving micromouse.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/maze00004.png" alt="maze picture" title="maze" /&gt;

&lt;p&gt;By colouring the visited cells in blue, you can see the way the search has 'flowed' outward from the green starting point to the red end point, like a wave slowly flooding the maze. It's worth noting that this version of the flooding algorithm doesn't search the entire maze, only as much of the maze as is required. Here are some more examples:&lt;/p&gt;

&lt;img src="/images/maze00005.png" alt="maze picture" title="maze" /&gt;
&lt;img src="/images/maze00006.png" alt="maze picture" title="maze" /&gt;
&lt;img src="/images/maze00007.png" alt="maze picture" title="maze" /&gt;&lt;/p&gt;

&lt;p&gt;You can see, though, that the algorithm doesn't always find the minimum possible number of cells.&lt;/p&gt;

&lt;p&gt;Performance can vary, depending on the configuration of the maze and the chosen start and end points. I'm seeing solutions generated in about 600 microseconds for these simple 15 by 15 mazes, which is OK.&lt;/p&gt;

&lt;p&gt;Next time, I'm going to be looking at another way to represent and draw mazes. Well. if I get time, I might!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7306346508025747760?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7306346508025747760/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7306346508025747760' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7306346508025747760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7306346508025747760'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/03/mazes.html' title='Mazes'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6798315256733104097</id><published>2009-02-10T19:46:00.000Z</published><updated>2011-02-07T12:20:35.206Z</updated><title type='text'>On my command</title><content type='html'>&lt;p&gt;I don't use the newLISP terminal very much. Most of the time, I use an editor such as BBEdit to write and run newLISP scripts. The terminal interface to newLISP is certainly useful, for testing commands and for debugging, but I don't find it a comfortable environment for writing scripts. &lt;/p&gt;

&lt;p&gt;In BBEdit I can define a key combination (such as Command + Return) that evaluates the current selection and inserts the result into the text. I have all the convenience of a handy newLISP terminal in addition to the comforts of a text editor, such as code completion, syntax colouring, parenthesis-balancing, and so on.&lt;/p&gt;

&lt;p&gt;Of course, the newLISP terminal isn't meant to be an editor or development environment - you certainly don't want to load one of those every time you want to run a script.&lt;/p&gt;

&lt;p&gt;But the terminal is cool in its own way. And, because the philosophy of Lisp is that you can adapt the language to suit your purposes, it's no surprise that the newLISP terminal is adaptable too.&lt;/p&gt;

&lt;p&gt;Here's an extract from a editing session using the newLISP terminal to show the sort of thing I mean:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;newLISP v.10.0.0 on OSX IPv4 UTF-8, execute 'newlisp -h' for more info.

OK    (define (fred a b c)
1       (if (&gt; a 0)
2            (println (* b c))
2            (println (/ b c))
2        )
1      )

(lambda (a b c) 
 (if (&gt; a 0) 
  (println (* b c)) 
  (println (/ b c))))

OK    
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;First thing to notice are the prompts. The initial OK prompt (makes a change from the "&gt;") changes after the first line of typing to indicate the current nesting level. Also, the [CMD] and [/CMD] tags that you normally have to use to enclose multi-line text input aren't necessary (and don't currently work in this set-up!).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OK    (fred
1     1 2
1     3
1     )
6
6

OK    
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Just as the &lt;em&gt;if&lt;/em&gt; statement was spread over four lines, the &lt;em&gt;(fred  1 2 3)&lt;/em&gt; expression sprawls over more lines than necessary.&lt;/p&gt;

&lt;p&gt;It's quite easy to make newLISP do this. (It's much harder to make it robust and reliable - I'm not that sort of programmer.) You need to use two built-in newLISP functions: &lt;em&gt;command-event&lt;/em&gt; and &lt;em&gt;prompt-event&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Of the two, &lt;em&gt;command-event&lt;/em&gt; is the more powerful. You assign a function to &lt;em&gt;command-event&lt;/em&gt;, and that function handles all incoming text from the user. You can do what you want with that text, so it's not too difficult to make it build up a multi-line buffer, delaying evaluation until some later stage. And by keeping track of how many parentheses have been typed, and only evaluating the buffer when the nesting level returns to 0, you can get primitive multi-line editing support.&lt;/p&gt;

&lt;p&gt;The code to make this happen is this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;context&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="built-in"&gt;Multiline&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;buffer&lt;/span&gt; &lt;span class="quoted-string"&gt;""&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;indent&lt;/span&gt; &lt;span class="quoted-string"&gt;"    "&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Multiline:Multiline&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;explode&lt;/span&gt; &lt;span class="sym"&gt;c&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;buffer&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;cond&lt;/span&gt; 
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="quoted-string"&gt;"§"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;buffer&lt;/span&gt; &lt;span class="quoted-string"&gt;""&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="quoted-string"&gt;" "&lt;/span&gt; &lt;span class="sym"&gt;buffer&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;when&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;unless&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;catch&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;eval-string&lt;/span&gt; &lt;span class="sym"&gt;buffer&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"\n"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;error&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="quoted-string"&gt;"! "&lt;/span&gt; &lt;span class="sym"&gt;error&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
        &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;buffer&lt;/span&gt; &lt;span class="quoted-string"&gt;""&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="quoted-string"&gt;""&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;prompt-event-function&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dup&lt;/span&gt; &lt;span class="quoted-string"&gt;" "&lt;/span&gt; &lt;span class="sym"&gt;nest&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;indent&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="quoted-string"&gt;"OK"&lt;/span&gt; &lt;span class="sym"&gt;indent&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;context&lt;/span&gt; &lt;span class="built-in"&gt;MAIN&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;command-event&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="built-in"&gt;Multiline&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;prompt-event&lt;/span&gt;  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Multiline:prompt-event-function&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One obvious problem with this simplistic parsing is that parentheses inside strings have to balance:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OK    (set 'x "(
2      this is a string)
1     "
1     )
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;otherwise you would have to write code that recognises the different types of string and stops counting parentheses while inside them.&lt;/p&gt;

&lt;p&gt;The current nesting level is output by the function that &lt;em&gt;prompt-event&lt;/em&gt; function calls whenever a prompt is required.&lt;/p&gt;

&lt;p&gt;You might have noticed the action in the &lt;em&gt;cond&lt;/em&gt; clause on encountering a &lt;em&gt;§&lt;/em&gt; character, the section sign. Well, this code was tricky to debug, because every mistake I made meant that I could no longer enter any newLISP commands... I thought an escape hatch would be a useful addition.&lt;/p&gt;

&lt;p&gt;Improvements will be most welcome!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6798315256733104097?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6798315256733104097/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6798315256733104097' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6798315256733104097'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6798315256733104097'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/02/on-my-command.html' title='On my command'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-8253161682881765585</id><published>2009-01-30T22:47:00.004Z</published><updated>2011-02-07T12:21:05.241Z</updated><title type='text'>About this site</title><content type='html'>&lt;p&gt;This site is dedicated to newLISP, which is a fun, free, easy-to-use Lisp-like scripting language available for most current operating systems. The posts are mainly short articles exploring various aspects of the language, and there are also some occasional attempts at humour and perhaps fun too. I'm not a programmer, so don't expect to see any serious programming voodoo here.&lt;/p&gt;

&lt;p&gt;The site is not associated with the developer of newLISP, Lutz Mueller, who can be found at &lt;a href="http://www.newlisp.org/"&gt;the official newLISP web site&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Go away if...&lt;/h3&gt;

&lt;p&gt;If you're a fundamentalist Lisp user, a newLISP-hater, or are just not interested in exploring newLISP, then please don't bother to read anything here, and please don't waste your time and mine by complaining about how newLISP isn't a proper Lisp, isn't as good as (insert name of some other Lisp here), doesn't have Feature X or Facility Y, blah blah blah. You are wasting your (presumably) valuable time on a pointless and some would say objectionable activity - go and do something more useful with your time.&lt;/p&gt;

&lt;p&gt;If you're a spammer, don't waste your time trying to get your comments published here. All comments are carefully reviewed before being posted, and I'm unlikely to approve yours, obviously. Also, your IP address will be added (possibly more than once) to the banned list, which means that sooner or later you won't be able to load the site at all. It's wasting much more of your time than mine. Again, please find something more useful to do. The world needs improving; try and do something to make it better.&lt;/p&gt;

&lt;h3&gt;Dragonfly&lt;/h3&gt;

&lt;p&gt;This site is also a testing ground for newLISP code. It's now powered by the speedy and generally awesome &lt;a href="http://www.rundragonfly.com/"&gt;Dragonfly web framework&lt;/a&gt;. Before then, it used by my attempt at a 'minimalist' blogging application called &lt;em&gt;The Parenthesis Engine&lt;/em&gt;. The Parenthesis Engine was a replacement for the previous newLISP blogging application which used to run this site, which has been mentioned before in these posts as the &lt;em&gt;LambdaPress&lt;/em&gt;. The LambdaPress was based on a SQLite database, and is now obsolete. Some of the older posts reference some code excursions that are now meaningless, and not applicable to Dragonfly. Before then, the site was hosted on &lt;a href="http://newlisper.blogspot.com/"&gt;Blogger&lt;/a&gt;, and it's still there, although all the old posts were migrated over.&lt;/p&gt;

&lt;h3&gt;Downloads&lt;/h3&gt;

&lt;p&gt;There are a few files on the downloads page that you can have a look at. If you need any help with these, or wish to complain about the coding style, let me know or ask on the forums.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-8253161682881765585?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/8253161682881765585/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=8253161682881765585' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8253161682881765585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8253161682881765585'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/01/about-this-site.html' title='About this site'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7839122695610594857</id><published>2009-01-30T22:47:00.003Z</published><updated>2011-02-07T12:21:00.111Z</updated><title type='text'>Process Finder Selection: the return of AppleScript (for a few seconds
at least)</title><content type='html'>&lt;p&gt;I recently wanted to process a group of files that had been selected in the MacOS X Finder. (This is a Mac-specific post!) Obviously I wanted to do the work with newLISP. Here's a three-step reminder of how to do it.&lt;/p&gt;

&lt;p&gt;First, enable the Script icon on the menubar, if it isn't already. (Many people have described how to do this; here's &lt;a href="http://www.vanwilligen.nl/mac/applescripts.html"&gt;a good one&lt;/a&gt;.) The Script Menu shows both global scripts and the scripts that are designed for the application you’re running. Scripts to be run in the Finder should be in /Users/you/Library/Scripts/Applications/Finder/. You don’t have to use a special suffix for the file.&lt;/p&gt;

&lt;p&gt;Secondly, save the Finder-related scripts in that folder, and don’t forget to make them executable first! Here's the basic idea:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;#!/usr/bin/env newlisp&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;applescript&lt;/span&gt; 
&lt;span class="bracketed-string"&gt;[text]
set rslt to {}
tell application "Finder"
  set s to selection
  repeat with i in s
    set end of rslt to (POSIX path of (i as alias))
  end repeat
end tell
rslt
[ /text]&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;set&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;file-list&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;parse&lt;/span&gt; 
   &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; 
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;exec&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="quoted-string"&gt;"osascript -e '"&lt;/span&gt; &lt;span class="sym"&gt;applescript&lt;/span&gt; &lt;span class="quoted-string"&gt;"'"&lt;/span&gt; &lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;", "&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;f&lt;/span&gt; &lt;span class="sym"&gt;file-list&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;   
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;println&lt;/span&gt; &lt;span class="sym"&gt;f&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;; printed output goes to the console&lt;/span&gt;

&lt;span class="sym"&gt;/usr/share/newlisp/guiserver.jar&lt;/span&gt;
&lt;span class="sym"&gt;/usr/share/newlisp/newLISP128.png&lt;/span&gt;
&lt;span class="sym"&gt;/usr/share/newlisp/modules/cgi.lsp&lt;/span&gt;
&lt;span class="sym"&gt;/usr/share/newlisp/util/newlisp.vim&lt;/span&gt;  &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, enjoy working in newLISP rather than AppleScript! :)&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7839122695610594857?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7839122695610594857/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7839122695610594857' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7839122695610594857'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7839122695610594857'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/01/process-finder-selection-return-of.html' title='Process Finder Selection: the return of AppleScript (for a few seconds&#xA;at least)'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-2147017883331596168</id><published>2009-01-30T22:47:00.002Z</published><updated>2011-02-07T12:20:56.244Z</updated><title type='text'>Simple database in newLISP</title><content type='html'>&lt;p&gt;As I was updating the sqlite section of the &lt;a href="http://www.newlisp.org/index.cgi?Documentation"&gt;Introduction to newLISP&lt;/a&gt; for version 10 last year, I wondered whether it would be easy to write a simple database interface using pure newLISP rather than SQL. This would be useful for those applications where a sqlite or other type of database might be difficult to install, implement, or just larger or more powerful than necessary.&lt;/p&gt;

&lt;p&gt;Join me in the following &lt;a href="http://en.wikipedia.org/wiki/Thought_experiment"&gt;thought experiment&lt;/a&gt; to see whether it would work. I'll use the periodic table example again, for this 'elementary' exploration.&lt;/p&gt;

&lt;p&gt;We would start by loading the database code as a module:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;load&lt;/span&gt; &lt;span class="quoted-string"&gt;"nldb.nl"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To make it easier to use (since you wouldn't have to keep supplying context prefixes), let's enter the module:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;context&lt;/span&gt; &lt;span class="sym"&gt;nldb&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Like many modern applications, let's assume there's an empty database already there by default, waiting to be filled. So the first real job would be to create a table. We would specify its name, and the names of the columns too:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;create-table&lt;/span&gt; 
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; 
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;No&lt;/span&gt; &lt;span class="sym"&gt;AtomicWt&lt;/span&gt; &lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;Symbol&lt;/span&gt; &lt;span class="sym"&gt;MP&lt;/span&gt; &lt;span class="sym"&gt;BP&lt;/span&gt; &lt;span class="sym"&gt;Density&lt;/span&gt; &lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="sym"&gt;Group&lt;/span&gt; &lt;span class="sym"&gt;IonEn&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Hopefully that would create a new table called &lt;em&gt;elements&lt;/em&gt;, with the columns as shown. The next task would be to add some data. To add a row, you would expect to be able to do something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;add-row&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="float"&gt;1.0079&lt;/span&gt; &lt;span class="quoted-string"&gt;"Hydrogen"&lt;/span&gt; &lt;span class="quoted-string"&gt;"H"&lt;/span&gt; &lt;span class="integer"&gt;-259&lt;/span&gt; &lt;span class="integer"&gt;-253&lt;/span&gt; &lt;span class="float"&gt;0.09&lt;/span&gt; &lt;span class="float"&gt;0.14&lt;/span&gt; &lt;span class="integer"&gt;1776&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="float"&gt;13.5984&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Although, in that form, the data has to be provided in full, in the right order. A refinement might be to be able to type this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;new-row&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quoted-string"&gt;"Unobtainium"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Symbol&lt;/span&gt; &lt;span class="quoted-string"&gt;"Ub"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;    &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where we can assume that any missing or unspecified columns are left undefined.&lt;/p&gt;

&lt;p&gt;After using these and similar functions interactively, it would be useful to be able to see what's been done so far. Logically:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="sym"&gt;tables&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (elements)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;should return a list of tables, and, to see the columns in a table:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;list-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;;-&gt; (No AtomicWt Name Symbol MP BP Density EarthCrust DiscoveryYear Group IonEn)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To see the whole database, it would be useful to have a command like &lt;em&gt;show&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;show&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;for output such as this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Contents of database db
 1 table: (elements)

 Table:  elements
 Columns (No AtomicWt Name Symbol MP BP Density EarthCrust DiscoveryYear Group IonEn)
 Rows:   
  (1 1.0079 "Hydrogen" "H" -259 -253 0.09 0.14 1776 1 13.5984)
  (2 4.0026 "Helium" "He" -272 -269 0 0 1895 18 24.5874)
  (3 6.941 "Lithium" "Li" 180 1347 0.53 0 1817 1 5.3917)
  ; and so on
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When using a database, one of the primary tasks is to select rows in a table. So something like this would be a must-have function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which should return all rows. But you would also expect to be able to specify a filtering function, so that only certain rows would be selected. So, to find elements discovered since 1900:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;=&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which should return:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(((No 43) (AtomicWt 98) 
  (Name "Technetium") 
  (Symbol "Tc") 
  (MP 2200) 
  (BP 4877) 
  (Density 11.5) 
  (EarthCrust 0) 
  (DiscoveryYear 1937) 
  (Group 7) 
  (IonEn 7.28)) 
; and so on
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and to find common elements (more than 5% of the earth's crust):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;=&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which should return:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(((No 8) (AtomicWt 15.9994) 
  (Name "Oxygen") 
  (Symbol "O") 
  (MP -218) 
  (BP -183) 
  (Density 1.43) 
  (EarthCrust 46.71) 
  (DiscoveryYear 1774) 
  (Group 16) 
  (IonEn 13.6181)) 
; and so on
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ideally, the function used to select rows could be &lt;em&gt;any&lt;/em&gt; newLISP function that returns true (select) or nil (don't select) based on the contents of each row's column:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;find&lt;/span&gt; &lt;span class="quoted-string"&gt;"ium"&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;starts-with&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quoted-string"&gt;"C"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="comment"&gt;; regular expression&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and it should also be easy to specify more than one row-filter function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or do set intersections:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;intersect&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It should be possible to display just a few columns of the results:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;EarthCrust&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which should return:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(((Name "Hydrogen") (EarthCrust 0.14)) 
 ((Name "Helium") (EarthCrust 0)) 
 ((Name "Lithium") (EarthCrust 0)) 
 ((Name "Beryllium") (EarthCrust 0))
; and so on
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;Symbol&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;row&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;find&lt;/span&gt; &lt;span class="quoted-string"&gt;"ium"&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;row&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which should return:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(((Name "Helium") (Symbol "He")) 
 ((Name "Lithium") (Symbol "Li")) 
 ((Name "Beryllium") (Symbol "Be")) 
 ((Name "Sodium") (Symbol "Na")) 
; and so on
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or even:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;map&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;curry&lt;/span&gt; &lt;span class="built-in"&gt;lookup&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;starts-with&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quoted-string"&gt;"A"&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"Aluminum"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Argon"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Arsenic"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Antimony"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Astatine"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Actinium"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Americium"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once this selection technique works, extending it to delete rows shouldn't be too difficult:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;delete-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1945&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which should delete every element discovered after 1945.&lt;/p&gt;

&lt;p&gt;So, how would we change the data in a row? It would make sense if you could pass a selection function to find some rows, and were then able to supply a column name and a modification function that could transform the data in those rows. Something like this, perhaps:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;change-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quoted-string"&gt;"Hydrogen"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;BP&lt;/span&gt; &lt;span class="built-in"&gt;inc&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where the &lt;em&gt;inc&lt;/em&gt; function would be applied to the contents of the BP (boiling point) column for all matching rows. The effect of this example would be to increase the value of Hydrogen's boiling point by 1 degree.&lt;/p&gt;

&lt;p&gt;And the modification function should be, not just a newLISP primitive, but any function that modifies the current value:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;change-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;ends-with&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quoted-string"&gt;"ium"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="integer"&gt;-2000&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There, the anonymous function would set the value of &lt;em&gt;x&lt;/em&gt; to -2000, and this function would be applied to the value of the column DiscoveryYear, for all matching (ie ending with "ium") elements.&lt;/p&gt;

&lt;p&gt;A useful function to have would be a regular expression search. It could look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;find-text&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quoted-string"&gt;"pb"&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;;-&gt; (((Name "Lead")))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, a case-insensitive regex search for "pb", filtered to show only names, would return just a single element:&lt;/p&gt;

&lt;p&gt;To sort a table, you'd want to specify the table, the column, and a function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;sort-table&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;BP&lt;/span&gt; &lt;span class="built-in"&gt;&gt;&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This returns the table, after sorting it by each element's boiling point. You should also be able to supply your own sorting function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;sort-table&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="comment"&gt;; returns the table, sorted with the shortest name first&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To sort a row, it would be cool if you could apply a &lt;em&gt;sort-rows&lt;/em&gt; function to a selection of columns:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;sort-rows&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;Symbol&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; 
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="comment"&gt;;-&gt; (((Name "Rutherfordium") (Symbol "Rf")) &lt;/span&gt;
&lt;span class="comment"&gt;;     ((Name "Praseodymium") (Symbol "Pr")) &lt;/span&gt;
&lt;span class="comment"&gt;;     ((Name "Protactinium") (Symbol "Pa")) &lt;/span&gt;
&lt;span class="comment"&gt;; and so on     &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This example is better, as the whole table isn't sorted, just the extracts are.&lt;/p&gt;

&lt;p&gt;There's no reason why we shouldn't be able to add new columns whenever we like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;add-columns&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Explosiveness&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, we'd obviously want to save the database:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;save-db&lt;/span&gt; &lt;span class="quoted-string"&gt;"/Users/me/elements.nldb"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That would save just the tables, not the entire &lt;em&gt;nldb&lt;/em&gt; context with its functions as well. This way, we could develop the two sides (code and data) separately, even though they both live together when they're active. Loading the database would be a two-stage process. First, load the database module:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;load&lt;/span&gt; &lt;span class="quoted-string"&gt;"nldb.nl"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;then load the database:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;load&lt;/span&gt; &lt;span class="quoted-string"&gt;"/Users/me/elements.nldb"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, what do you think? To me, this first sketch of the 'user interface' didn't look too bad. So I decided to have a go at writing a fully-functional version. In the early stages I was helped by some work done last year by &lt;em&gt;kinghajj&lt;/em&gt; on the newLISP forum. I ended up using a bit of his code, but changing the model, making it a bit easier for me to understand. (If you're out there, kinghajj, thanks, and hope you're enjoying your current language!)&lt;/p&gt;

&lt;p&gt;I'm going to use this simple database for storing the entries on this blog. Unfortunately I'll have to wait until Nearly Free Speech get round to updating their version of newLISP to version 10, since I've been using some 10-only constructs.&lt;/p&gt;

&lt;p&gt;If you'd like to help test out the code, contact me!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-2147017883331596168?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/2147017883331596168/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=2147017883331596168' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2147017883331596168'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2147017883331596168'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/01/simple-database-in-newlisp.html' title='Simple database in newLISP'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6196867118941439906</id><published>2009-01-30T22:47:00.001Z</published><updated>2011-02-07T12:20:43.881Z</updated><title type='text'>Simple database, second attempt</title><content type='html'>&lt;p&gt;Rewriting is part of a writer's job, and with practice it's easy to be ruthless about your previous drafts. &lt;a href="http://www.jakonrath.com/tips.html"&gt;This chap&lt;/a&gt; has written more about the process than I ever could. &lt;/p&gt;

&lt;p&gt;But it's slightly harder for me to be ruthless when editing code, because once I manage to get something working, I'm reluctant to break it just to try and make it better. But I'm slowly getting the hang of this too. &lt;/p&gt;

&lt;p&gt;The simple database I started working on recently was OK, but there were things I didn't like about it. Perhaps the main problem area was with the way I switched between a simple list of lists:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;((1 1.0079 "Hydrogen" "H" -259 -253 0.09 0.14 1776 1 13.5984) 
 (2 4.0026 "Helium" "He" -272 -269 0 0 1895 18 24.5874) 
 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;to a list of association lists, where the keys were the 'column' names:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(((No 1) (AtomicWeight 1.0079) (Name "Hydrogen") ... )
 ((No 2) (AtomicWeight 4.0026) (Name "Helium") ... )
 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This happened all the time; virtually every function did this conversion process. Before any rows could be selected, the whole table had to be modified, with keys inserted before every value. Not a good idea with large tables!&lt;/p&gt;

&lt;p&gt;So I abandoned the association list approach. You don't really have to use &lt;em&gt;lookup&lt;/em&gt;, because &lt;em&gt;nth&lt;/em&gt; and &lt;em&gt;find&lt;/em&gt; can do a similar job, albeit less easily:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;nth&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;find&lt;/span&gt; &lt;span class="sym"&gt;column-name&lt;/span&gt; &lt;span class="sym"&gt;columns&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;row&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;eval&lt;/span&gt; &lt;span class="sym"&gt;modify-fn&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I had to rewrite many of the functions, too, because now you always have to know the name of the table you're working on (before, you didn't, because the association list's keys were all you needed). It's a little less easy on the eye now, but more powerful. To select rows, you do this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which is slightly different to the old way (there's no &lt;em&gt;where&lt;/em&gt; any more):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="comment"&gt;; old version&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;where&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;5&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&lt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;DiscoveryYear&lt;/span&gt; &lt;span class="integer"&gt;1900&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;em&gt;select-rows&lt;/em&gt; function now also does column filtering and sorting:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;select-rows&lt;/span&gt; 
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;                               &lt;span class="comment"&gt;; table&lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;EarthCrust&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;                      &lt;span class="comment"&gt;; selection criterion&lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="sym"&gt;Symbol&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;                          &lt;span class="comment"&gt;; columns to return&lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Symbol&lt;/span&gt;                                 &lt;span class="comment"&gt;; column to sort by&lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;fn&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;x&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;&gt;&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;x&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;length&lt;/span&gt; &lt;span class="sym"&gt;y&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;   &lt;span class="comment"&gt;; sort function &lt;/span&gt;
  &lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(You can perhaps see why I still think it would be cool to have named parameters...!)&lt;/p&gt;

&lt;p&gt;I've also re-written &lt;em&gt;change-rows&lt;/em&gt; so that it can access all columns when making a change. The idea here is that you can change a column's value using a newLISP expression that can access information from other columns in that row:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;change-rows&lt;/span&gt;          
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;elements&lt;/span&gt;                 &lt;span class="comment"&gt;; table              &lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;ends-with&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;Name&lt;/span&gt; &lt;span class="quoted-string"&gt;"ium"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;  &lt;span class="comment"&gt;; selection criterion        &lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;MP&lt;/span&gt;                       &lt;span class="comment"&gt;; column to modify &lt;/span&gt;
  &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;BP&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;                &lt;span class="comment"&gt;; expression to apply to column        &lt;/span&gt;
&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This changes the Melting Point of every "ium" element to be one less than its Boiling Point. (Yes, I noticed that the 'BP is quoted, when it normally wouldn't be, but it's already been replaced by the actual value when the &lt;em&gt;dec&lt;/em&gt; function is called.)&lt;/p&gt;

&lt;p&gt;So far, it's looking promising, not much slower than a SQLite database, and doing the same job with fewer functions. &lt;/p&gt;

&lt;p&gt;If you'd like to see the second attempt, you can find it &lt;a href="http://unbalanced-parentheses.nfshost.com/downloads/nldb.nl"&gt;here&lt;/a&gt;, and there's a simple database to practice with &lt;a href="http://unbalanced-parentheses.nfshost.com/downloads/elements.nldb"&gt;here&lt;/a&gt;. I'd be very grateful for any improvements you can suggest!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6196867118941439906?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6196867118941439906/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6196867118941439906' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6196867118941439906'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6196867118941439906'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/01/simple-database-second-attempt.html' title='Simple database, second attempt'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6034208478803869319</id><published>2009-01-30T22:47:00.000Z</published><updated>2011-02-07T12:20:40.192Z</updated><title type='text'>newLISP all of a Twitter</title><content type='html'>&lt;p&gt;I don't quite understand &lt;a href="http://twitter.com"&gt;Twitter&lt;/a&gt; but I thought I'd see what all the fuss was about by writing a small plugin for the ParenthesisEngine that picks up newLISP-related twitterings. You can see them in the left margin of the web page.&lt;/p&gt;

&lt;p&gt;The only tricky thing is making sure that the information from twitter isn't updated five times a minute, which wouldn't be very considerate.&lt;/p&gt;

&lt;p&gt;So far, one of the appealing things I've discovered about Twitter is the fact that messages are limited in length to 140 characters or thereabouts. Compulsory brevity has its good points!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6034208478803869319?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6034208478803869319/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6034208478803869319' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6034208478803869319'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6034208478803869319'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/01/newlisp-all-of-twitter.html' title='newLISP all of a Twitter'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7154693415122301666</id><published>2009-01-02T09:41:00.000Z</published><updated>2011-02-07T12:21:07.529Z</updated><title type='text'>Bits and pieces</title><content type='html'>&lt;p&gt;Happy new year!&lt;/p&gt;

&lt;p&gt;It's all been a bit quiet round here recently (I write more code these days), but here are a few miscellaneous bits and pieces of newLISP-related stuff.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.newlisp.org/downloads/newLISP-10.0-Release.html"&gt;newLISP version 10&lt;/a&gt; has been released! It's full of cool things, and it's quicker (slightly) than version 9.4.5. It's not 100% compatible with earlier versions, though, so you'd better read the release notes carefully. The transition to version 10 will take some time: this site is (at the time of writing) running on version 9.3, which is two versions ago now. But any transition takes time. Most of the files in downloads have been updated to version 10.&lt;/p&gt;

&lt;p&gt;I've switched off comments on this blog. It's a pity, because I found my automatic newLISP real-time Bayesian comment-spam eliminator worked really well (about 99.5% effective). But there are still far too many comment spammers looking for somewhere to practise, and I don't want them to use my disk space or CPU cycles when they do.&lt;/p&gt;

&lt;p&gt;Lutz is very kindly &lt;a href="http://www.newlisp.org/index.cgi?Documentation"&gt;hosting&lt;/a&gt; the latest iteration of my &lt;em&gt;Introduction to newLISP&lt;/em&gt; tutorial. Apart from making changes related to newLISP 10, the only major improvement for this version is the addition of coloured code listings, for which I had to write some translation code: one to display in HTML (the dreaded &lt;code&gt;&lt;span&gt;&lt;/code&gt; tag) and one to output text formatted in the TEX-based system I use, called ConTeXt. &lt;/p&gt;

&lt;p&gt;For editing newLISP code, I've found myself back with BBEdit, the classic text editor for Macs. I'm finding the current version, 9.1, to be very adaptable. In the screen shot below, I've just typed the letters "wri". The completion/clippings system in BBEdit is suggesting I use one of six functions, and gives me the syntax for each one. If I select one of these, I'll insert the template at the current insertion point - although that requires me to replace the placeholders. It's very useful for those functions for which I can never remember the order, such as &lt;em&gt;starts-with&lt;/em&gt;). Setting it up requires a 10-line newLISP script - let me know if you'd like a copy.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/bbedit-completion.png" alt="completion in BBEdit using clippings" title="" /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the 2008 newLISP competition didn't really take off. My contribution is on the downloads page - mycroft, a simple newLISP code profiler. The HTML and CSS code took me much longer to figure out than the newLISP code!&lt;/p&gt;

&lt;p&gt;Finally, a mention for a really cool MacOS application called &lt;a href="http://www.taoeffect.com/espionage/"&gt;Espionage&lt;/a&gt;. It's an excellent encryption and security product. I tried it out and the user experience  is first class, and the software does a complicated job in real style. I'm hoping to be able to justify buying it (I don't have many secrets...). Oh, Espionage has no relevance for newLISP that I'm aware of, but I think the author has contributed to the newLISP forums, so he's a pretty talented guy!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7154693415122301666?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7154693415122301666/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7154693415122301666' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7154693415122301666'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7154693415122301666'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2009/01/bits-and-pieces.html' title='Bits and pieces'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6934492490110731607</id><published>2008-12-24T22:57:00.000Z</published><updated>2011-02-07T12:21:12.186Z</updated><title type='text'>Spellbound</title><content type='html'>&lt;p&gt;&lt;b&gt;Update&lt;/b&gt; I think he's updated his table, and unsorted the results. No problem. :) &lt;/p&gt;

&lt;p&gt;I've been enjoying Peter Norvig's article in which he explores ways of writing software to correct typos and spelling mistakes in English text. You can find it &lt;a href="http://norvig.com/spell-correct.html"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;His general idea is to deliberately misspell a wrongly-spelled word in all kinds of simple ways, such as transposing two letters, omitting a letter, including an extra letter, and so on. One of the 'misspellings', if found in a suitable dictionary, is quite likely to be the correct spelling of the original word.&lt;/p&gt;

&lt;p&gt;Although it's not the primary point of his article, I can't help noticing that he's secretly quite proud of the brevity of his Python script ("So here, in 21 lines of Python 2.5 code, is the complete spelling corrector"), and he included at the end the current score table of the number of lines of code required by different languages&lt;/p&gt;

&lt;p&gt;I don't really get this "lines of code" thing. That's partly because I'm more used to talking about brevity in terms of words or characters, rather than lines. A journalist, asked to produce 150 lines of copy, would immediately ask "How many words in a line?'. And in newLISP (and probably many other languages), there's no real restriction on formatting, because white space is provided for the benefit of the human reader or writer rather than for the machine interpreter or compiler. In one sense, all newLISP programs occupy a single line, since a linefeed is just another way for the human writer to format their code and generally has no significance for the interpreter.&lt;/p&gt;

&lt;p&gt;Brevity may well be the soul of wit, but it can also be the first step to unreadable code. A better way to compare the brevity of scripts would be to count how many significant keywords were required, or even how many characters were used, excluding identifiers, which you would obviously want to be expressive and informative rather than cryptic, just for the sake of saving a few letters.&lt;/p&gt;

&lt;p&gt;Just for fun, though, I couldn't help but take up the implicit challenge of writing a newLISP version of Peter Norvig's Python script, to see if I could get it as concise without being unintelligible or unidiomatic. I don't know any Python at all, but I felt that his script was as compressed as he could make it without cheating (nested loops on the same line, for example), so I felt I could adopt a similar approach for newLISP.&lt;/p&gt;

&lt;p&gt;Here's the Python:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import re, collections

def words(text): return re.findall('[a-z]+', text.lower()) 

def train(features):
    model = collections.defaultdict(lambda: 1)
    for f in features:
        model[f] += 1
    return model

NWORDS = train(words(file('big.txt').read()))
alphabet = 'abcdefghijklmnopqrstuvwxyz'
def edits1(word):
    n = len(word)
    return set([word[0:i]+word[i+1:] for i in range(n)] +                     # deletion
               [word[0:i]+word[i+1]+word[i]+word[i+2:] for i in range(n-1)] + # transposition
               [word[0:i]+c+word[i+1:] for i in range(n) for c in alphabet] + # alteration
               [word[0:i]+c+word[i:] for i in range(n+1) for c in alphabet])  # insertion
def known_edits2(word):
    return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in NWORDS)
def known(words): return set(w for w in words if w in NWORDS)
def correct(word):
    candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]
    return max(candidates, key=lambda w: NWORDS[w])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As far as I can understand the original, here's a version in newLISP:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(bayes-train (map lower-case (parse (read-file {big.dms}) {\W} 0)) 'Lexicon)
(define (edits1 word) 
 (let ((l (length word)) (res '()))
  (for (i 0 (- l 1)) (push (replace (nth i (string word)) (string word) "") res -1))
  (for (i 0 (- l 2)) (push (swap i (+ i 1) (string word)) res -1))
  (for (i 0 (- l 1)) (for (c (char "a") (char "z")) 
    (push (replace (nth i (string word)) (string word) (char c)) res -1)
    (push (replace (nth i (string word)) (string word) (string (char c) (nth i (string word)) )) res -1)))
  res))
(define (edits2 word) (unique (flat (map edits1 (edits1 word)))))
(define (known-edits2 word) (filter (fn (w) (Lexicon w)) (edits2 word)))
(define (known words) (filter (fn (w) (Lexicon w)) words))
(define (correct word)
  (let ((candidates (or (known (list word)) (known (edits1 word)) (known (edits2 word)))))
    (first (sort candidates (fn (w1 w2) (&gt; (Lexicon w1) (Lexicon w2)))))))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And to test it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(join (map correct (parse "tihs sentnce is ful of inkorrect spelingz")) " ")

;-&gt; this sentence is full of incorrect spelling
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(By the way, this newLISP code runs on versions 9.4 and higher, but doesn't run on 9.3.) &lt;/p&gt;

&lt;p&gt;If you allow me the single-line function definitions (like his &lt;code&gt;words&lt;/code&gt; function), then I reckon newLISP is about 15 non-blank lines of code. &lt;/p&gt;

&lt;p&gt;In fact, character and word counts are pretty similar. And I think the Python version of &lt;code&gt;edits1&lt;/code&gt; is neater than mine - I'm struggling with my string indexing at the moment. But newLISP has &lt;em&gt;bayes-train&lt;/em&gt; batteries built in, and it doesn't have to import regular expressions or collections. I have no idea whether it runs fast or slow - I don't have the time to optimize size and performance, or to check whether the code is doing what I thought it was.&lt;/p&gt;

&lt;p&gt;As I said, it's just for fun, so don't take it seriously, and I won't try to knock Peter's score off the top on his own web page. But, secretly, just between you and me:&lt;/p&gt;

&lt;p&gt;
&lt;table style="background-color: #dde;"&gt;
&lt;tr&gt;&lt;th&gt;Language&lt;th&gt;Lines of Code&lt;th&gt;Author (and link to implementation)
&lt;tr&gt;&lt;td&gt;newLISP&lt;td&gt;15&lt;td&gt;cormullion
&lt;tr&gt;&lt;td&gt;Awk&lt;td&gt;28&lt;td&gt;&lt;a href="http://feedback.exalead.com/feedbacks/191466-spell-checking"&gt;Gregory Grefenstette&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;C&lt;td&gt;184&lt;td&gt;&lt;a href="http://blog.marcelotoledo.org/2007/08/10/how-to-write-a-spelling-corrector/"&gt;Marcelo Toledo&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;C++&lt;td&gt;98&lt;td&gt;&lt;a href="http://scarvenger.wordpress.com/2007/12/11/how-to-write-a-spelling-corrector/"&gt;Felipe Farinon&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;C#&lt;td&gt;22&lt;td&gt;&lt;a href="http://www.codegrunt.co.uk/?page=cSharp#norvigSpell"&gt;Lorenzo Stoakes&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Clojure&lt;td&gt;18&lt;td&gt;&lt;a href="http://en.wikibooks.org/wiki/Clojure_Programming#Norvig.27s_Spelling_Corrector"&gt;Rich Hickey&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;D&lt;td&gt;23&lt;td&gt;&lt;a href="http://leonardo-m.livejournal.com/59589.html"&gt;Leonardo M&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Erlang&lt;td&gt;87&lt;td&gt;&lt;a href="http://www.pixzone.com/blog/223/spell-corrector-aka-google-suggest-in-erlang-first-part/"&gt;Federico Feroldi&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;F#&lt;td&gt;16&lt;td&gt;&lt;a href="http://www.jelovic.com/weblog/?p=201"&gt;Dejan Jelovic&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;F#&lt;td&gt;34&lt;td&gt;&lt;a href="http://cs.hubfs.net/forums/thread/3085.aspx"&gt;Sebastian G&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Groovy&lt;td&gt;23&lt;td&lt;a href="http://raelcunha.com/spell-correct.php#groovy"&gt;Rael Cunha&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Haskell&lt;td&gt;24&lt;td&gt;&lt;a href="http://pithekos.net/brainwave/"&gt;Grzegorz&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Java&lt;td&gt;36&lt;td&gt;&lt;a href="http://raelcunha.com/spell-correct.php"&gt;Rael Cunha&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Java&lt;td&gt;372&lt;td&gt;&lt;a href="http://developer.gauner.org/jspellcorrect/"&gt;Dominik Schulz&lt;/a&gt;&lt;tr&gt;&lt;td&gt;Perl&lt;td&gt;63&lt;td&gt;&lt;a href="http://www.riffraff.info/2007/5/20/a-spell-corrector-in-perl6-part-3"&gt;riffraff&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;PHP&lt;td&gt;68&lt;td&gt;&lt;a href="http://www.phpclasses.org/browse/package/4859.html"&gt;Felipe Ribeiro&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;PHP&lt;td&gt;103&lt;td&gt;&lt;a href="http://soundofemotion.com/spellcorrect.txt"&gt;Joe Sanders&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Python&lt;td&gt;21&lt;td&gt;Peter Norvig
&lt;tr&gt;&lt;td&gt;Rebol&lt;td&gt;133&lt;td&gt;&lt;a href="http://www.rebol.cz/~cyphre/spell.r"&gt;Cyphre&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Ruby&lt;td&gt;34&lt;td&gt;&lt;a href="http://lojic.com/blog/2008/09/04/how-to-write-a-spelling-corrector-in-ruby/"&gt;Brian Adkins&lt;/a&gt;
&lt;tr&gt;&lt;td&gt;Scheme&lt;td&gt;45&lt;td&gt;&lt;a href="http://practical-scheme.net/wiliki/wiliki.cgi?Gauche%3aSpellingCorrection&amp;l=en"&gt;Shiro&lt;/a&gt; 
&lt;tr&gt;&lt;td&gt;Scheme&lt;td&gt;89&lt;td&gt;&lt;a href="http://scheme.dk/blog/2007/04/writing-spelling-corrector-in-plt.html"&gt;Jens Axel&lt;/a&gt;
&lt;/table&gt;
&lt;/p&gt;

&lt;p&gt;:-)&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6934492490110731607?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6934492490110731607/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6934492490110731607' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6934492490110731607'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6934492490110731607'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/12/spellbound.html' title='Spellbound'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5509234877317243086</id><published>2008-10-25T10:42:00.000+01:00</published><updated>2011-02-07T12:21:28.418Z</updated><title type='text'>Project Nestor: part 1</title><content type='html'>&lt;p&gt;Most of my newLISP projects are unfinished and nameless, but my current project, still an early prototype, and still far from being finished, has already managed to acquire a name. It's called Nestor. I needed to call it something, and since it deals with nested lists quite a lot, this was the first name that sprang to mind.&lt;/p&gt;

&lt;p&gt;For those of you with a classical education, and/or fans of the film &lt;em&gt;Troy&lt;/em&gt;, &lt;a href="http://en.wikipedia.org/wiki/Nestor_(mythology)"&gt;Nestor&lt;/a&gt; was the old soldier in Homer's Iliad who advised King Agamemnon of Mycenae. To be honest, though, I was thinking more of Captain Haddock's butler, Nestor, who runs Marlinspike Hall. After some desperate reverse acronymization, I managed to make NESTOR stand for "newLISP Source Translator Or Reducer.&lt;/p&gt;

&lt;p&gt;The general aim is to convert newLISP source code into s-expressions, to extend the classic Lisp principle of allowing a rich interplay between code and data. Although newLISP has functions for converting text into code (&lt;em&gt;read-expr&lt;/em&gt;), and for evaluating code passed as text data (&lt;em&gt;eval&lt;/em&gt; and &lt;em&gt;eval-string&lt;/em&gt;), information is lost in the process. And I'm hoping to preserve the two aspects of source code that are usually discarded: the &lt;em&gt;comments&lt;/em&gt; and the &lt;em&gt;white space&lt;/em&gt; (ie the formatting). Although these are solely for the benefit of the human reader and writer of code, I think they're an important part of the process, and should be treated as such rather than always discarded.&lt;/p&gt;

&lt;p&gt;My first attempt at a &lt;em&gt;recursive descent parser for an LL(0) grammar&lt;/em&gt; worked OK. In plain English, that simply meant that the &lt;em&gt;read&lt;/em&gt; function scanned the source input from left to right, and called itself recursively to look for the next token. On large source files, though (over 3000 lines), it did require a stack larger than the newLISP default of 2800 elements (so it would have to be run with a larger stack setting), and it wasn't very quick either. I found it hard to control the nesting of the output as the various lists within lists were encountered in the source. And recursion, elegant though it undoubtedly is, has always made me scratch my head a little.&lt;/p&gt;

&lt;p&gt;For the next version I went for a more iterative approach, and used a plain list as a stack, controlling the nesting by maintaining a stack pointer in a list:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Stack:Stack&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;   
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;stack&lt;/span&gt; &lt;span class="sym"&gt;sp&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;sp&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;sp&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Stack:inc-nest&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="integer"&gt;0&lt;/span&gt; &lt;span class="sym"&gt;sp&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;push&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;stack&lt;/span&gt; &lt;span class="sym"&gt;sp&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;inc&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;nest-level&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;

&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Stack:dec-nest&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;pop&lt;/span&gt; &lt;span class="sym"&gt;sp&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;setf&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;sp&lt;/span&gt; &lt;span class="integer"&gt;-1&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;+&lt;/span&gt; &lt;span class="integer"&gt;1&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;sp&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dec&lt;/span&gt; &lt;span class="quote"&gt;'&lt;/span&gt;&lt;span class="sym"&gt;nest-level&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the Stack default function, which is used to add items to the stack, &lt;em&gt;push&lt;/em&gt; is putting an item on the stack at the location determined by the stack pointer &lt;code&gt;sp&lt;/code&gt;, which is just a list of index numbers. The nesting can then be easily controlled by adding or removing indices to &lt;code&gt;sp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main scanner works its way through the source, and calls the relevant function. For example, an opening parenthesis is easy:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;open-paren-token&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Stack:inc-nest&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;Stack&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;list&lt;/span&gt; &lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;but some of the others are more complicated, such as the number scanner, which is recursive:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;read-number-scanner&lt;/span&gt; &lt;span class="sym"&gt;list-so-far&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;let&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;next-char&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;peek-char&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="comment"&gt;;; if next-char is a digit then call self recursively&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;and&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;char-numeric?&lt;/span&gt; &lt;span class="sym"&gt;next-char&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;next-char&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
          &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;read-number-scanner&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;cons&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;get-next-char&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="sym"&gt;list-so-far&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
          &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;reverse&lt;/span&gt; &lt;span class="sym"&gt;list-so-far&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The top-level function, &lt;em&gt;Nestor:read&lt;/em&gt;, converts the following newLISP source:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;; get stdin POST method parameters if present
;
(set 'inline (read-line))
(if inline 
  (set 'params (get-vars inline)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;to this list:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"comment"&lt;/span&gt; &lt;span class="quoted-string"&gt;"; get stdin POST method parameters if present"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"comment"&lt;/span&gt; &lt;span class="quoted-string"&gt;";"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"set"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IA=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"quote"&lt;/span&gt; &lt;span class="quoted-string"&gt;"'"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"inline"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IA=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"read-line"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"Cg=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"if"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IA=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"inline"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IAoJ"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"set"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IA=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"quote"&lt;/span&gt; &lt;span class="quoted-string"&gt;"'"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"params"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IA=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;"("&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"get-vars"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt; &lt;span class="quoted-string"&gt;"IA=="&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt; &lt;span class="quoted-string"&gt;"inline"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
    &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt; &lt;span class="quoted-string"&gt;")"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's wordy, isn't it?! Perhaps I'll switch to abbreviations or code numbers one day, but at the moment I find the longer names really useful. And the format isn't going to be used for permanent data storage, just for temporary processing. The whitespace is stored in base64 encoding - again, this may not be the best choice, but for now it works OK.&lt;/p&gt;

&lt;p&gt;Now that I've got this newLISP expression, or &lt;em&gt;nl-expression&lt;/em&gt;, perhaps the most obvious and important function is the logical inverse of &lt;em&gt;Nestor:read&lt;/em&gt;, &lt;em&gt;Nestor:nlx-to-text&lt;/em&gt;, which converts the nl-expression back to plain source form. Fortunately it's easy, and a natural application for a recursive function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;define&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;nlx-to-text&lt;/span&gt; &lt;span class="sym"&gt;nlxp&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dolist&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;i&lt;/span&gt; &lt;span class="sym"&gt;nlxp&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
  &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;if&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;atom?&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
     &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;begin&lt;/span&gt;
       &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;cond&lt;/span&gt; 
         &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"symbol"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
              &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;write-buffer&lt;/span&gt; &lt;span class="sym"&gt;buff&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
         &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"open-paren"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
              &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;write-buffer&lt;/span&gt; &lt;span class="sym"&gt;buff&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt;  &lt;span class="braced-string"&gt;{(}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
         &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"close-paren"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
              &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;write-buffer&lt;/span&gt; &lt;span class="sym"&gt;buff&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt;  &lt;span class="braced-string"&gt;{)}&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
         &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;first&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; &lt;span class="quoted-string"&gt;"whitespace"&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
              &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;dostring&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;s&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;base64-dec&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;last&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt; 
                &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;write-buffer&lt;/span&gt; &lt;span class="sym"&gt;buff&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;string&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;char&lt;/span&gt; &lt;span class="sym"&gt;s&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
      &lt;span class="comment"&gt;; and so on with other element types&lt;/span&gt;
      &lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;
     &lt;span class="comment"&gt;; not an atom, recurse&lt;/span&gt;
     &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;nlx-to-text&lt;/span&gt; &lt;span class="sym"&gt;i&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When this completes, the buffer &lt;code&gt;buff&lt;/code&gt; should contain the de-nested source code.&lt;/p&gt;

&lt;p&gt;When I tested this approach against the official newLISP installation, I was pleasantly surprised that all the  modules converted to and from nl-expressions without error. In other words, given some newLISP in &lt;em&gt;source-text&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="built-in"&gt;=&lt;/span&gt; &lt;span class="sym"&gt;source-text&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Nestor:nlx-to-text&lt;/span&gt; &lt;span class="open-paren"&gt;(&lt;/span&gt;&lt;span class="sym"&gt;Nestor:read&lt;/span&gt; &lt;span class="sym"&gt;source-text&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;span class="close-paren"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;usually returns true.&lt;/p&gt;

&lt;p&gt;However, there are problems. For one thing, I'm embarassed that the presence of one or more Unicode characters will kill the translator stone dead. I've no idea what the cause is yet, but I hope it's fixable. &lt;/p&gt;

&lt;p&gt;There are other things on my to-do list that will be done soon: nested braces in strings, for example. But there are some aspects of the language that I probably won't be able to handle at all. The newLISP interpreter is a strange and powerful beast, and my own scripting is never going to be able to match it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;1.2.3e4e5                          ; three tokens: 1.2 .3e4 e5 ?
(println"hi there")                ; no space?
string}                            ; weird symbol or string typo?
(set '[ you're (not) joking ] 0)   ; wacky symbol names in brackets?
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That first one was aptly described as a &lt;a href="http://slobin.pp.ru/vim/syntax/newlisp.vim"&gt;monster&lt;/a&gt;! Does anyone use these newLISP constructions in their code?&lt;/p&gt;

&lt;p&gt;The first application for Nestor is to re-introduce the syntax colouring of newLISP source code that appeared about a year ago on this site and disappeared during the revamp in March this year. If you're seeing newLISP code in colour then it's working. More on that another day.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5509234877317243086?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5509234877317243086/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5509234877317243086' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5509234877317243086'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5509234877317243086'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/10/project-nestor-part-1.html' title='Project Nestor: part 1'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7755389843168237099</id><published>2008-10-06T00:02:00.000+01:00</published><updated>2011-02-07T12:21:35.946Z</updated><title type='text'>Going API</title><content type='html'>&lt;p&gt;I've been playing with the &lt;a href="http://code.google.com/apis/chart/"&gt;Google chart API&lt;/a&gt;, and it's a great way of inserting charts into your web pages. Here, for example, is a pie chart showing the relative sizes of the different directories in this web site:&lt;/p&gt;
&lt;p&gt;--- (Route.Blog:pie-chart  "600x200" "t:10.79,1.07,42.80,2.15,19.42,3.59,18.70,1.43" "p3" "views|resources|jack-the-glypher|includes|images|dragonfly-framework|downloads|databases") ---&lt;/p&gt;
&lt;p&gt;This chart is generated by Google automatically whenever the page is loaded. Originally it was a live chart, produced by examining the current file system every time the page was loaded, but this made the page take too long to generate... :)&lt;/p&gt;
&lt;p&gt;Charts can be generated using this newLISP function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(define (pie-chart 
  (chs "400x200") 
  (chd "t:60,40") 
  (cht "p3") 
  (chl "Hello|World") 
  (chalt "Sample Google Chart"))
  (format 
    {&lt;img src="http://chart.apis.google.com/chart?chs=%s&amp;chd=%s&amp;cht=%s&amp;chl=%s" alt="%s" &gt;}
    chs chd cht chl chalt))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which is called like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- --(Route.Blog:pie-chart  "600x200" "t:10.79,1.07,42.80,2.15,19.42,3.59,18.70,1.43" "p3" "views|resources|jack-the-glypher|includes|images|dragonfly-framework|downloads|databases") - --
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;although there shouldn't be spaces between the three "-" signs (see &lt;a href="http://unbalanced-parentheses.nfshost.com/lispinwebpages"&gt;here&lt;/a&gt; for details).&lt;/p&gt;
&lt;p&gt;This following little bit of code is useful for converting numbers into percentages, because the pie chart expects percentage values by default:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(define (make-percentages)
  (let ((total 0))
     (set 'total (apply add (args)))
     (map (fn (f) (mul 100 (div f total))) (args))))
     
(make-percentages 300 800)
;-&gt;(27.27272727 72.72727273)
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7755389843168237099?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7755389843168237099/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7755389843168237099' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7755389843168237099'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7755389843168237099'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/10/going-api.html' title='Going API'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-2875002784317862570</id><published>2008-08-20T18:41:00.000+01:00</published><updated>2011-02-07T12:21:38.452Z</updated><title type='text'>Reasons to learn newLISP</title><content type='html'>&lt;p&gt;It's that time of the year when many people face new academic challenges, so now's a great time to learn newLISP! Here are some - frankly quite biased - suggestions as to why newLISP is a good choice for a first - or second or third - programming language. By the way, most of these suggestions are true of most programming languages. &lt;/p&gt;

&lt;h2&gt;1 It's easy&lt;/h2&gt;

&lt;p&gt;newLISP is easy to download, install, and use. For most operating systems you'll find an installer and/or prebuilt versions. You won't have to build, compile, or make the software, and running it is as easy as typing its name.&lt;/p&gt;

&lt;p&gt;It's easy to program in, too. The carefully-designed syntax aims for readability and write-ability, without introducing too many special forms and conventions. Obscure function names have been replaced by more English-like ones, and the temptation to make use of every punctuation mark known to humankind has been resisted, for the most part. Although sadly I don't think we'll ever be able to say goodbye regular expressions. And there are some great introductory tutorials too. (I wrote one of them, so I'm biased!)&lt;/p&gt;

&lt;p&gt;'easy' isn't always the main reason for doing anything. Sometimes we do things not because they're easy, but because they are hard. Sometimes we have to use things that aren't easy to use to solve hard problems. But easy can be good when you're learning something new. You probably didn't learn to drive a car in a Ferrari...&lt;/p&gt;

&lt;h2&gt;2 It's small&lt;/h2&gt;

&lt;p&gt;It would be hard to find a memory card on sale today with a capacity too small to hold newLISP - if you can find a 512KB memory card you'll have plenty of room to spare. &lt;/p&gt;

&lt;p&gt;More seriously: it's a compact language that tries to provide everything you need and dispenses with much that you don't. With about 350 functions, it's a reasonably complete basic programmer's toolkit without the complexity and advanced features of the larger languages. You can worry about making it bigger - or moving to a bigger language - later, when (or if) you outgrow it.&lt;/p&gt;

&lt;p&gt;Yes, it hasn't got sub-atomic reference de-clustering, quasi-optimal cyclical redundancy, or meta-stable bytecode traversal, to name just a few of the language's most obvious omissions, but there's plenty of useful tools to get you started. (I made those up, by the way, but that doesn't mean that they don't actually exist...)&lt;/p&gt;

&lt;h2&gt;3 It's fast&lt;/h2&gt;

&lt;p&gt;newLISP is quick enough for most purposes, so you shouldn't have too many problems with speed. For extra performance you can learn how to link to compiled C libraries, or write assembly code (two things which I've managed to avoid so far). The 50,000 word introduction I mentioned above is translated in about 0.4 seconds, which is too quick for me - better to take 10 seconds so that I can drink some coffee...&lt;/p&gt;

&lt;h2&gt;4 It's versatile&lt;/h2&gt;

&lt;p&gt;Use newLISP to explore programming concepts, crunch numbers, write internet applications, analyze text documents, post rubbish to blogs, or compose music. You can learn many different styles of programming in newLISP: casual scripting (my favourite!), imperative programming, functional programming, object-oriented programming, and even a form of macro programming - you'll be working with newLISP for a long time before you've exhausted newLISP's potential and are ready to move on to bigger and more complex languages (if you want to). Instead of just following what other people tell you is the 'right' way, why not try the different styles and make your own mind up?&lt;/p&gt;

&lt;p&gt;You don't have to be a mathematician or computer scientist to use newLISP: musician, writer, artist, sysadmin, architect, web developer, surveyor, mathematician, tinker, tailor, soldier, sailor - if your work involves the computer, it could benefit if that computer works harder for you.&lt;/p&gt;

&lt;p&gt;This is the sort of newLISP I use all the time:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set 'dir (real-path))
(dolist (i (directory dir {^[^.]}))
 (set 'item (string dir "/" i))
 (set 'mod-date (date (file-info item 6) 0 "%Y%m%d-%H%M%S"))
 (rename-file item (string dir "/" mod-date i)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which simply renames files based on their modification dates. But others are into newLISP like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; (let ((g (lambda (h) 
          (expand (lambda (n) 
            (let ((f (lambda (f n) 
                    (if (&lt; n 2) 1 (* n (f (- n 1))))))) 
            (f (h h) n))) 'h))))
       ((g g) 10)) 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which is something to do with the &lt;a href="http://www.newlisp.org/index.cgi?Y_Function"&gt;Y function&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;5 It's different&lt;/h2&gt;

&lt;p&gt;newLISP is certainly not your Dad's Lisp, and there's plenty of tutting in some of the online communities who get easily worked up by the different. But perhaps you're ready to find out for yourself what you think is good or bad, and perhaps you like to avoid doing the obvious and predictable. Or at least prove to your own satisfaction that your Dad was right all along.&lt;/p&gt;

&lt;h2&gt;6 It's friendly&lt;/h2&gt;

&lt;p&gt;On the newLISP forum, genuine questions usually receive genuine answers. You probably won't encounter the uncompromising attitudes that newcomers to other languages &lt;a href="http://social-problems-of-lisp.blogspot.com/"&gt;occasionally encounter&lt;/a&gt;. You might even get help from Lutz, the language's author.&lt;/p&gt;

&lt;h2&gt;7 It's not finished&lt;/h2&gt;

&lt;p&gt;newLISP is still developing. There's room for contributions from you: plenty of investigations and discoveries still to be made, plenty of code and applications still to be written, and many subjects for writing and blogging about. If you want to take an active part in its development, there are plenty of oportunities waiting for you. You might even get your own function included in the next release (but it will have to be a &lt;em&gt;really&lt;/em&gt; good idea!). It won't be named after you, though.&lt;/p&gt;

&lt;h2&gt;8 It's fun&lt;/h2&gt;

&lt;p&gt;Is programming fun? It's not always fun; sometimes the code doesn't do what you thought you told it to do. But newLISP has got to be fun, because the slogan says it is!&lt;/p&gt;

&lt;h2&gt;9 It's free&lt;/h2&gt;

&lt;p&gt;Of course, sometimes the best things in life are free!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-2875002784317862570?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/2875002784317862570/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=2875002784317862570' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2875002784317862570'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2875002784317862570'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/08/reasons-to-learn-newlisp.html' title='Reasons to learn newLISP'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-8225177178899391561</id><published>2008-08-02T11:04:00.000+01:00</published><updated>2011-02-07T12:21:42.925Z</updated><title type='text'>Two (or more) challenges for the weekend</title><content type='html'>&lt;p&gt;What's the point of the following line at the start of this newLISP blog script?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(if (unless nil nil true) (exit))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;See if you can get your comment through the impassable barrier that is the newLISP Bayesian Comment Spam Killer below! (No, it won't kill Bayesian comemnts - although it might - but it tries to kill spam comments using Bayesian analysis.)&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-8225177178899391561?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/8225177178899391561/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=8225177178899391561' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8225177178899391561'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8225177178899391561'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/08/two-or-more-challenges-for-weekend.html' title='Two (or more) challenges for the weekend'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-1799850192048746167</id><published>2008-07-31T20:36:00.000+01:00</published><updated>2011-02-07T12:21:48.354Z</updated><title type='text'>Looking for things</title><content type='html'>&lt;p&gt;When writing newLISP, it's often useful to find any existing code that uses a particular function or symbol name. I'm a fan of both the Unix &lt;em&gt;find&lt;/em&gt; command and the Mac's built-in search tool, Spotlight. The former is all-powerful, but it can be a little tricky to remember all those options. The latter is particularly good for finding text in documents - not just text files - since many developers provide a Spotlight system plug-in for searching their own document types. &lt;/p&gt;

&lt;p&gt;Naturally, though, I also use a small newLISP utility alongside these two: its purpose is to look only in places where there are newLISP-related files. I reworked this recently based on some suggestions in the newLISP forum. Here's the code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/usr/bin/env newlisp
(context 'Look4)

(constant 'extensions '("lsp" "txt" "html")) 

(set 'places 
  (list (string (env "HOME") "/projects/lisp")
        (string (env "HOME") "/projects/webapps")
        (string (env "HOME") "/projects/guiapps")
        (string (env "HOME") "/projects/tech-writing")
        "/usr/share/newlisp"))

(define (string-ends-with L str) 
  (exists (fn (x) (ends-with str x)) L))

(define (look-in-file pn)
 (let (contents "" res '())
  (when (string-ends-with extensions pn)
     (set 'file (open pn "r"))
     (while (search file (string "(.*?" Term ".{10,20})") true 1)
        (push $1 res -1))
     (close file)
     (if res (list pn res)))))

(define (Look4:Look4 dir)
   (dolist (nde (directory dir "^[A-z]"))
     (set 'item (append dir "/" nde))
     (if (directory? item)
       (Look4 item)
       (when (set 'r (look-in-file item Term)) (inc 'counter) (push r results)))))

(when (&gt;= (length (main-args)) 2) 
  (set 'Term (main-args 2) 'counter 0)
  (map Look4 places)
  (when results
    (sort results)
    (dolist (r results)
        (println "\n" (r 0) "\n\t\t\t")
        (dolist (e (r 1)) (println "\t" e)))
        (println "Found " counter " occurrences of \"" Term "\"")))

(exit)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can see that I've hardwired the names of the directories to search in. That's not very flexible, but it may suit your working style.&lt;/p&gt;

&lt;p&gt;On the command line, you run the script and supply a string:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ look4 unless
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and you'll see the results in a couple of seconds:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
/Users/me/projects/lisp/tokenizer.lsp

    (unless txt (exit))

/usr/share/newlisp/guiserver.lsp

;; a Piano instrument unless the function 'gs:mi

/usr/share/newlisp/modules/smtp.lsp

      (unless (and (empty?  user-

/usr/share/newlisp/util/nanorc

color blue "([[:space:]()]|^)(trim|true|true\?|unicode|unify|unique|unless|unpack|until|upper-
...

Found 201 occurrences of "unless"
$ 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Since the search is a regular expression one, it &lt;em&gt;might&lt;/em&gt; be possible to supply a regex-friendly string, if you escape all the regex characters, but I find that I rarely use regular expressions in this type of search. I'm more likely to be asking "Didn't I once write a &lt;em&gt;binary&lt;/em&gt; function?".&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-1799850192048746167?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/1799850192048746167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=1799850192048746167' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1799850192048746167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1799850192048746167'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/07/looking-for-things.html' title='Looking for things'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-3598679401310498336</id><published>2008-07-25T23:43:00.000+01:00</published><updated>2011-02-07T12:21:51.236Z</updated><title type='text'>Character reference</title><content type='html'>&lt;p&gt;I was looking through an old (1990!) book on Unicode the other day. I've always been intrigued by the amazing diversity of letter forms that we've created over the last few thousand years. Here are just some of the many wonderful and peculiar characters you'll find tucked away in the Unicode glyph banks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;۞&lt;/li&gt;
&lt;li&gt;ⱁ&lt;/li&gt;
&lt;li&gt;ᚅ&lt;/li&gt;
&lt;li&gt;Ꮈ&lt;/li&gt;
&lt;li&gt;Ϣ&lt;/li&gt;
&lt;li&gt;܍&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also find the I Ching, Braille, alchemy, an alphabet funded by George Bernard Shaw, neo-pagan tree language, astrology, dentists, talking leaves, and much more besides.&lt;/p&gt;

&lt;p&gt;Most of the technical aspects of Unicode escape me (supplementary planes, normalization, high surrogates, collation?) but it's useful to know the basics of using Unicode in newLISP, particularly now that it's &lt;a href="http://googleblog.blogspot.com/2008/05/moving-to-unicode-51.html"&gt;the most popular encoding used on the internet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;newLISP is UTF-8 friendly by default on MacOS X, and UTF-8 versions are available for other platforms too (although I'm not sure whether the default versions are UTF-8). UTF-8 is a variable-length character encoding, which allows characters to use 1, 2, 3 or 4 bytes depending on their Unicode value.&lt;/p&gt;

&lt;p&gt;One essential newLISP function for exploring the Unicode character set is &lt;em&gt;char&lt;/em&gt;. This takes either a number or a character, and returns the matching character or number:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(char 63498)
""

(char "")
63498
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Unicode characters are usually described using hexadecimal, so it's useful to know how to translate between hex and decimal. To convert a decimal integer to a hex string, use &lt;em&gt;format&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(format "%llx" 63498)
"f80a"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To convert a hex string to a decimal integer, pass a hexadecimal string starting with "0x" to &lt;em&gt;int&lt;/em&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(int (string "0x" "f80a"))
63498
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When you're writing text, it would be good if you could easily insert these characters as you type. There are useful system tools for doing this (on MacOS X, there's the Character Palette), but for fun I've added the following two functions to the Markdown converter that I use to process my writing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (hex-str-to-unicode-char strng)
   (char (int (string "0x" (1 strng)) 0 16)))

(define (ustring s)
  (replace "U[0-9a-f]{4,}" s (hex-str-to-unicode-char $0) 1))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So now I can type "U" followed by 4 hexadecimal characters, and the appropriate Unicode character is inserted automatically: "U f80a" is converted to "". (I had to insert a space after the U to prevent translation.)&lt;/p&gt;

&lt;p&gt;You can happily use Unicode characters anywhere in newLISP code, if your text editor or console is up to the job. And if &lt;em&gt;ustring&lt;/em&gt; is available, you can generate them easily too:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(constant (sym (ustring "U 2660")) 4  ; spades
       (sym (ustring "U 2661"))      3  ; hearts
       (sym (ustring "U 2662"))      2  ; diamonds
       (sym (ustring "U 2663"))      1  ; clubs
     )

(symbols)

(! != $ $0 $1 $10 $11 $12 $13 $14 $15 $2 $3 $4 $5 $6 $7 $8 $9 $HOME $args $idx $main-args ...  zero? | ~ ♠ ♡ ♢ ♣)

(println "(&gt; ♢ ♣)? " (&gt; ♢ ♣))
(&gt; ♢ ♣)? true

(println "(&gt; ♡ ♠)? " (&gt; ♡ ♠))
(&gt; ♡ ♠)? nil
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using descriptive Unicode characters for your symbol names could introduce a whole new level of readability to your code!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(constant (global '☼)  MAIN)
(context '☺)

(define (☻ ✄ ☁ ⍾)
   (print ✄ ☁ ⍾))

(define (‽) 
   (println {‽}))

(context ☼)
(set '℥ "what "  'ᴥ "the " 'ᴒ "dickens")
(☺:☻ ℥ ᴥ ᴒ)
(☺:‽)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Appropriately enough, that last function call returns "‽", which is the much-needed &lt;a href="http://en.wikipedia.org/wiki/Interrobang"&gt;interrobang&lt;/a&gt; character.&lt;/p&gt;

&lt;p&gt;The problem now is to remember all those four digit hexadecimal numbers that identify the Unicode characters. I whipped up a quick Unicode browser in newLISP:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/unicode-app.png" alt="a Unicode browser" title="" /&gt;&lt;/p&gt;

&lt;p&gt;This just shows a page of Unicode characters at a time, and lets you move up and down through the 'pages'. It has some problems when the character code exceeds FFFF - I don't know why‽&lt;/p&gt;

&lt;p&gt;This post should display correctly on most modern browsers. If you see lots of boxes rather than characters, then you are using a browser or system that doesn't handle Unicode well. This applies to  the iPhone and iPod Touch as well: it appears that Mobile Safari doesn't like Unicode as much as its desktop version. Apple - improve Unicode support please!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3598679401310498336?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3598679401310498336/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3598679401310498336' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3598679401310498336'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3598679401310498336'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/07/character-reference.html' title='Character reference'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-2603420263229048941</id><published>2008-07-13T20:31:00.000+01:00</published><updated>2011-02-07T12:21:55.753Z</updated><title type='text'>googled again</title><content type='html'>&lt;p&gt;In the top right corner of this page you'll see a search box. This is a custom Google search engine, and it's currently set to search three newLISP-related sites using Google technology:  &lt;a href="unbalanced-parentheses.nfshost.com"&gt;this one&lt;/a&gt;, the main newLISP site at &lt;a href="http://newlisp.org"&gt;newlisp.org&lt;/a&gt;, and the &lt;a href="http://newlisp-on-noodles.org/wiki/index.php/NewLISP_on_Noodles"&gt;newLISP on Noodles wiki&lt;/a&gt;. I didn't want to add anyone else's site without their permission or knowledge, but it's easy to add extra sites, so just let me know if you'd like yours added.&lt;/p&gt;

&lt;p&gt;So far I've found it fairly useful, and it's meant that I haven't had to write my own blog search tool yet. However, it's been doing odd things with the HTML display of the page, generating thousands of Javascript errors and doing weird stuff like (apparently) loading the page more than once. Also, it fails to find things that I think it should, so I'm not convinced it's as good as Google engineers apparently think it is.&lt;/p&gt;

&lt;p&gt;If anyone is at all knowledgeable about what the custom Google search engine is doing with its HTML, please help! The Google engineers can't be bothered to help anyone use their work &lt;a href="http://groups.google.com/group/google-custom-search-creating-and-editing/topics"&gt;it seems&lt;/a&gt;, so I'm on my own. Perhaps I'll have to write my own search engine after all...!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-2603420263229048941?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/2603420263229048941/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=2603420263229048941' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2603420263229048941'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2603420263229048941'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/07/googled-again.html' title='googled again'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7659363338524340876</id><published>2008-06-27T18:33:00.003+01:00</published><updated>2011-02-07T12:22:10.782Z</updated><title type='text'>Simple help for newLISP</title><content type='html'>&lt;p&gt;An interesting &lt;a href="http://www.alh.net/newlisp/phpbb/viewtopic.php?p=12664"&gt;discussion&lt;/a&gt; about an online help facility for newLISP threw up lots of interesting ideas and useful suggestions. One advantage to not having certain facilities built in is that it gives you scope for making them yourself. This version I threw together is simple enough:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (help func-name)
   (if (find func-name "|+*-") (push "\\" func-name))
   (set 'html-text (join (find-all (format {&lt;b&gt;(syntax: \(%s.*?)&lt;/b&gt;} func-name) 
      (read-file "/usr/share/doc/newlisp/newlisp_manual.html")) "\n"))
  (println (replace "&lt;.*?&gt;" html-text "" 0))
  (silent))

&gt; (help "date")
syntax: (date)
syntax: (date int-secs [int-offset])
syntax: (date int-secs int-offset str-format)
syntax: (date-value int-year int-month int-day [int-hour int-min int-sec])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first line of the function was a temporary fix for a puzzling bug, and it also offered a nice surprise. The bug was that it was impossible to get help on functions with names like &lt;em&gt;|&lt;/em&gt; or &lt;em&gt;+&lt;/em&gt; without listing way too much. I eventually realized (duh) that some newLISP symbols were also significant in regular expressions and needed to be escaped. Because no symbols use the period (.), I hadn't bothered to escape it. But it then becomes a useful option for searching for functions:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&gt; (help "fi.")
syntax: (file-info str_name [int-index])
syntax: (file? str-name)
syntax: (filter exp-predicate exp-list)
syntax: (find exp-key list [func-compare | int-option])
syntax: (find str-key str-data [int-option])
syntax: (find-all str-pattern str-text [expr [int-option]])
syntax: (find-all list-pattern list-lists [expr])
syntax: (find-all expr-key list expr func-compare)
syntax: (first list)
syntax: (first array)
syntax: (first str)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The only thing left to do now is to work out how to avoid using &lt;em&gt;silent&lt;/em&gt;. Although I want to suppress the value returned by &lt;em&gt;println&lt;/em&gt;, I don't want the prompt to disappear as well. Unfortunately, &lt;em&gt;silent&lt;/em&gt; does both. I bet there's a way, though!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7659363338524340876?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7659363338524340876/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7659363338524340876' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7659363338524340876'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7659363338524340876'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/simple-help-for-newlisp.html' title='Simple help for newLISP'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7020737977307730652</id><published>2008-06-27T18:33:00.002+01:00</published><updated>2011-02-07T12:22:08.549Z</updated><title type='text'>Smashing the Atom</title><content type='html'>&lt;p&gt;Despite my attempts to get the Atom news feed working for this site, it still doesn't work. I'm not clear what the problem is - something to do with the way the page is served, rather than the syntax or the content. I've been hunting on the web for information about whether to serve pages with "Content-Type: application/atom+xml" headers or not, followed by carriage returns or not, without a lot of success.&lt;/p&gt;

&lt;p&gt;I'll have a go at fixing it in the next week.&lt;/p&gt;

&lt;p&gt;I was quite pleased that the code to produce the Atom document was only about 45 lines, though. &lt;/p&gt;

&lt;h3&gt;Edit&lt;/h3&gt;

&lt;p&gt;I discovered how to make it work: I used a content type of text/xml:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set '*content-type* "text/xml\n\n")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and claimed that I was generating it using a file called 'atom.cgi', although I'm not.&lt;/p&gt;

&lt;p&gt;So now I can put this on my page, apparently:&lt;/p&gt;&lt;p&gt;&lt;a href="http://feedvalidator.org/check.cgi?url=http%3A//unbalanced-parentheses.nfshost.com/atom.cgi"&gt;&lt;img src="/images/valid-atom.png" alt="[Valid Atom 1.0]" title="Validate my Atom 1.0 feed" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7020737977307730652?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7020737977307730652/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7020737977307730652' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7020737977307730652'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7020737977307730652'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/smashing-atom.html' title='Smashing the Atom'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5190076856605055588</id><published>2008-06-27T18:33:00.001+01:00</published><updated>2011-02-07T12:22:06.482Z</updated><title type='text'>Two out of three - TicTacToe</title><content type='html'>&lt;p&gt;After a mention of TicTacToe in the newLISP forums, I thought I'd take a look at writing a version of what we used to call Noughts and Crosses for myself. Peter posted a &lt;a href="http://www.turtle.dds.nl/newlisp/tictactoe.lsp"&gt;link&lt;/a&gt; to his excellent version, which plays a perfect game from the command line. So, to be different, I decided to start from the other end and write a GUI version using newLISP-GS.&lt;/p&gt;

&lt;p&gt;There are basically three tasks to do: do the basic game mechanics, implement a user interface, and add a dash or two of intelligent strategy.&lt;/p&gt;

&lt;p&gt;The game mechanics are simple enough. You need a board:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set '*grid* (dup nil 9))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and a list of winning positions:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set '*winning-positions* 
   '((0 1 2) (3 4 5) (6 7 8) (0 3 6) (1 4 7) (2 5 8) (0 4 8) (2 4 6)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each move places a 'X or 'O symbol in the grid, using &lt;em&gt;set-nth&lt;/em&gt; or &lt;em&gt;nth-set&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set-nth (*grid* a-move) 'X)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There's also a function to find the moves still available:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (available-moves)
  (index nil? *grid*))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and a function to check whether a proposed move is valid:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (check-move move)
  (and (&lt;= 0 move 8) (find move (available-moves))))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here's a function to see if a player has won:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (won? player)
   (letn ((player-squares (index (fn (x) (= x player)) *grid*))
          (wins-for-player 
             (map (fn (win) (= win (intersect win player-squares))) *winning-positions*)))
      (if (exists true? wins-for-player)
          (set '*winner* (list player (find true wins-for-player))))))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This uses the &lt;em&gt;intersect&lt;/em&gt; function, which I haven't used much before. The idea is that the set of grid squares occupied by a player might equal or contain the set of numbers which constitute a winning position:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set 'win '(2 5 8))
(intersect '(2 5 8) '(2 3 5 7 8))
;-&gt; (2 5 8)
(= win (intersect win '(2 3 5 7 8)))
;-&gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I wonder if there's a simpler way to see whether '(2 3 5 7 8) contains '(2 5 8)?&lt;/p&gt;

&lt;p&gt;Add a &lt;em&gt;game-over?&lt;/em&gt; function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (game-over?)
  (or (won? 'X) (won? 'O) (empty? (available-moves))))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and the mechanics are mostly done.&lt;/p&gt;

&lt;p&gt;I found the user interface task a bit harder. The problem is that you have to use an event-driven model, but the only event that's going to happen is the user clicking a square to place a 'X. So inside the main loop:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(do-until (game-over?) (gs:check-event 1000000))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;everything has to be driven by mouse clicks. I ended up with something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (do-human-move move)
  (and (not (game-over?) (= *turn* "human"))
       (check-move move)
       (set-nth (*grid* move) 'X)
       (display)
       (set '*turn* "computer")
       (do-computer-move)
       (display)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which kind of works - and you can see now why I used &lt;em&gt;set-nth&lt;/em&gt;. You might think that &lt;em&gt;nth-set&lt;/em&gt; would do the job perfectly, and it does, but it returns the value of the replaced element, which, being nil, finished the &lt;em&gt;and&lt;/em&gt; clause too early.&lt;/p&gt;

&lt;p&gt;And what about the third task, adding the intelligent strategy? This is certainly the most interesting part of the job, but I haven't had the time to start it yet. I've been testing with the simplest possible strategy:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (generate-computer-move)
  (apply amb (available-moves)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, &lt;em&gt;amb&lt;/em&gt; has to be &lt;em&gt;apply&lt;/em&gt;ed to a list; unlike &lt;em&gt;randomize&lt;/em&gt;, it takes single arguments rather than a single list.&lt;/p&gt;

&lt;p&gt;Perhaps one day I'll get round to starting the third task. If you want to help me, get the file from the &lt;a href="http://unbalanced-parentheses.nfshost.com/downloads/"&gt;downloads page&lt;/a&gt; and get coding!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5190076856605055588?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5190076856605055588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5190076856605055588' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5190076856605055588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5190076856605055588'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/two-out-of-three-tictactoe.html' title='Two out of three - TicTacToe'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-1623980157537094870</id><published>2008-06-27T18:33:00.000+01:00</published><updated>2011-02-07T12:22:02.337Z</updated><title type='text'>Vacuum-tube Lisp</title><content type='html'>&lt;p&gt;I love the idea of a &lt;a href="http://infolab.stanford.edu/pub/voy/museum/pictures/display/2-2-Tubes.htm"&gt;vacuum-tube computer&lt;/a&gt; running Lisp. Here's &lt;a href="http://www.iwriteiam.nl/HaCAR_CDR.html"&gt;Steve Russell&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wrote the first implementation of a LISP interpreter on the IBM 704 at MIT in early in 1959.&lt;/p&gt;

&lt;p&gt;The 704 family (704, 709, 7090) had "Address" and "Decrement" fields that were 15 bits long in some of the looping instructions. There were also special load and store instructions that moved these 15-bit addresses between memory and the index regiseters ( 3 on the 704, 7 on the others )&lt;/p&gt;

&lt;p&gt;We had devised a representation for list structure that took advantage of these instructions.&lt;/p&gt;

&lt;p&gt;Because of an unfortunate temporary lapse of inspiration, we couldn't think of any other names for the 2 pointers in a list node than "address" and "decrement", so we called the functions CAR for "Contents of Address of Register" and CDR for "Contents of Decrement of Register".&lt;/p&gt;

&lt;p&gt;After several months and giving a few classes in LISP, we realized that "first" and "rest" were better names, and we (John McCarthy, I and some of the rest of the AI Project) tried to get people to use them instead.&lt;/p&gt;

&lt;p&gt;Alas, it was too late! We couldn't make it stick at all. So we have CAR and CDR.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like Steve, I much prefer &lt;em&gt;first&lt;/em&gt; and &lt;em&gt;rest&lt;/em&gt; to &lt;em&gt;car&lt;/em&gt; and &lt;em&gt;cdr&lt;/em&gt;. This approach is evident throughout newLISP, which usually leans towards a user-friendly and abbreviation-free approach to function naming.&lt;/p&gt;

&lt;p&gt;But, although &lt;em&gt;car&lt;/em&gt; and &lt;em&gt;cdr&lt;/em&gt; have been uninspired for nearly 50 years, they have survived because they offer an extra geeky ability: you can add more &lt;em&gt;a&lt;/em&gt; and &lt;em&gt;d&lt;/em&gt; letters between the "c" and "r", to produce functions with even weirder names. So &lt;em&gt;caddr&lt;/em&gt; finds the &lt;em&gt;car&lt;/em&gt; of the &lt;em&gt;cdr&lt;/em&gt; of the &lt;em&gt;cdr&lt;/em&gt;; you read from left to right, although the functions are applied from right to left as usual.&lt;/p&gt;

&lt;p&gt;To be honest, I don't know how I've managed to write any code at all in newLISP without having this readable and user-friendly syntax at my fingertips. So it's time for newLISP to be 'cdadderized':&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (car x) (first x))
(define (cdr x) (rest x))

(define (cdadderize x) 
  (inc 'x)
  (set 'results '())
  (until (= x 0)
     (push   (% x 2) results)
     (set 'x (/ x 2)))
  (set 'results (rest results) 'f-name results)
  (map (fn (a b) (replace a results b)) '(0 1) '("(car " "(cdr "))
  (push (string "x"  (dup ")" (length results))) results -1)
  (set 'results (join results))
  (map (fn (a b) (replace a f-name b)) '(0 1) '("a" "d"))
  (letex ((fnm   (sym (string "c" (join f-name) "r")))
          (body results))
    (define (fnm x) (eval-string body))
    fnm))

(for (i 3 1022) (cdadderize i)) ; we did 1 and 2 :)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Not the prettiest code, but let's run it and look at the new functions that have been defined:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(filter (fn (s) 
    (and (starts-with  (name s) "ca|cd" 0) 
         (ends-with (name s) "r"))) 
    (symbols))
;-&gt;
(caaaaaaaaaar caaaaaaaaadr caaaaaaaaar caaaaaaaadr caaaaaaaar caaaaaaadar caaaaaaaddr 
caaaaaaadr caaaaaaar caaaaaadaar caaaaaadadr caaaaaadar caaaaaaddar caaaaaadddr 
caaaaaaddr caaaaaadr caaaaaar caaaaadaaar caaaaadaadr caaaaadaar caaaaadadar caaaaadaddr 
caaaaadadr caaaaadar caaaaaddaar caaaaaddadr caaaaaddar caaaaadddar caaaaaddddr 
caaaaadddr caaaaaddr caaaaadr caaaaar caaaadaaaar caaaadaaadr caaaadaaar caaaadaadar 
caaaadaaddr caaaadaadr caaaadaar caaaadadaar caaaadadadr caaaadadar caaaadaddar 
caaaadadddr caaaadaddr caaaadadr caaaadar caaaaddaaar caaaaddaadr caaaaddaar caaaaddadar 
caaaaddaddr caaaaddadr caaaaddar caaaadddaar caaaadddadr caaaadddar caaaaddddar 
caaaadddddr caaaaddddr caaaadddr caaaaddr caaaadr caaaar caaadaaaaar caaadaaaadr 
caaadaaaar caaadaaadar caaadaaaddr caaadaaadr caaadaaar caaadaadaar caaadaadadr 
caaadaadar caaadaaddar caaadaadddr caaadaaddr caaadaadr caaadaar caaadadaaar caaadadaadr 
caaadadaar caaadadadar caaadadaddr caaadadadr caaadadar caaadaddaar caaadaddadr 
caaadaddar caaadadddar caaadaddddr caaadadddr caaadaddr caaadadr caaadar caaaddaaaar 
caaaddaaadr caaaddaaar caaaddaadar caaaddaaddr caaaddaadr caaaddaar caaaddadaar 
caaaddadadr caaaddadar caaaddaddar caaaddadddr caaaddaddr caaaddadr caaaddar caaadddaaar 
caaadddaadr caaadddaar caaadddadar caaadddaddr caaadddadr caaadddar caaaddddaar 
caaaddddadr caaaddddar caaadddddar caaaddddddr caaadddddr caaaddddr caaadddr caaaddr 
caaadr caaar caadaaaaaar caadaaaaadr caadaaaaar caadaaaadar caadaaaaddr caadaaaadr 
caadaaaar caadaaadaar caadaaadadr caadaaadar caadaaaddar caadaaadddr caadaaaddr 
caadaaadr caadaaar caadaadaaar caadaadaadr caadaadaar caadaadadar caadaadaddr caadaadadr 
caadaadar caadaaddaar caadaaddadr caadaaddar caadaadddar caadaaddddr caadaadddr 
caadaaddr caadaadr caadaar caadadaaaar caadadaaadr caadadaaar caadadaadar caadadaaddr 
caadadaadr caadadaar caadadadaar caadadadadr caadadadar caadadaddar caadadadddr 
caadadaddr caadadadr caadadar caadaddaaar caadaddaadr caadaddaar caadaddadar caadaddaddr 
caadaddadr caadaddar caadadddaar caadadddadr caadadddar caadaddddar caadadddddr 
caadaddddr caadadddr caadaddr caadadr caadar caaddaaaaar caaddaaaadr caaddaaaar 
caaddaaadar caaddaaaddr caaddaaadr caaddaaar caaddaadaar caaddaadadr caaddaadar 
caaddaaddar caaddaadddr caaddaaddr caaddaadr caaddaar caaddadaaar caaddadaadr caaddadaar 
caaddadadar caaddadaddr caaddadadr caaddadar caaddaddaar caaddaddadr caaddaddar 
caaddadddar caaddaddddr caaddadddr caaddaddr caaddadr caaddar caadddaaaar caadddaaadr 
caadddaaar caadddaadar caadddaaddr caadddaadr caadddaar caadddadaar caadddadadr 
caadddadar caadddaddar caadddadddr caadddaddr caadddadr caadddar caaddddaaar caaddddaadr 
caaddddaar caaddddadar caaddddaddr caaddddadr caaddddar caadddddaar caadddddadr 
caadddddar caaddddddar caadddddddr caaddddddr caadddddr caaddddr caadddr caaddr 
caadr caar cadaaaaaaar cadaaaaaadr cadaaaaaar cadaaaaadar cadaaaaaddr cadaaaaadr 
cadaaaaar cadaaaadaar cadaaaadadr cadaaaadar cadaaaaddar cadaaaadddr cadaaaaddr 
cadaaaadr cadaaaar cadaaadaaar cadaaadaadr cadaaadaar cadaaadadar cadaaadaddr cadaaadadr 
cadaaadar cadaaaddaar cadaaaddadr cadaaaddar cadaaadddar cadaaaddddr cadaaadddr 
cadaaaddr cadaaadr cadaaar cadaadaaaar cadaadaaadr cadaadaaar cadaadaadar cadaadaaddr 
cadaadaadr cadaadaar cadaadadaar cadaadadadr cadaadadar cadaadaddar cadaadadddr 
cadaadaddr cadaadadr cadaadar cadaaddaaar cadaaddaadr cadaaddaar cadaaddadar cadaaddaddr 
cadaaddadr cadaaddar cadaadddaar cadaadddadr cadaadddar cadaaddddar cadaadddddr 
cadaaddddr cadaadddr cadaaddr cadaadr cadaar cadadaaaaar cadadaaaadr cadadaaaar 
cadadaaadar cadadaaaddr cadadaaadr cadadaaar cadadaadaar cadadaadadr cadadaadar 
cadadaaddar cadadaadddr cadadaaddr cadadaadr cadadaar cadadadaaar cadadadaadr cadadadaar 
cadadadadar cadadadaddr cadadadadr cadadadar cadadaddaar cadadaddadr cadadaddar 
cadadadddar cadadaddddr cadadadddr cadadaddr cadadadr cadadar cadaddaaaar cadaddaaadr 
cadaddaaar cadaddaadar cadaddaaddr cadaddaadr cadaddaar cadaddadaar cadaddadadr 
cadaddadar cadaddaddar cadaddadddr cadaddaddr cadaddadr cadaddar cadadddaaar cadadddaadr 
cadadddaar cadadddadar cadadddaddr cadadddadr cadadddar cadaddddaar cadaddddadr 
cadaddddar cadadddddar cadaddddddr cadadddddr cadaddddr cadadddr cadaddr cadadr 
cadar caddaaaaaar caddaaaaadr caddaaaaar caddaaaadar caddaaaaddr caddaaaadr caddaaaar 
caddaaadaar caddaaadadr caddaaadar caddaaaddar caddaaadddr caddaaaddr caddaaadr 
caddaaar caddaadaaar caddaadaadr caddaadaar caddaadadar caddaadaddr caddaadadr caddaadar 
caddaaddaar caddaaddadr caddaaddar caddaadddar caddaaddddr caddaadddr caddaaddr 
caddaadr caddaar caddadaaaar caddadaaadr caddadaaar caddadaadar caddadaaddr caddadaadr 
caddadaar caddadadaar caddadadadr caddadadar caddadaddar caddadadddr caddadaddr 
caddadadr caddadar caddaddaaar caddaddaadr caddaddaar caddaddadar caddaddaddr caddaddadr 
caddaddar caddadddaar caddadddadr caddadddar caddaddddar caddadddddr caddaddddr 
caddadddr caddaddr caddadr caddar cadddaaaaar cadddaaaadr cadddaaaar cadddaaadar 
cadddaaaddr cadddaaadr cadddaaar cadddaadaar cadddaadadr cadddaadar cadddaaddar 
cadddaadddr cadddaaddr cadddaadr cadddaar cadddadaaar cadddadaadr cadddadaar cadddadadar 
cadddadaddr cadddadadr cadddadar cadddaddaar cadddaddadr cadddaddar cadddadddar 
cadddaddddr cadddadddr cadddaddr cadddadr cadddar caddddaaaar caddddaaadr caddddaaar 
caddddaadar caddddaaddr caddddaadr caddddaar caddddadaar caddddadadr caddddadar 
caddddaddar caddddadddr caddddaddr caddddadr caddddar cadddddaaar cadddddaadr cadddddaar 
cadddddadar cadddddaddr cadddddadr cadddddar caddddddaar caddddddadr caddddddar 
cadddddddar cadddddddddr caddddddddr cadddddddr caddddddr cadddddr caddddr cadddr 
caddr cadr car cdaaaaaaaar cdaaaaaaadr cdaaaaaaar cdaaaaaadar cdaaaaaaddr cdaaaaaadr 
cdaaaaaar cdaaaaadaar cdaaaaadadr cdaaaaadar cdaaaaaddar cdaaaaadddr cdaaaaaddr 
cdaaaaadr cdaaaaar cdaaaadaaar cdaaaadaadr cdaaaadaar cdaaaadadar cdaaaadaddr cdaaaadadr 
cdaaaadar cdaaaaddaar cdaaaaddadr cdaaaaddar cdaaaadddar cdaaaaddddr cdaaaadddr 
cdaaaaddr cdaaaadr cdaaaar cdaaadaaaar cdaaadaaadr cdaaadaaar cdaaadaadar cdaaadaaddr 
cdaaadaadr cdaaadaar cdaaadadaar cdaaadadadr cdaaadadar cdaaadaddar cdaaadadddr 
cdaaadaddr cdaaadadr cdaaadar cdaaaddaaar cdaaaddaadr cdaaaddaar cdaaaddadar cdaaaddaddr 
cdaaaddadr cdaaaddar cdaaadddaar cdaaadddadr cdaaadddar cdaaaddddar cdaaadddddr 
cdaaaddddr cdaaadddr cdaaaddr cdaaadr cdaaar cdaadaaaaar cdaadaaaadr cdaadaaaar 
cdaadaaadar cdaadaaaddr cdaadaaadr cdaadaaar cdaadaadaar cdaadaadadr cdaadaadar 
cdaadaaddar cdaadaadddr cdaadaaddr cdaadaadr cdaadaar cdaadadaaar cdaadadaadr cdaadadaar 
cdaadadadar cdaadadaddr cdaadadadr cdaadadar cdaadaddaar cdaadaddadr cdaadaddar 
cdaadadddar cdaadaddddr cdaadadddr cdaadaddr cdaadadr cdaadar cdaaddaaaar cdaaddaaadr 
cdaaddaaar cdaaddaadar cdaaddaaddr cdaaddaadr cdaaddaar cdaaddadaar cdaaddadadr 
cdaaddadar cdaaddaddar cdaaddadddr cdaaddaddr cdaaddadr cdaaddar cdaadddaaar cdaadddaadr 
cdaadddaar cdaadddadar cdaadddaddr cdaadddadr cdaadddar cdaaddddaar cdaaddddadr 
cdaaddddar cdaadddddar cdaaddddddr cdaadddddr cdaaddddr cdaadddr cdaaddr cdaadr 
cdaar cdadaaaaaar cdadaaaaadr cdadaaaaar cdadaaaadar cdadaaaaddr cdadaaaadr cdadaaaar 
cdadaaadaar cdadaaadadr cdadaaadar cdadaaaddar cdadaaadddr cdadaaaddr cdadaaadr 
cdadaaar cdadaadaaar cdadaadaadr cdadaadaar cdadaadadar cdadaadaddr cdadaadadr cdadaadar 
cdadaaddaar cdadaaddadr cdadaaddar cdadaadddar cdadaaddddr cdadaadddr cdadaaddr 
cdadaadr cdadaar cdadadaaaar cdadadaaadr cdadadaaar cdadadaadar cdadadaaddr cdadadaadr 
cdadadaar cdadadadaar cdadadadadr cdadadadar cdadadaddar cdadadadddr cdadadaddr 
cdadadadr cdadadar cdadaddaaar cdadaddaadr cdadaddaar cdadaddadar cdadaddaddr cdadaddadr 
cdadaddar cdadadddaar cdadadddadr cdadadddar cdadaddddar cdadadddddr cdadaddddr 
cdadadddr cdadaddr cdadadr cdadar cdaddaaaaar cdaddaaaadr cdaddaaaar cdaddaaadar 
cdaddaaaddr cdaddaaadr cdaddaaar cdaddaadaar cdaddaadadr cdaddaadar cdaddaaddar 
cdaddaadddr cdaddaaddr cdaddaadr cdaddaar cdaddadaaar cdaddadaadr cdaddadaar cdaddadadar 
cdaddadaddr cdaddadadr cdaddadar cdaddaddaar cdaddaddadr cdaddaddar cdaddadddar 
cdaddaddddr cdaddadddr cdaddaddr cdaddadr cdaddar cdadddaaaar cdadddaaadr cdadddaaar 
cdadddaadar cdadddaaddr cdadddaadr cdadddaar cdadddadaar cdadddadadr cdadddadar 
cdadddaddar cdadddadddr cdadddaddr cdadddadr cdadddar cdaddddaaar cdaddddaadr cdaddddaar 
cdaddddadar cdaddddaddr cdaddddadr cdaddddar cdadddddaar cdadddddadr cdadddddar 
cdaddddddar cdadddddddr cdaddddddr cdadddddr cdaddddr cdadddr cdaddr cdaderizer 
cdadr cdar cddaaaaaaar cddaaaaaadr cddaaaaaar cddaaaaadar cddaaaaaddr cddaaaaadr 
cddaaaaar cddaaaadaar cddaaaadadr cddaaaadar cddaaaaddar cddaaaadddr cddaaaaddr 
cddaaaadr cddaaaar cddaaadaaar cddaaadaadr cddaaadaar cddaaadadar cddaaadaddr cddaaadadr 
cddaaadar cddaaaddaar cddaaaddadr cddaaaddar cddaaadddar cddaaaddddr cddaaadddr 
cddaaaddr cddaaadr cddaaar cddaadaaaar cddaadaaadr cddaadaaar cddaadaadar cddaadaaddr 
cddaadaadr cddaadaar cddaadadaar cddaadadadr cddaadadar cddaadaddar cddaadadddr 
cddaadaddr cddaadadr cddaadar cddaaddaaar cddaaddaadr cddaaddaar cddaaddadar cddaaddaddr 
cddaaddadr cddaaddar cddaadddaar cddaadddadr cddaadddar cddaaddddar cddaadddddr 
cddaaddddr cddaadddr cddaaddr cddaadr cddaar cddadaaaaar cddadaaaadr cddadaaaar 
cddadaaadar cddadaaaddr cddadaaadr cddadaaar cddadaadaar cddadaadadr cddadaadar 
cddadaaddar cddadaadddr cddadaaddr cddadaadr cddadaar cddadadaaar cddadadaadr cddadadaar 
cddadadadar cddadadaddr cddadadadr cddadadar cddadaddaar cddadaddadr cddadaddar 
cddadadddar cddadaddddr cddadadddr cddadaddr cddadadr cddadar cddaddaaaar cddaddaaadr 
cddaddaaar cddaddaadar cddaddaaddr cddaddaadr cddaddaar cddaddadaar cddaddadadr 
cddaddadar cddaddaddar cddaddadddr cddaddaddr cddaddadr cddaddar cddadddaaar cddadddaadr 
cddadddaar cddadddadar cddadddaddr cddadddadr cddadddar cddaddddaar cddaddddadr 
cddaddddar cddadddddar cddaddddddr cddadddddr cddaddddr cddadddr cddaddr cddadr 
cddar cdddaaaaaar cdddaaaaadr cdddaaaaar cdddaaaadar cdddaaaaddr cdddaaaadr cdddaaaar 
cdddaaadaar cdddaaadadr cdddaaadar cdddaaaddar cdddaaadddr cdddaaaddr cdddaaadr 
cdddaaar cdddaadaaar cdddaadaadr cdddaadaar cdddaadadar cdddaadaddr cdddaadadr cdddaadar 
cdddaaddaar cdddaaddadr cdddaaddar cdddaadddar cdddaaddddr cdddaadddr cdddaaddr 
cdddaadr cdddaar cdddadaaaar cdddadaaadr cdddadaaar cdddadaadar cdddadaaddr cdddadaadr 
cdddadaar cdddadadaar cdddadadadr cdddadadar cdddadaddar cdddadadddr cdddadaddr 
cdddadadr cdddadar cdddaddaaar cdddaddaadr cdddaddaar cdddaddadar cdddaddaddr cdddaddadr 
cdddaddar cdddadddaar cdddadddadr cdddadddar cdddaddddar cdddadddddr cdddaddddr 
cdddadddr cdddaddr cdddadr cdddar cddddaaaaar cddddaaaadr cddddaaaar cddddaaadar 
cddddaaaddr cddddaaadr cddddaaar cddddaadaar cddddaadadr cddddaadar cddddaaddar 
cddddaadddr cddddaaddr cddddaadr cddddaar cddddadaaar cddddadaadr cddddadaar cddddadadar 
cddddadaddr cddddadadr cddddadar cddddaddaar cddddaddadr cddddaddar cddddadddar 
cddddaddddr cddddadddr cddddaddr cddddadr cddddar cdddddaaaar cdddddaaadr cdddddaaar 
cdddddaadar cdddddaaddr cdddddaadr cdddddaar cdddddadaar cdddddadadr cdddddadar 
cdddddaddar cdddddadddr cdddddaddr cdddddadr cdddddar cddddddaaar cddddddaadr cddddddaar 
cddddddadar cddddddaddr cddddddadr cddddddar cdddddddaar cdddddddadr cdddddddar 
cddddddddar cdddddddddr cddddddddr cdddddddr cddddddr cdddddr cddddr cdddr cddr 
cdr)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Some useful tools there - surely some of these functions would be proud to appear in any Common Lisp program? How about the stretch-limo &lt;em&gt;caaaaaaaaar&lt;/em&gt;, for one ("My god, it's full of &lt;em&gt;car&lt;/em&gt;s")? Then there's &lt;em&gt;cadadar&lt;/em&gt; - north of the USA, if you have a cold. A few are useful in other ways, too: &lt;em&gt;cdddddddddr&lt;/em&gt; provides excellent tonguing practice for woodwind players, especially the bassoonists among you.&lt;/p&gt;

&lt;p&gt;By the way, for compatibility with Common Lisp, it's not necessary to go all the way up 1022. Call &lt;em&gt;(cdadderize 14)&lt;/em&gt; to give you:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(caaar caadr caar cadar caddr cadr car cdaar cdadr cdar cddar cdddr cddr cdr)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;whereas 30 gives you everything up to &lt;em&gt;cddddr&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To be honest, I can't see myself using these very much. Well, OK: I will &lt;em&gt;never&lt;/em&gt; use these. But I like the idea that the ever-adaptable Lisp was once written for vacuum-tubes and can still run like the wind on my iMac.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-1623980157537094870?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/1623980157537094870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=1623980157537094870' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1623980157537094870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1623980157537094870'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/vacuum-tube-lisp.html' title='Vacuum-tube Lisp'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-1082053163284902766</id><published>2008-06-27T18:32:00.003+01:00</published><updated>2011-02-07T12:22:33.527Z</updated><title type='text'>iTunes covers by newLISP</title><content type='html'>&lt;p&gt;The latest iPods and iPhones from Apple share an excellent user interface, but there's one area that I find difficult to use. Ironically, in the case of the iPod at least, it's the main screen you see while music's being played that's the problem. Consider this photograph:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/ipod-display.jpg" alt="photo of iPod Touch being unhelpful" title="this is what my iPod sometimes looks like" /&gt;&lt;/p&gt;

&lt;p&gt;It gives a reasonable impression of what the problems are. Unless your eyesight is good (mine isn't), and the lighting conditions are good (they often aren't), it's hard to read the three lines of text at the top - artist, title, and album. Two of these lines are drawn in grey on a black background. A lower-case 'a' in the chosen font measures just over 1 millimetre, or about 3 points. The large area in the middle of the display can show you the album artwork, but if there's no artwork you see two big quavers on a light grey background. What really needs to be provided is a customizable display, where you can specify what information is shown, and how it's presented. It would be nice if you could ask for a display that contains the main information in a larger font, and easier to read graphics.&lt;/p&gt;

&lt;p&gt;But, I hear you say, what's all this got to do with newLISP?&lt;/p&gt;

&lt;p&gt;You know the answer.&lt;/p&gt;

&lt;p&gt;I couldn't resist trying to persuade iTunes to create substitute album artwork that shows the music's details in  larger type as album art when it's downloaded to the iPod... I can use newLISP for most of the work. But, unfortunately, the easiest way to talk to iTunes on MacOS X is by using AppleScript, that quirky and devious language whose syntax was once described as "similar to English spelling - not thoroughly thought through".&lt;/p&gt;

&lt;p&gt;So let's get the AppleScript stuff over first. Here's a good place to start: get the current selection from iTunes. The necessary AppleScript can be wrapped up in a newLISP function call:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (itunes-get-selection)
    (let ((result (exec 
             (format [text]osascript -s s -e 'tell application "iTunes" to get selection'[ /text]))))
         (set 'result (replace "{|}" (first result) "" 0))
         (parse result ", ")))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;iTunes returns the information in an AppleScript list, which is easily changed into a newLISP list. Notice that extra '-s s' option flag in the osascript command. That appears to be significant. The result is a list of wordy track references (WTRs):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;("file track id 15639 of user playlist id 13999 of source id 41 of application \"iTunes\"" 
 "file track id 15299 of user playlist id 13999 of source id 41 of application \"iTunes\"")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Given a WTR, it's possible to ask iTunes for more information about a track. As usual, I use the Unlikely Delimiter Ploy (UDP) when obtaining information from iTunes, mainly because iTunes track names can contain virtually any character. I've not yet seen four tildes used before, though, so I'm sticking with them for now:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (itunes-get-details-of-track file-track-id)
  (let ((result (exec 
            (format [text]osascript -s s -e 'tell application "iTunes"
              tell %s 
                set d to (name &amp; "~~~~" &amp; artist &amp; "~~~~" &amp; album &amp; "~~~~" &amp; duration &amp; "~~~~" &amp; year &amp; "~~~~" &amp; genre) as text
               end tell
             end tell '[ /text] file-track-id))))
      (when result           
         (set 'result 
             (transpose 
                (list '(track-name artist album duration year genre) 
                (parse (trim (first result) {"} {"}) "~~~~"))))
          result)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's such an ugly function! But it works:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;((track-name "Maiden Voyage") 
 (artist "Herbie Hancock") 
 (album "An Evening With Herbie Hancock and Chick Corea - In Concert CD2") 
 (duration "810.840026855469") 
 (year "1978") 
 (genre "jazz"))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There's just one more piece of AppleScript required, to change the album artwork. Apple could have made this nice and easy:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;set artwork of track to "some-artwork.jpg" -- this doesn't work :)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;but they didn't. The required newLISP-wrapped code is this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (itunes-set-data-of-artwork file-track-id)
   (exec (format [text]osascript -s s -e 'tell application "iTunes"
      delete every artwork of %s
      set the_artwork to read file (((path to desktop) as text) &amp; "artwork-canvas.pict") from 513 as picture
      set data of artwork 1 of %s to the_artwork
    end tell'[ /text] file-track-id file-track-id )))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This looks for a graphic in PICT format at ~/Desktop/artwork-canvas.pict, strips the 512 byte header, loads the picture data, and applies the result to the track as album artwork.&lt;/p&gt;

&lt;p&gt;I can't persuade newLISP-GS to produce PICT files, but luckily the Mac comes with the &lt;em&gt;sips&lt;/em&gt; image processing tool, so converting PNG to PICT is easy.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (convert-to-pict file-name)
    (let ((new-file-name (replace {(.*)(\.)(.*$)} (string file-name) (string $1 ".pict") 1)))
          (exec (format "sips -s format pict '%s' --out '%s'"  file-name new-file-name))))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So now I can take off my AppleScripter's hat and switch to newLISP. To produce the graphics, I use a newLISP-GS canvas, after first setting up the graphics system:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(load (append (env "NEWLISPDIR") "/guiserver.lsp"))
(gs:init)
(gs:window 'Artwork 200 200 320 320)  
(gs:canvas 'ArtworkCanvas)
(gs:set-size 'ArtworkCanvas  320 320)
(gs:add-to 'Artwork 'ArtworkCanvas )
(gs:set-background 'ArtworkCanvas gs:gray)
(gs:set-visible 'Artwork true)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and then use commands such as &lt;em&gt;set-font&lt;/em&gt; and &lt;em&gt;draw-text&lt;/em&gt; to draw some more informative artwork. The main loop is simple enough, in theory at least:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(dolist (track (itunes-get-selection))
  (set 'data (itunes-get-details-of-track track))

  ; start drawing
  (gs:draw-text 'AlbumText (string (lookup 'artist data)) ...

  ; repeat for other information

  ; then:
  (gs:update)  

  (set 'temp-file (string "/Users/me/Desktop/" {artwork-canvas.png}))
  (gs:export temp-file)
  (convert-to-pict temp-file)
  (itunes-set-data-of-artwork track)

  ; delete all text
  (gs:delete-tag 'AlbumText)

  ; and start on the next one
  )
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In practice there are various issues to address, such as timing - all these external processes appear to hamper a smoothly flowing script - so some &lt;em&gt;sleep&lt;/em&gt; commands have to be added. Also, you might want to waste, I mean, spend more time adjusting the formatting and typography. I like adjusting the point size so that the line of type fits perfectly, and changing the colours to suit the genre. But that's another story!&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/artwork-canvas.png" alt="some artwork" title="a bit of handmade iTunes artwork" /&gt;&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-1082053163284902766?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/1082053163284902766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=1082053163284902766' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1082053163284902766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1082053163284902766'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/itunes-covers-by-newlisp.html' title='iTunes covers by newLISP'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-2580124353194457045</id><published>2008-06-27T18:32:00.002+01:00</published><updated>2011-02-07T12:22:27.195Z</updated><title type='text'>The making of ... Scene 78</title><content type='html'>&lt;p&gt;INT. OFFICE. NIGHT. GEORGE sits at a big desk. A computer screen casts a dull light over a pile of papers.&lt;/p&gt;

&lt;p&gt;The phone rings. GEORGE answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE: (VOICEOVER)&lt;/strong&gt; Hey, George, it's Steve. Any progress on the title? Gonna have to move forward on this real quick now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; Nothing yet, Steve. Still working through some suggestions. I got the final draft of the titlemaker code, just running it now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; Uh huh, OK. What did you feed into it this time?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; Those key words we talked about last week. Changed a few.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; Right. So, did we get any promising ideas?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; Um ... let me see. Bit of problem with memory allocation. Ah yes. How about: "Indiana Jones and the Lost Kingdom of the Crystal Grail."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; I dunno. Sounds a bit ... er ... country and western.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; Know what you mean. So what about "Indiana Jones and the Holy Crystal of the Doom Raiders". OK, don't answer... Try "Indiana Jones and the Doom Skull of the Lost Kingdom"? No? "Indiana Jones and the Lost Legend of the Holy Kingdom"?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; Yeah, yeah, it's not ... these are just not working for me. Although I like the first four words. They're, you know, strong. Are you sure that code's good, George?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; Yup, got it from Hanson, out East. He's usually our number one guy for this kind of thing. Looks OK to me, I think we got the latest version...&lt;/p&gt;

&lt;p&gt;Cut to computer screen, which shows the following programming code.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (mappend) (apply append (apply map (args))))

(define (remove1 elt lst) 
  (let ((elt-pos (find elt lst))) 
    (if elt-pos (pop lst elt-pos)) 
    lst)) 

(define (k-permutations k multiset) 
  (let ((pivots (unique multiset))) 
    (if (= k 1) 
        (map list pivots) 
        (mappend (lambda (p) 
                 (map (lambda (k-1-perm) (cons p k-1-perm)) 
                      (k-permutations (- k 1) (remove1 p multiset)))) 
               pivots)))) 

(set 'keywords 
  '("Lost" "Skull" "Kingdom" "Raiders" "Ark" "Grail" "Doom" "Legend" "Holy" "Crystal" "Kingdom"))

(map (fn (title) 
   (println  "Indiana Jones and the " 
     (title 0) " " 
     (title 1) " of the " 
     (title 2) " " 
     (title 3))) 
   (k-permutations (length keywords) keywords))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; Hmm, well it sure isn't doing the business for us yet. Run it some more - we'll just have to keep at it until something good turns up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; OK, Steve. What about "Indiana Jones and the Lost Skull of the Legend Kingdom"?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; Nah.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEORGE:&lt;/strong&gt; Uh, "Indiana Jones and the Grail Ark of the Holy Skull"?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEVE:&lt;/strong&gt; Holy skull? Christ, George, I think even Crystal Gayle's better than that.&lt;/p&gt;

&lt;p&gt;FADE.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-2580124353194457045?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/2580124353194457045/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=2580124353194457045' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2580124353194457045'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2580124353194457045'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/making-of-scene-78.html' title='The making of ... Scene 78'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5170920148229428664</id><published>2008-06-27T18:32:00.001+01:00</published><updated>2011-02-07T12:22:22.370Z</updated><title type='text'>(newLISP) plist</title><content type='html'>&lt;p&gt;Seth Dillingham is a unusual guy. As well as - apparently - being able to think in regular expressions, he is also prepared to write something useful for us newLISPers, even though he doesn't use the language himself. If you use BBEdit or TextWrangler, and use newLISP, you'll probably want to get the latest version of his &lt;a href="http://media.macrobyte.net/bbedit/newlisp.plist.zip"&gt;codeless language module&lt;/a&gt; for newLISP, which he's updated for the forthcoming (and amazing) newLISP version 9.4.&lt;/p&gt;

&lt;p&gt;You can also read about how it's done, too, &lt;a href="http://www.truerwords.net/articles/bbedit/codeless_language_module.html"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Thanks, Seth, and good luck with the &lt;a href="http://www.pmc.org/"&gt;PMC challenge&lt;/a&gt;!&lt;/p&gt;

&lt;br /&gt;
&lt;p&gt;&lt;a href="http://www.truerwords.net/fundraising/pmcsoftware/"&gt;&lt;img
  src="http://media.truerwords.net/images/pmc/donations_wanted_2008_500.png"
  width="500" height="331"alt="Donate Software, Fight Cancer" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5170920148229428664?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5170920148229428664/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5170920148229428664' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5170920148229428664'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5170920148229428664'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/newlisp-plist.html' title='(newLISP) plist'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-3655517913610522377</id><published>2008-06-27T18:32:00.000+01:00</published><updated>2011-02-07T12:22:15.555Z</updated><title type='text'>Plug addict</title><content type='html'>&lt;p&gt;I'm trying out a very simple plug-in system for the newLISP script (as yet unnamed) that produces this blog. The idea is that you can more easily add or remove features by adding or removing plug-ins, rather than by modifying the main script each time. So far the results are promising.&lt;/p&gt;

&lt;p&gt;Each plug-in is just a newLISP context stored in a file, so the first relevant piece of code in the main script is this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set 'plug-ins '())
(dolist (i (sort (directory plug-ins-dir "\\.lsp")))
   (load i))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which loads every newLISP file in the plug-ins directory. The code for a plug-in file looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(context 'Whatever)
; ... function definitions
; and finally:
(push (context) plug-ins -1)
; eof
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the last line the plug-in registers its presence and willingness to participate by appending its name to the master list. The advantage of doing it this way is that you can modify the order in which plug-ins load, and execute, by adjusting the file names, rather than by changing any code. For example, given these plug-ins:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;01debug.lsp
10html.lsp
20atom.lsp
30cache.lsp
default.lsp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;you can play with the numbers to change the order of execution.&lt;/p&gt;

&lt;p&gt;The following macro function in the main script runs through the plug-ins (ie the contexts) in order, executing the same function in each.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define-macro (call-plug-in-function flag func)
  (catch 
    (dolist (ctxt plug-ins)
      (if (context ctxt func)
          (set 'result (apply (sym func ctxt) (args)))
          (set 'result nil))
      (if (and flag result) 
          (throw result))))
   result)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This tries to call function &lt;em&gt;func&lt;/em&gt; in every available context, in the order the files were loaded. There's a &lt;em&gt;flag&lt;/em&gt; argument to the macro which is used to specify whether only one plug-in can handle the task or whether all plug-ins can have a shot at the task. The result returned by each function also determines whether subsequent plug-ins can handle the task if for any reason the current plug-in can't cope.&lt;/p&gt;

&lt;p&gt;All that's now required is for the tasks to be organized into functions, and for the functions to be passed to &lt;em&gt;call-plug-in-function&lt;/em&gt; for evaluation in all contexts.&lt;/p&gt;

&lt;p&gt;Here's an example of how it works, for the Atom news-feed plug-in, given the example set of plug-ins shown above. The main script decides that it's time to choose a template:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; (call-plug-in-function true "choose-template")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;em&gt;call-plug-in-function&lt;/em&gt; macro looks for a &lt;em&gt;choose-template&lt;/em&gt; function in every context. Only one template set can be chosen, so the  &lt;em&gt;true&lt;/em&gt; value tells all the plug-ins that the first one to handle it successfully will be the only one to handle it. There's no definition for &lt;em&gt;choose-template&lt;/em&gt; in the first plug-in (ie &lt;em&gt;Debug:choose-template&lt;/em&gt; doesn't exist), but in the next plug-in there is. However, &lt;em&gt;HTML:choose-template&lt;/em&gt; decides that it doesn't want to handle anything to do with Atom, so returns nil. Next, it's the turn of &lt;em&gt;Atom:choose-template&lt;/em&gt;, which should be able to complete its tasks successfully and return &lt;em&gt;true&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;; in context Atom:
(define (choose-template)
  (cond 
     ((= required-format "atom")
         ;
         ; choose suitable template set
         ;
         true)
    (true
         nil)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No other plug-ins get the chance of choosing templates now.&lt;/p&gt;

&lt;p&gt;Changes to the way Atom-formatted news is generated can be made to the Atom context. The Atom context can be removed altogether without affecting the behaviour of other parts of the application. I could write a different Atom context and try it out by changing the numbers in the filenames.&lt;/p&gt;

&lt;p&gt;Notice that I've assumed that the main script provides enough information about the environment and the task in hand for the plug-ins to make intelligent decisions about what to do. I'm also assuming - obviously - that there's no malicious intent or desire to disrupt the whole set-up. It's not a good idea to load any plug-ins from an untrusted source...&lt;/p&gt;

&lt;p&gt;It's possible to override built-in behaviour because even the basic tasks are handled by a plug-in file as well. This is loaded last, so it can provide default behaviour to be carried out when none of the other plug-ins have volunteered for tasks.&lt;/p&gt;

&lt;p&gt;Some tasks can be handled by a number of different plug-ins one after the other. For example, a 'tidying-up' function is called from the main script with:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(call-plug-in-function nil "tidy-up")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and each plug-in can do some tidying up if necessary.&lt;/p&gt;

&lt;p&gt;It's early days yet, but I'm interested to see how many different tasks I can get the plug-ins to handle until I have to make changes to the main script!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3655517913610522377?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3655517913610522377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3655517913610522377' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3655517913610522377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3655517913610522377'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/plug-addict.html' title='Plug addict'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-8173823014045940522</id><published>2008-06-27T18:31:00.002+01:00</published><updated>2011-02-07T12:22:52.962Z</updated><title type='text'>C*mments</title><content type='html'>&lt;p&gt;I've managed to cobble together a Comments plugin for this site. It's still a work in progress, so I don't expect it to be 100% reliable just yet. If you submit a comment and it doesn't appear (and you don't consider yourself to be a spammer), please email me and I'll investigate!&lt;/p&gt;

&lt;p&gt;I was a bit surprised that, after installing the plugin, it took only 25 minutes for the first spammers to arrive and leave their tell-tale pharmacological trail. Google doesn't bother much with this site, but apparently spammers can immediately find any web page with a comments form - presumably they're searching the internet for anything with the word "comments" in? Perhaps I should have called it something different.&lt;/p&gt;

&lt;p&gt;As it turned out, it wasn't too bad, because it gave me the chance to test some IP-blacklisting code I put together. This may or may not work, but I needed a bit of genuine spam to test against. I don't need any more, though. :)&lt;/p&gt;

&lt;p&gt;I'm trying out a JavaScript utility called &lt;a href="http://wmd-editor.com/"&gt;WMD&lt;/a&gt;. This lets you format your comments in Markdown and see what the results look like before they're submitted. Although there's a development version of Markdown written in newLISP &lt;a href="http://unbalanced-parentheses.nfshost.com/downloads/"&gt;here&lt;/a&gt;, I couldn't use this to generate a live preview in a web page - JavaScript still rules the browser.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-8173823014045940522?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/8173823014045940522/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=8173823014045940522' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8173823014045940522'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/8173823014045940522'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/cmments.html' title='C*mments'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-1930119035752453512</id><published>2008-06-27T18:31:00.001+01:00</published><updated>2011-02-07T12:22:50.415Z</updated><title type='text'>Dated posts</title><content type='html'>&lt;p&gt;You'll sometimes see the date and time presented in this format - &lt;em&gt;2008-04-19T22:45:33Z&lt;/em&gt;. It's the date (year-month-day) followed by a "T", followed by the time (hours:minutes:seconds), followed by a Z, which specifies the Greenwich or UT Time Zone. ('Z' stands for 'Zulu', but that's another, and possibly more interesting, story.) This date-time format conforms to the Atom Date construct, as described in the &lt;a href="http://tools.ietf.org/html/rfc4287#section-3.3"&gt;Atom specification&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Date construct is an element whose content MUST conform to the
  "date-time" production in RFC3339.  In addition, an uppercase "T"
  character MUST be used to separate date and time, and an uppercase
  "Z" character MUST be present in the absence of a numeric time zone
  offset.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;atomDateConstruct =
   atomCommonAttributes,
   xsd:dateTime
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Such date values happen to be compatible with the following
  specifications: ISO.8601.1988, W3C.NOTE-datetime-19980827, and
  W3C.REC-xmlschema-2-20041028.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and here's &lt;a href="http://www.w3.org/TR/1998/NOTE-datetime-19980827"&gt;ISO 8601&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The following profile of ISO 8601 dates SHOULD be used in
  new protocols on the Internet.  This is specified using the syntax
  description notation defined in ABNF.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code&gt;
     date-fullyear   = 4DIGIT
     date-month      = 2DIGIT  ; 01-12
     date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                               ; month/year
     time-hour       = 2DIGIT  ; 00-23
     time-minute     = 2DIGIT  ; 00-59
     time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second
                               ; rules
     time-secfrac    = "." 1*DIGIT
     time-numoffset  = ("+" / "-") time-hour ":" time-minute
     time-offset     = "Z" / time-numoffset
    
     partial-time    = time-hour ":" time-minute ":" time-second
                       [time-secfrac]
     full-date       = date-fullyear "-" date-month "-" date-mday
     full-time       = partial-time time-offset
    
     date-time       = full-date "T" full-time
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Important stuff!&lt;/p&gt;

&lt;p&gt;In newLISP, you can get time in this format by using a suitable time format string with &lt;em&gt;date&lt;/em&gt;. For example, the modification time of a file can be presented in RFC3339 date-time format with this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set 'modification-date (date file-mod-date 0 "%Y-%m-%dT%H:%M:%SZ"))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where &lt;em&gt;file-mod-date&lt;/em&gt; is the file's modification date as a seconds-from-1970 value, obtained like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set 'file-mod-date (file-info file 6))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The previous blogging tool I wrote made extensive use of compressed ISO 8601 identifiers:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;20080419224533
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which is a 14-digit number or string. It can be used as either:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(set 'p 20080419224533)
(dec 'p)
;-&gt; 20080419224532
(set 'p "20080419224533")
(length p)
;-&gt; 14
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I used this both as the unique identifier for a post or blog entry, and as the publication date. To convert these compressed 14 digit ISO date-times (and others) to a newLISP seconds-from-1970 value, you can use &lt;em&gt;parse-date&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(parse-date "20080419224533" "%Y%m%d%H%M%S")
;-&gt; 1208645133

(date (parse-date "20080419224533" "%Y%m%d%H%M%S"))
;-&gt; "Sat Apr 19 23:45:33 2008"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first problem is that &lt;em&gt;parse-date&lt;/em&gt; on MacOS X systems appears to have some flaws. &lt;a href="http://www.alh.net/newlisp/phpbb/viewtopic.php?t=2259"&gt;Lutz reckons&lt;/a&gt; that libc.dylib on Mac OS X was broken for the  &lt;em&gt;strptime()&lt;/em&gt; function somewhere between MacOS X 10.4 (Tiger) and 10.5 (Leopard). It was apparently the case that, for some versions of newLISP and MacOS X, the conversion of compressed ISO8601 identifiers didn't work:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(parse-date "20080411234423" "%Y%m%d%H%M%S") 
;-&gt; nil 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Suddenly being unable to read date strings without spaces, I spent a few minutes writing a &lt;em&gt;parse-iso-date&lt;/em&gt; function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(define (parse-iso-date s)
 (let ((y (pop s 0 4))
       (m (pop s 0 2))
       (d (pop s 0 2))
       (h (pop s 0 2))
       (mn (pop s 0 2))
       (sec s))
 (parse-date (format "%s %s %s %s %s %s" y m d h mn sec) "%Y %m %d %H %M %S")))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;but it doesn't look like the right solution - just reformatting a string so that it has spaces between the components and then using &lt;em&gt;parse-date&lt;/em&gt;. And later I found that I was converting between different date formats too often: from compressed ISO to Atom, or from compressed ISO to a familiar local representation, and then back into Atom again.&lt;/p&gt;

&lt;p&gt;When you stare at a problem for long enough, you can often see a bigger problem hidden behind it. I started to dislike seeing these IDs. References to stories used to take the form:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;index.cgi?post-id=20080411234423
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which is computer-friendly but user-hostile, easy enough for a computer to produce but not easy for humans to recognise or decipher. I've typed enough of these in by hand to know that it's surprisingly easy to lose your place in a long string of digits when you're tired or in a hurry. Another problem is updating a post makes these date-based IDs less meaningful - but changing them probably isn't the right solution either.&lt;/p&gt;

&lt;p&gt;So, for the current blogging script, I've adopted a different approach. Stories are referred to using more readable URLs - I read that this was &lt;a href="http://www.w3.org/Provider/Style/URI"&gt;A Good Idea&lt;/a&gt;. Dates are now a part of the metadata but extracted and displayed when required, with more emphasis on how old something is rather than the exact moment it was written. And hopefully it's no longer necessary to display weird date formats just because they're used internally by the software.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-1930119035752453512?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/1930119035752453512/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=1930119035752453512' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1930119035752453512'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/1930119035752453512'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/dated-posts.html' title='Dated posts'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5342769448052333321</id><published>2008-06-27T18:31:00.000+01:00</published><updated>2011-02-07T12:22:40.782Z</updated><title type='text'>Functions That Use and the Functions That Get Used...</title><content type='html'>&lt;p&gt;While writing a function to tell me what functions a function functions with (by this I mean, what functions a function uses to do its thing), it occurred to me that this could be accomplished using newLISP's ability to look within (no wonder newLISP is so enlightened ;-) We could, of course, hand-code one for each function:&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (define (f a b) (+ a  b (- b a)))
&lt;strong&gt;(lambda (a b) (+ a  b (- b a)))&lt;/strong&gt;
&gt; (define (f-uses) '(+ -))
&lt;strong&gt;(lambda () '(+ -))&lt;/strong&gt;
&gt; (f-uses)
&lt;strong&gt;(+ -)&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
 
&lt;p&gt;But that would add a lot of needless functions to our namespace -- and make competent programmers laugh at us :-)&lt;/p&gt;
&lt;p&gt;Better to look at the function itself (the S-expression) and extract all of the function names automatically. But how?&lt;/p&gt;
&lt;p&gt;Let's pretend we have no idea how to do this yet and just feel our way along. Of course, &lt;em&gt;I&lt;/em&gt; already know how to do this (he says in a voice that betrays his ignorance), so don't you worry about a thing. Everything will be fine (*gulp*).&lt;/p&gt;
&lt;p&gt;The best place to start may be by looking at a function's S-expression. A naive attempt might be to just look at the value of a function's name. But first, we need to define a function to look at:&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (define (f a b) (+ a b (- b a)))
&lt;strong&gt;(lambda (a b) (+ a b (- b a)))&lt;/strong&gt;
&gt; f
&lt;strong&gt;(lambda (a b) (+ a b (- b a)))&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;Look, newLISP rewards our naiveté with exactly what we want, matching our mental model of the world! Two things to notice here. The result of &lt;strong&gt;define&lt;/strong&gt; and the value of &lt;strong&gt;f&lt;/strong&gt; are identical, and this result is a lambda (a function without a name).&lt;/p&gt;
&lt;p&gt;Now let's begin our dissection of this S-expression to see if we can get at those function names.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (0 f)
&lt;strong&gt;((a b) (+ a b (- b a)))&lt;/strong&gt;
&gt; (1 f)
&lt;strong&gt;((+ a b (- b a)))&lt;/strong&gt;
&gt; &lt;em style="color:#777777"&gt;;; almost there!&lt;/em&gt;
&gt; ((1 f) 1)
&lt;strong&gt;(+ a b (- b a))&lt;/strong&gt;
&gt; &lt;em style="color:#777777"&gt;;; here we go.&lt;/em&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;Now we just need to look at the first symbol in each expression (with exceptions) to know which functions this function is using. Before we continue, we'd better get a better grip on our subject.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (set 'subj ((1 f) 1))
&lt;strong&gt;(+ a b (- b a))&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;To get the first one is easy. Just use &lt;strong&gt;first&lt;/strong&gt;!&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (first subj)
&lt;strong&gt;+&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;If all we were interested in was the first function our function calls, we'd be done. Actually, we want &lt;em&gt;all&lt;/em&gt; of the functions, so we must keep going. Iteration seems to be in order. Warning: The next few snippets contain examples of code-groping. Don't try this at work ;-)&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
(map (fn (ea) (and (list? ea) (first ea))) subj)
&lt;strong&gt;(nil nil nil -)&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;That's almost it. We don't need all those &lt;strong&gt;nil&lt;/strong&gt;s, though. Next try.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt;(filter (fn (ea) (and (list? ea) (first ea))) subj)
&lt;strong&gt;((- b a))&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;That's not exactly right, either. Maybe we should break this up into finer steps.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (filter list? subj)
&lt;strong&gt;((- b a))&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;Now we're on to it. If this were a complete example, we would have more than just one expression in our resultant list. Now for the next part:&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (map first (filter list? subj))
&lt;strong&gt;(-)&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;You know what? We're missing something here. A test to see if the first element of the list is a lambda or a primitive. How do we do that? &lt;strong&gt;lambda?&lt;/strong&gt; and &lt;strong&gt;primitive?&lt;/strong&gt;, of course.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; [cmd] 
(filter
    (fn (ea)
        (let (ea (eval ea))
        (or (lambda? ea) (primitive? ea))))
    (map
        first
        (filter list? subj)))
[/cmd]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;There we are. We're all done except for the first function in the expression, which we got by applying &lt;strong&gt;first&lt;/strong&gt;. Here we need it &lt;strong&gt;cons&lt;/strong&gt;ed to the front of our list:&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; [cmd] 
(cons
    (first subj)
    (filter
    (fn (ea)
            (let (ea (eval ea))
            (or (lambda? ea) (primitive? ea))))
        (map
            first
            (filter list? subj))))
[/cmd]
&lt;strong&gt;(+ -)&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;Wow. There it is! The functions our subject function calls. But are we really done yet? Done is something that programming never seems to be ;-) Is the first element being tested to see if it's a function? And will it work with deeply nested functions? This is the part where I expect the author to say, "I'll leave that as an excerise for you," probably because he or she is tired of writing ;-) Not me!&lt;/p&gt;
&lt;p&gt; First, let's define this as a function.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; [cmd]
(define (uses fname)
    (let (subj ((1 fname) 1))
    (cons
        (first subj)
        (filter
            (fn (ea)
                (let (ea (eval ea))
                (or (lambda? ea) (primitive? ea))))
            (map
                first
                (filter list? subj))))))
[/cmd]
&lt;strong&gt;(lambda (fname) 
    (let (subj ((1 fname) 1)) 
    (cons (first subj) (filter (lambda (ea) 
        (let (ea (eval ea)) 
            (or (lambda? ea) (primitive? ea)))) 
        (map first (filter list? subj))))))&lt;/strong&gt;
&gt; (uses f)
&lt;strong&gt;(+ -)&lt;/strong&gt;
&gt; _
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;That seems to be working. Let's try this with a deeper function.&lt;/p&gt;
&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;pre style="font-size:95%; font-family:Optima, Futura, sans-serif"&gt;
&gt; (define (deep-f a b) (+ a (- (* b a) a)))
&lt;strong&gt;(lambda (a b) (+ a (- (* b a) a)))&lt;/strong&gt;
&gt; (uses deep-f)
&lt;strong&gt;(+ -)&lt;/strong&gt;
&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/blockquote&gt;
&lt;p&gt;Just as I suspected. Anything deeper than one level, and it's ignored. Where do we go from here? I'll leave that as an exercis . . . just kidding ;-) Maybe we should conclude for today and then we can look forward to Part II of "Functions That Use and the Functions That Get Used by Other Functions"! If you would like to post solutions to the comments section, by all means do. I'll just check them against my already-conceived code (you don't really believe this, do you?), and we can pick the best one. I'm pretty sure mine won't win, though ;-)&lt;/p&gt;
&lt;p style="color:#aa0000"&gt;m i c h a e l&lt;/p&gt;
&lt;p&gt;P.S. If you detect a slight shallowing of the code in these posts, the effect is entirely acknowledged to be true. I'm looking with nostalgia at the days when I could spend an hour or more coding something up in newLISP, and that's a bad sign, indeed.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://www.blogger.com/profile/11117702685492567043" rel="nofollow"&gt;don Lucio&lt;/a&gt;&lt;/h3&gt;Here is a solution using the function 'flat' which makes it easy to acess a deep nested list:&lt;br/&gt;&lt;br/&gt;(define (uses fname) &lt;br/&gt;    (unique (filter &lt;br/&gt;               (fn (s) (primitive? (eval s))) &lt;br/&gt;               (flat fname))))
&lt;/div&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;span class="anon-comment-author"&gt;cormullion&lt;/span&gt;&lt;/h3&gt;Excellent post! I've always wondered about this area of newLISP!&lt;br/&gt;&lt;br/&gt;(The modest note at the end of your post is entirely unjustified!)
&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5342769448052333321?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5342769448052333321/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5342769448052333321' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5342769448052333321'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5342769448052333321'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/06/functions-that-use-and-functions-that.html' title='Functions That Use and the Functions That Get Used...'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-3340249859198664003</id><published>2008-04-15T12:25:00.000+01:00</published><updated>2011-02-07T12:22:55.241Z</updated><title type='text'>Under construction (again)</title><content type='html'>&lt;p&gt;This site is being rebuilt, again! Yes, I've written another blogging application in newLISP to generate these pages. It's far from finished yet, but at the moment it's looking quite promising: only about 250 lines of newLISP so far, and I hope to get it down even further.&lt;/p&gt;

&lt;p&gt;Unfortunately, there are some features I've yet to get round to doing - the most obvious being the Atom newsfeed. Soon, perhaps. &lt;/p&gt;

&lt;p&gt;I don't think I'll be enabling comments again, but the email address should be somewhere on this page so pelase write to me!&lt;/p&gt;

&lt;p&gt;I've kept the old articles from the earlier blog here, for posterity, even though some of them are no longer applicable - or just plain wrong.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3340249859198664003?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3340249859198664003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3340249859198664003' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3340249859198664003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3340249859198664003'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/04/under-construction-again.html' title='Under construction (again)'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5105721308291589522</id><published>2008-03-06T22:44:00.000Z</published><updated>2011-02-07T12:22:57.254Z</updated><title type='text'>Links</title><content type='html'>&lt;p&gt;This post contains a link to &lt;a href="http://adam.schmideg.net/"&gt;Adam's newLISP-powered web site&lt;/a&gt; (Hi Adam!) and a link to &lt;a href="http://newlisp.wordpress.com/"&gt;Alessandro's newLISP blog&lt;/a&gt; (Ciao!).&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5105721308291589522?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5105721308291589522/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5105721308291589522' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5105721308291589522'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5105721308291589522'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2008/03/links.html' title='Links'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-3863749069828430601</id><published>2007-10-01T18:04:00.000+01:00</published><updated>2011-02-07T12:24:47.387Z</updated><title type='text'>ThisService reaches version 2</title><content type='html'>&lt;p&gt;The cool utility &lt;b&gt;ThisService&lt;/b&gt; from the excellently-named &lt;a href="http://wafflesoftware.net/thisservice/"&gt;WaffleSoftware&lt;/a&gt; has been updated to version 2. It's a clever way of running scripts written in scripting languages - such as newLISP - from the Services menu (the menu that's available in every application, with a few dishonourable exceptions). It's MacOS X specific - there's probably an equivalent on other platforms.&lt;/p&gt;

&lt;p&gt;One good addition is that you can now make services that link to scripts stored somewhere sensible on your disk. Before, your script was copied into the service, but now you can keep scripts in their rightful place and yet still use them in the services you create. &lt;/p&gt;

&lt;p&gt;To complement this change, there's also a packaging option, so that scripts and services can be merged ready for distribution.&lt;/p&gt;

&lt;p&gt;The example scripts provided with this donation-ware application (written in Ruby, Perl, Python, and AppleScript - the four horsemen of Macintosh scripting) all seem to avoid reading line by line from STDIN, instead preferring to read any text input into a variable first. The word 'slurp' is used...&lt;/p&gt;

&lt;p&gt;In newLISP, a typical service might look like this. This puts a comment before every line of the selection:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; #!/usr/bin/env newlisp

 (while (read-line) 
   (push (current-line) the-text -1))

 (dolist (line the-text)
   (println {; } line))

 (exit)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As before, there will be the inevitable problems with applications that use "\r" rather than "\n" as line breaks. I think I've bored myself with that before!&lt;/p&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://waffle.wootest.net/" rel="nofollow"&gt;Jesper&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;As the writer of the Ruby, Perl and AppleScript versions (and, also, of ThisService itself - hello everyone) I just want to defend my usage of slurping. It's really easy - most of the time you want to run the whole input through a filter, and that might not work well with line-by-line or word-by-word or 512-summat-byte-block-by-block.
&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;It will confuse the least amount of people who just want to get stuff done. Peter Hosey wrote the Python scripts completely independently (I said to comment well and keep STDIN/OUT UTF-8 and that was basically it), and he too chose slurping. I'd like to believe that this confirms my suspicions. ;)&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://unbalanced-parentheses.nfshost.com" rel="nofollow"&gt;cormullion&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Hi! Slurping is cool. And it's what I used too. I just quite like the word, which is much more descriptive than the usual way of describing it!
&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;And ThisService is cool too, Jesper! If I was currently employed, I'd send you some money :-)&lt;/p&gt;&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3863749069828430601?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3863749069828430601/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3863749069828430601' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3863749069828430601'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3863749069828430601'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/10/thisservice-reaches-version-2.html' title='ThisService reaches version 2'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7679517176134650396</id><published>2007-09-27T15:59:00.000+01:00</published><updated>2011-02-07T12:24:51.008Z</updated><title type='text'>Search me</title><content type='html'>&lt;p&gt;I've been fixing a few bugs and tweaking bits of the newLISP code that publishes this page - and starting to document it too using newLISPdoc. If I've broken anything, or need to fix something that isn't working properly, please tell me.&lt;/p&gt;

&lt;p&gt;I've adjusted the search code a bit, so that searches examine most of the database. I don't think SQLite has a regular expression search tool, but it seems to work OK without one. The text you type in the box is enclosed in percent signs (the SQLite wild card character equivalent to *), so it will find partial matches in most of the fields. You can type another one in the middle of your search text if you want but it's usually not needed.&lt;/p&gt;

&lt;p&gt;The single character wild card (the underscore) can be typed into the box to search for any single character. So "l__z" will find posts and comments containing Liszt and Lutz.&lt;/p&gt;

&lt;p&gt;It's also possible to search for posts and comments by date. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; 200605
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;will find all posts and comments from May 2006 - all posts and comments are stored by their creation date and time. For some while I was puzzled by why the search found other posts from later in the year, until I realised that posts are stored in HTML, so all the links and attributes are searched as well as the text itself. Any link or reference to an earlier post will provide a match for the search, even though nothing is visible.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7679517176134650396?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7679517176134650396/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7679517176134650396' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7679517176134650396'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7679517176134650396'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/search-me.html' title='Search me'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5732966077128508450</id><published>2007-09-26T10:45:00.000+01:00</published><updated>2011-02-07T12:24:52.139Z</updated><title type='text'>A local affair</title><content type='html'>&lt;p&gt;One minor problem that I encountered when setting up this newLISP-powered site was that, on my own computer, the newLISP binary is installed in /usr/bin/, whereas on the host, it's installed in /usr/local/bin. &lt;/p&gt;

&lt;p&gt;To make life easier, you can define a /usr/local/bin/newlisp link on your own system:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; sudo ln -s /usr/local/bin/newlisp /usr/bin/newlisp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and that saves you having to change your newLISP installation when you're testing.&lt;/p&gt;

&lt;p&gt;Doing a bit of research it &lt;a href="http://hivelogic.com/narrative/articles/using_usr_local?status=301"&gt;appears&lt;/a&gt; that it's considered to be better to install packages like newLISP into /usr/local/bin rather than into the system's own /usr/bin.&lt;/p&gt;

&lt;p&gt;Perhaps a future newLISP installer might allow a choice of locations.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://artfulcode.nfshost.com" rel="nofollow"&gt;Jeff&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;You can install it wherever you like if you compile it from the source.  Just do ./configure --prefix=/usr/local.
&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;Also, instead of using #!/usr/bin/newlisp or #!/usr/local/bin/newlisp, most modern unixes have env, so you can do #!/usr/bin/env newlisp, which will ask env for the location of newlisp and then use it to execute the file.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://unbalanced-parentheses.nfshost.com" rel="nofollow"&gt;cormullion&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Cool - env works. I'll use that to avoid future problems.
&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5732966077128508450?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5732966077128508450/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5732966077128508450' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5732966077128508450'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5732966077128508450'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/local-affair.html' title='A local affair'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5579271300681705029</id><published>2007-09-25T19:13:00.000+01:00</published><updated>2011-02-07T12:24:56.368Z</updated><title type='text'>What is the LambdaPress?</title><content type='html'>&lt;p&gt;This page is produced by something which I've jokingly called (the) LambdaPress, a short newLISP script that generates web pages in a timely fashion. If you're reading this, then the 'LambdaPress' has flexed a few lambdas and shaken a few s-expressions, and managed to output a bit of marked-up text as a result.&lt;/p&gt;

&lt;p&gt;It's hard to think of names these days. Many of the basic ones have been taken, and the harder you try, the less pleasing the names become. Being devoid of inspiration, I decided to try something a bit like TypePad, WordPress, MoveableType, with a Lisp-y flavour. Since &lt;em&gt;LambdaPress&lt;/em&gt; was an &lt;a href="http://en.wikipedia.org/wiki/Googlewhack"&gt;Antegooglewhackblatt&lt;/a&gt; at the weekend, I used that as a working term. It will probably change to something more sensible.&lt;/p&gt;

&lt;p&gt;I saw &lt;a href="http://ifacethoughts.net/2007/09/19/want-to-learn-web-programming-write-a-blog-engine/"&gt;this article about writing your own blogging software&lt;/a&gt; only after I'd finished an early prototype last week. I think I agree with the basic idea, but of course I had managed to ignore most of the sensible advice and had just hacked my way through to some basic functionality. Some of the features seem too hard to do.&lt;/p&gt;

&lt;p&gt;I had originally planned on using the newLISP-wiki for the Blogger replacement. The wiki is a remarkable package, cramming a lot of functionality into a small space. But after playing with it for a while I decided to write something from scratch.&lt;/p&gt;

&lt;p&gt;Why? First, and most important, because it's fun to do things in your own way, sometimes. Second, the wiki has more features than I needed just for publishing some text. Third, I wanted to make various small changes to the wiki code, which was both difficult and undesirable. Difficult because I wasn't too sure what was going on, and undesirable because I didn't want to make a degraded copy that couldn't be upgraded when or if a new version was released.&lt;/p&gt;

&lt;p&gt;The original idea of the 'LambdaPress' (still in quotes - I'm not convinced about the name) was that the posts could be stored in an SQLite database, and the newLISP code would simply do a few queries and output the results enclosed in a few &lt;div&gt;s. That way, all sorts of things were easy, and most of the effort would be on the formatting and presentation.&lt;/p&gt;

&lt;p&gt;In the end, that's exactly how it worked. My previous blog was exported from Blogger (not that difficult, although it involved changing lots of settings and then changing them back again). It was easily imported into a database, using a short newLISP script, of course. The basic query code was simple too. The difficult bits were: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;learning about CGI&lt;/li&gt;
&lt;li&gt;getting the CSS styles to look half decent (I haven't even tried looking at the site on Windows yet)&lt;/li&gt;
&lt;li&gt;getting the permissions on the hosting computers correct (they're still not right...)&lt;/li&gt;
&lt;li&gt;getting the Atom feed to work properly (it's still not working - it keeps saying the posts are new when they're not!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this is version 0.0.1, an alpha, a work-in-progress. I hope to put the script in a downloads section soon so that you can suggest improvements.&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-5579271300681705029?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/5579271300681705029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=5579271300681705029' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5579271300681705029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/5579271300681705029'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/what-is-lambdapress.html' title='What is the LambdaPress?'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-3930100467555252370</id><published>2007-09-25T16:16:00.000+01:00</published><updated>2011-02-07T12:25:01.181Z</updated><title type='text'>Atom feed</title><content type='html'>&lt;p&gt;I've added an Atom feed for this site. It's a bit of a quick hack (like the rest of this site :-) so please let me know whether it doesn't work for you. &lt;/p&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://blog.postmaster.gr" rel="nofollow"&gt;adamo&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;It seems that Technorati is not getting it
&lt;/p&gt;&lt;p&gt;:(&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://unbalanced-parentheses.nfshost.com" rel="nofollow"&gt;cormullion&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I'm still trying to find out what Atom is, really. I'm confused by the publishing and updating dates too. (I'm new to this stuff!)&lt;/p&gt;
&lt;p&gt;This is still a work in progress!&lt;/p&gt;
&lt;p&gt;I don't think I've done anything about Technorati yet.&lt;/p&gt;&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-3930100467555252370?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/3930100467555252370/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=3930100467555252370' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3930100467555252370'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/3930100467555252370'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/atom-feed.html' title='Atom feed'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6937268993736957111</id><published>2007-09-25T01:21:00.000+01:00</published><updated>2011-02-07T12:25:02.352Z</updated><title type='text'>Moving house...</title><content type='html'>... to &lt;a href="http://unbalanced-parentheses.nfshost.com/index.cgi"&gt;Unbalanced Parentheses&lt;/a&gt;.
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6937268993736957111?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6937268993736957111/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6937268993736957111' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6937268993736957111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6937268993736957111'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/moving-house.html' title='Moving house...'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-4522045264046369547</id><published>2007-09-25T00:59:00.000+01:00</published><updated>2011-02-07T12:25:08.754Z</updated><title type='text'>Hi again!</title><content type='html'>&lt;p&gt;I've moved this blog to a new host. Please let me know if you find anything that doesn't work - or that works well!&lt;/p&gt;&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://newlisp.org" rel="nofollow"&gt;Lutz&lt;/a&gt;&lt;/h3&gt;Powered by newLISP? is 'lambda Press' based on newlisp-wiki? How did you move the pages from blogspot?

Congratulations, it looks nice.&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-4522045264046369547?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/4522045264046369547/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=4522045264046369547' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/4522045264046369547'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/4522045264046369547'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/hi-again.html' title='Hi again!'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6651147870701478206</id><published>2007-09-17T16:29:00.000+01:00</published><updated>2011-02-07T12:25:10.294Z</updated><title type='text'>Backup</title><content type='html'>Backup
&lt;p&gt;
I'm just backing up this blog. Please excuse any disruption.&lt;/p&gt;
&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://test" rel="nofollow"&gt;test&lt;/a&gt;&lt;/h3&gt;test&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6651147870701478206?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6651147870701478206/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6651147870701478206' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6651147870701478206'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6651147870701478206'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/backup_17.html' title='Backup'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-6955694254496033870</id><published>2007-09-17T13:29:00.000+01:00</published><updated>2011-02-07T12:13:56.152Z</updated><title type='text'>Backup</title><content type='html'>I'm just backing up this blog. Please excuse any disruption.
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-6955694254496033870?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/6955694254496033870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=6955694254496033870' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6955694254496033870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/6955694254496033870'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/backup.html' title='Backup'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-7476441939940558958</id><published>2007-09-14T19:27:00.000+01:00</published><updated>2011-02-07T12:25:12.758Z</updated><title type='text'>Gotcha</title><content type='html'>&lt;p&gt;Those captcha things are everywhere these days. You know, the little pictures of random letters that you have to identify before you can post a comment or display a web page. There's even one on this blog, courtesy of Google/Blogger - if you want to comment on these posts of mine, you'll be expected to identify terribly tortured type and gruesomely garbled glyphs before your thoughts can be published.&lt;/p&gt;
&lt;p&gt;I didn't realise that 'CAPTCHA' is an acronym - it stands for Completely Automated Public Turing test to tell Computers and Humans Apart (a struggle, but they made it work, just). It's even been trademarked by Carnegie-Mellon University.&lt;/p&gt;
&lt;p&gt;To be honest, I'm not a big fan of the typographical captcha. I can understand their purpose, and they're acceptable as part of the ongoing fight against spammers. But I don't feel good when I'm typing nonsense into little boxes. Letters can be beautiful things that help us communicate, and these captchas are ugly reminders of an ugly reality. The task of responding to a captcha is the McJob of the internet browsing world - a sad and mindless task that it usually isn't worth teaching computers to do.&lt;/p&gt;
&lt;p&gt;(It turns out that many of these captchas are now crackable by dumb computers and their clever programmers: see &lt;a href='http://sam.zoy.org/pwntcha/'&gt;here&lt;/a&gt; and &lt;a href='http://www.brains-n-brawn.com/default.aspx?vDir=aicaptcha'&gt;here&lt;/a&gt;, for example.)&lt;/p&gt;
&lt;p&gt;Another problem with captchas is that they're hard for people who have vision problems. It's sad that computers can sometimes solve things that they're not supposed to while humans can't sometimes solve things that they've been made to.&lt;/p&gt;
&lt;p&gt;Unfortunately, it's hard to think of anything more pleasant that offers any kind of resistance to automated attacks. Given enough time, most simple text-based captchas can be overcome, particularly if the rewards are sufficient. Some spammers presumably spend days cracking challenging Yahoo and Hotmail captchas so that they can send millions of spam emails from temporary accounts.&lt;/p&gt;
&lt;p&gt;But something is better than nothing, and it's fun to try and devise a simple question/answer method that's just challenging enough to deter the idle spammer.&lt;/p&gt;
&lt;p&gt;My first attempt, for verifying comments posted to a newLISP wiki, was fun, but probably not very effective as a security measure. Here's the basic idea, with a small sample data set:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(set 'data '(
  ({apple} {pear} {cherry} {mango} {peach} {lime} {strawberry} {kumquat})
  ({onion} {carrot} {potato} {bean} {pepper}  {cucumber})
  ({red} {ultramarine} {pink} {green}  {blue} {turquoise})
  ({bach} {beethoven} {zappa} {liszt} {mozart} {lennon} {brahms})
  ({london} {paris} {chicago} {rome} {athens} {moscow} {beijing})
  ({oxygen} {lead} {plutonium} {calcium} {cobalt} {strontium})
  ({elephant} {mouse} {lion} {toad} {frog} {slug})
  ({ls} {cat} {vi} {ps} {echo} {man} {ed} {diff} {troff})))
(seed (date-value))
(define (generate)
  (map set '(odd-one-list others-list) (randomize data))
  (set 'odd-one (first (randomize odd-one-list)))
  (map set '(other1 other2 other3) (randomize others-list))
  (join (randomize (list odd-one other1 other2 other3) ) { }))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The question is something like "what's the odd one out?", followed by the result of &lt;em&gt;(generate)&lt;/em&gt;. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  zappa beethoven lennon cobalt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The user then has to type the odd one out before the comment is posted. I liked the strange poetry that this generated:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat vi oxygen ls
athens moscow lion chicago
rome oxygen lead calcium
lime cherry ultramarine pear
bach ultramarine zappa mozart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it wouldn't be an unpleasant task to answer such a simple question. I also like the idea that you could adjust the difficulty level by changing the data lists, not just by adding  more obscure elements from the periodic table and some more unusual Unix commands, but by adding lists of newLISP functions, or famous brands of whisky. It's tempting, but probably unjustified, to think that spammers are stupid and would have trouble answering questions like these.&lt;/p&gt;
&lt;p&gt;A subtle problem to avoid is that some words belong to two categories - &lt;em&gt;cat&lt;/em&gt;, for example. But a more serious deficiency is that, if the question is multiple choice, and the &lt;em&gt;n&lt;/em&gt; options are easily identified, the spammer has a 1 in &lt;em&gt;n&lt;/em&gt; chance of getting the solution just by choosing an answer at random. So it's a good idea to make sure that the right answer isn't on display at all. Finally, though, the most serious drawback is that it would be easy to solve these simple questions by writing a short program, assuming that there's not already an odd-one-out server. (Any takers? - it'd be an interesting task, given that the data lists themselves, and the complete list of categories, would not be immediately apparent to the would-be malefactor and would emerge only after repeated attempts.)&lt;/p&gt;
&lt;p&gt;So I've started thinking about alternatives where the answer isn't visible on the web page. No luck yet - arithmetic sequences with the last number missing are a bit dull!&lt;/p&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-7476441939940558958?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/7476441939940558958/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=7476441939940558958' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7476441939940558958'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/7476441939940558958'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/gotcha.html' title='Gotcha'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-2367331041866544435</id><published>2007-09-14T10:27:00.000+01:00</published><updated>2011-02-07T12:25:16.950Z</updated><title type='text'>Gotcha</title><content type='html'>&lt;p&gt;Those captcha things are everywhere these days. You know, the little pictures of random letters that you have to identify before you can post a comment or display a web page. There's even one on this blog, courtesy of Google/Blogger - if you want to comment on these posts of mine, you'll be expected to identify terribly tortured type and gruesomely garbled glyphs before your thoughts can be published.&lt;/p&gt;
&lt;p&gt;I didn't realise that 'CAPTCHA' is an acronym - it stands for Completely Automated Public Turing test to tell Computers and Humans Apart (a struggle, but they made it work, just). It's even been trademarked by Carnegie-Mellon University.&lt;/p&gt;
&lt;p&gt;To be honest, I'm not a big fan of the typographical captcha. I can understand their purpose, and they're acceptable as part of the ongoing fight against spammers. But I don't feel good when I'm typing nonsense into little boxes. Letters can be beautiful things that help us communicate, and these captchas are ugly reminders of an ugly reality. The task of responding to a captcha is the McJob of the internet browsing world - a sad and mindless task that it usually isn't worth teaching computers to do.&lt;/p&gt;
&lt;p&gt;(It turns out that many of these captchas are now crackable by dumb computers and their clever programmers: see &lt;a href='http://sam.zoy.org/pwntcha/'&gt;here&lt;/a&gt; and &lt;a href='http://www.brains-n-brawn.com/default.aspx?vDir=aicaptcha'&gt;here&lt;/a&gt;, for example.)&lt;/p&gt;
&lt;p&gt;Another problem with captchas is that they're hard for people who have vision problems. It's sad that computers can sometimes solve things that they're not supposed to while humans can't sometimes solve things that they've been made to.&lt;/p&gt;
&lt;p&gt;Unfortunately, it's hard to think of anything more pleasant that offers any kind of resistance to automated attacks. Given enough time, most simple text-based captchas can be overcome, particularly if the rewards are sufficient. Some spammers presumably spend days cracking challenging Yahoo and Hotmail captchas so that they can send millions of spam emails from temporary accounts.&lt;/p&gt;
&lt;p&gt;But something is better than nothing, and it's fun to try and devise a simple question/answer method that's just challenging enough to deter the idle spammer.&lt;/p&gt;
&lt;p&gt;My first attempt, for verifying comments posted to a newLISP wiki, was fun, but probably not very effective as a security measure. Here's the basic idea, with a small sample data set:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(set 'data '(
  ({apple} {pear} {cherry} {mango} {peach} {lime} {strawberry} {kumquat})
  ({onion} {carrot} {potato} {bean} {pepper}  {cucumber})
  ({red} {ultramarine} {pink} {green}  {blue} {turquoise})
  ({bach} {beethoven} {zappa} {liszt} {mozart} {lennon} {brahms})
  ({london} {paris} {chicago} {rome} {athens} {moscow} {beijing})
  ({oxygen} {lead} {plutonium} {calcium} {cobalt} {strontium})
  ({elephant} {mouse} {lion} {toad} {frog} {slug})
  ({ls} {cat} {vi} {ps} {echo} {man} {ed} {diff} {troff})))
(seed (date-value))
(define (generate)
  (map set '(odd-one-list others-list) (randomize data))
  (set 'odd-one (first (randomize odd-one-list)))
  (map set '(other1 other2 other3) (randomize others-list))
  (join (randomize (list odd-one other1 other2 other3) ) { }))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The question is something like "what's the odd one out?", followed by the result of &lt;em&gt;(generate)&lt;/em&gt;. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  zappa beethoven lennon cobalt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The user then has to type the odd one out before the comment is posted. I liked the strange poetry that this generated:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat vi oxygen ls
athens moscow lion chicago
rome oxygen lead calcium
lime cherry ultramarine pear
bach ultramarine zappa mozart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it wouldn't be an unpleasant task to answer such a simple question. I also like the idea that you could adjust the difficulty level by changing the data lists, not just by adding  more obscure elements from the periodic table and some more unusual Unix commands, but by adding lists of newLISP functions, or famous brands of whisky. It's tempting, but probably unjustified, to think that spammers are stupid and would have trouble answering questions like these.&lt;/p&gt;
&lt;p&gt;A subtle problem to avoid is that some words belong to two categories - &lt;em&gt;cat&lt;/em&gt;, for example. But a more serious deficiency is that, if the question is multiple choice, and the &lt;em&gt;n&lt;/em&gt; options are easily identified, the spammer has a 1 in &lt;em&gt;n&lt;/em&gt; chance of getting the solution just by choosing an answer at random. So it's a good idea to make sure that the right answer isn't on display at all. Finally, though, the most serious drawback is that it would be easy to solve these simple questions by writing a short program, assuming that there's not already an odd-one-out server. (Any takers? - it'd be an interesting task, given that the data lists themselves, and the complete list of categories, would not be immediately apparent to the would-be malefactor and would emerge only after repeated attempts.)&lt;/p&gt;
&lt;p&gt;So I've started thinking about alternatives where the answer isn't visible on the web page. No luck yet - arithmetic sequences with the last number missing are a bit dull!&lt;/p&gt;
  
&lt;div&gt;&lt;h3&gt;Comment from &lt;a href="http://www.blogger.com/profile/10648428084141856772" rel="nofollow"&gt;newdep&lt;/a&gt;&lt;/h3&gt;What is the capital city of G.B.?&lt;br/&gt;or&lt;br/&gt;1234 * 12345 = 344523445?&lt;br/&gt;or&lt;br/&gt;How many colors do you see here?&lt;br/&gt;or&lt;br/&gt;what is the current GMT?...&lt;br/&gt;&lt;br/&gt;its all there to be published... a little&lt;br/&gt;harder to knack..but its saver then whats avialable now...&lt;br/&gt;&lt;br/&gt;...intresting topic to think about ;-)&lt;br/&gt;Norman.
&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/19684405-2367331041866544435?l=newlisper.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://newlisper.blogspot.com/feeds/2367331041866544435/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=19684405&amp;postID=2367331041866544435' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2367331041866544435'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/19684405/posts/default/2367331041866544435'/><link rel='alternate' type='text/html' href='http://newlisper.blogspot.com/2007/09/gotcha_14.html' title='Gotcha'/><author><name>cormullion</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-19684405.post-5054952642405411115</id><published>2007-08-25T16:29:00.000+01:00</published><updated>2011-02-07T12:25:05.710Z</updated><title type='text'>Make a Mac app</title><content type='html'>&lt;p&gt;If you've written some handy applications using newLISP-GS, you might want to make them double-clickable - or runnable - so that they behave like other MacOS X apps. You can do this using a handy little utility called &lt;a href="http://sveinbjorn.org/platypus"&gt;Platypus&lt;/a&gt;. Just switch to the Finder, drag your newLISP script over to the Platypus window (into the "Script Path" field) and click Create. Your script is now an application!&lt;/p&gt;

&lt;p&gt;Of course, it won't run on another Mac that doesn't have a newLISP 9.2 installation already present: it's just a wrapper (or bundle), rather than a stand-alone app. But it means that you can treat your application in the same way as - or even better than - any other app, such as, say, Microsoft Word. Put it in the Dock, select it using Quicksilver or Launchbar, whatever. You don't have to open a terminal window or an editor to launch it.&lt;/p&gt;

&lt;p&gt;I've yet to experiment with all the options (the Run in Background looks like it will launch the wrapper application in the background, leaving just the newLISP-GS application visible), but it's worth noting that you can drag an image to the icon well. This puts a pretty icon onto your new application. I could use the excellent newLISP dragonfly (newlisp128.png) which lurks inside the newLISP-GS distribution, but a special 'application built with newLISP-GS' icon would be worth designing. Until then, a picture of the species Cordulegaster Obliqua (from &lt;a href="http://www.dragonflies.org"&gt;dragonflies.org&lt;/a&gt;) resting on some unfinished newLISP documentation will suffice:&lt;/p&gt;

&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K_MEfdCIi5o/RtA-vR1U_DI/AAAAAAAAAA0/GPC84q9liYU/s1600-h/Picture+1.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_K_MEfdCIi5o/RtA-vR1U_DI/AAAAAAAAAA0/GPC84q9liYU/s400/Picture+1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5102647359772621874" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a Finder window it looks a bit too fiddly, perhaps?&lt;/p&gt;

&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_K_MEfdCIi5o/RtA89R1U_CI/AAAAAAAAAAs/Ig2MFpwL76o/s1600-h/desktop.png"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_K_MEfdCIi5o
