Update: Lisp for the Web goes book
Lisp for the Web
by Adam Petersen, April 2008
With his essay Beating the Averages , Paul Graham told the story of how his web start-up Viaweb outperformed its competitors by using Lisp. Lisp? Did I parse that correctly? That ancient language with all those scary parentheses? Yes, indeed! And with the goal of identifying its strengths and what they can do for us, I'll put Lisp to work developing a web application. In the process we'll find out how a 50 years old language can be so well-suited for modern web development and yes, it's related to all those parentheses.
What to expect
Starting from scratch, we'll develop a three-tier web application. I'll show how to:
- develop a small, embedded domain specific language tailored for my application,
- extend the typical development cycle by modifying code in a running system and execute code during compilation,
- and finally migrate from data structures in memory to persistent objects using a third party database.
I'll do this in a live system transparent to the users of the application. Because Lisp is so high-level, I'll be able to achieve everything in just around 70 lines of code.
This article will not teach you Common Lisp (for that purpose I recommend Practical Common Lisp ). Instead, I'll give a short overview of the language and try to explain the concepts as I introduce them, just enough to follow the code. The idea is to convey a feeling of how it is to develop in Lisp rather than focusing on the details.
The Lisp story
Lisp is actually a family of languages discovered by John McCarthy 50 years ago. The characteristic of Lisp is that Lisp code is made out of Lisp data structures with the practical implication that it is not only natural, but also highly effective to write programs that write programs. This feature has allowed Lisp to adapt over the years. For example, as object-oriented programming became popular, powerful object systems could be implemented in Lisp as libraries without any change to the core language. Later, the same proved to be true for aspect-oriented programming.
This idea is not only applicable to whole paradigms of programming. Its true strength lays in solving everyday problems. With Lisp, it's straightforward to build-up a domain specific language allowing us to program as close to the problem domain as our imagination allows. I'll illustrate the concept soon, but before we kick-off, let's look closer at the syntax of Lisp.
Crash course in Lisp
What Graham used for Viaweb was Common Lisp, an ANSI standardized language, which we'll use in this article too (the other main contender is Scheme, which is considered cleaner and more elegant, but with a much smaller library).
Common Lisp is a high-level interactive language that may be either interpreted or compiled. You interact with Lisp through its top-level . The top-level is basically a prompt. On my system it looks like this:
Through the top-level, we can enter expressions and see the results (the values returned by the top-level are highlighted ):
CL-USER>(+ 1 2 3) 6
As we see in the example, Lisp uses a prefix notation. A parenthesized expression is referred to as a form . When fed a form such as (+ 1 2 3) , Lisp generally treats the first element (+) as a function and the rest as arguments. The arguments are evaluated from left to right and may themselves be function calls:
CL-USER>(+ 1 2 (/ 6 2)) 6
We can define our own functions with defun:
CL-USER>(defun say-hello (to) (format t "Hello, ~a" to))
Here we're defining a function say-hello , taking one argument: to . The format function is used to print a greeting and resembles a printf on steroids. Its first argument is the output stream and here we're using t as a shorthand for standard output. The second argument is a string, which in our case contains an embedded directive ~a instructing format to consume one argument and output it in human-readable form. We can call our function like this:
CL-USER>(say-hello "ACCU") Hello, ACCU NIL
The first line is the side-effect, printing "Hello, ACCU" and NIL is the return value from our function. By default, Common Lisp returns the value of the last expression. From here we can redefine say-hello to return its greeting instead:
CL-USER>(defun say-hello (to) (format nil "Hello, ~a" to))
With nil as its destination, format simply returns its resulting string:
CL-USER>(say-hello "ACCU") "Hello, ACCU"
Now we've gotten rid of the side-effect. Programming without side-effects is in the vein of functional programming, one of the paradigms supported by Lisp. Lisp is also dynamically typed. Thus, we can feed our function a number instead:
CL-USER>(say-hello 42) "Hello, 42"
In Lisp, functions are first-class citizens. That means, we can create them just like any other object and we can pass them as arguments to other functions. Such functions taking functions as arguments are called higher-order functions . One example is mapcar . mapcar takes a function as its first argument and applies it subsequently to the elements of one or more given lists:
CL-USER>(mapcar #'say-hello (list "ACCU" 42 "Adam")) ("Hello, ACCU" "Hello, 42" "Hello, Adam")
The funny #' is just a shortcut for getting at the function object. As you see above, mapcar collects the result of each function call into a list, which is its return value. This return value may of course serve as argument to yet another function:
CL-USER>(sort (mapcar #'say-hello (list "ACCU" 42 "Adam")) #'string-lessp) ("Hello, 42" "Hello, ACCU" "Hello, Adam")
Lisp itself isn't hard, although it may take some time to wrap ones mindset around the functional style of programming. As you see, Lisp expressions are best read inside-out. But the real secret to understanding Lisp syntax is to realize that the language doesn't have one; what we've been entering above is basically parse-trees, generated by compilers in other languages. And, as we'll see soon, exactly this feature makes it suitable for metaprogramming.
The Brothers are History
Remember the hot gaming discussions 20 years ago? "Giana Sisters" really was way better than "Super Mario Bros", wasn't it? We'll delegate the question to the wise crowd by developing a web application. Our web application will allow users to add and vote for their favourite retro games. A screenshot of the end result is provided in Figure 1 below.
From now on, I start to persist my Lisp code in textfiles instead of just entering expressions into the top-level. Further, I define a package for my code. Packages are similar to namespaces in C++ or Java's packages and helps to prevent name collisions (the main distinction is that packages in Common Lisp are first-class objects).
(defpackage :retro-games (:use :cl :cl-who :hunchentoot :parenscript))
The new package is named :retro-games and I also specify other packages that we'll use initially:
- CL is Common Lisp's standard package containing the whole language.
- CL-WHO is a library for converting Lisp expressions into XHTML.
- Hunchentoot is a web-server, written in Common Lisp itself, and provides a toolkit for building dynamic web sites.
With my package definition in place, I'll put the rest of the code inside it by switching to the :retro-games package:
Most top levels indicate the current package in their prompt. On my system the prompt now looks like this:
With the package in place, we can return to the problem. It seems to require some representation of a game and I'll choose to abstract it as a class:
(defclass game () ((name :initarg :name) (votes :initform 0)))
The expression above defines the class game without any user-specified superclasses, hence the empty list () as second argument. A game has two slots (slots are similar to attributes or members in other languages): a name and the number of accumulated votes . To create a game object I invoke make-instance and passes it the name of the class to instantiate:
RETRO-GAMES>(setf many-lost-hours (make-instance 'game :name "Tetris")) #<GAME @ #x7213da32>
Because I specified an initial argument in my definition of the name slot, I can pass this argument directly and initialize that slot to "Tetris". The votes slot doesn't have an initial argument. Instead I specify the code I want to run during instantiation to compute its initial value through :initform . In this case the code is trivial, as I only want to initialize the number of votes to zero. Further, I use setf to assign the object created by make-instance to the variable many-lost-hours.
Now that we got an instance of game we would like to do something with it. We could of course write code ourselves to access the slots. However, there's a more lispy way; defclass provides the possibility to automatically generate accessor functions for our slots:
(defclass game () ((name :reader name :initarg :name) (votes :accessor votes :initform 0)))
The option :reader in the name slot will automatically create a read function and the option :accessor used for the votes slot will create both read and write functions. Lisp is pleasantly uniform in its syntax and these generated functions are invoked just like any other function:
RETRO-GAMES>(name many-lost-hours) "Tetris" RETRO-GAMES>(votes many-lost-hours) 0 RETRO-GAMES>(incf (votes many-lost-hours)) 1 RETRO-GAMES>(votes many-lost-hours) 1
The only new function here is incf , which when given one argument increases its value by one. We can encapsulate this mechanism in a method used to vote for the given game:
(defmethod vote-for (user-selected-game) (incf (votes user-selected-game)))
The top-level allows us to immediately try it out and vote for Tetris:
RETRO-GAMES>(votes many-lost-hours) 1 RETRO-GAMES>(vote-for many-lost-hours) 2 RETRO-GAMES>(votes many-lost-hours) 2
A prototypic back end
Before we can jump into the joys of generating web pages, we need a back end for our application. Because Lisp makes it so easy to modify existing applications, it's common to start out really simple and let the design evolve as we learn more about the problem we're trying to solve. Thus, I'll start by using a list in memory as a simple, non-persistent storage.
(defvar *games* '())
The expression above defines and initializes the global variable (actually the Lisp term is special variable) *games* to an empty list. The asterisks aren't part of the syntax; it's just a naming convention for globals. Lists may not be the most efficient data structure for all problems, but Common Lisp has great support for lists and they are easy to work with. Later we'll change to a real database and, with that in mind, I encapsulate the access to *games* in some small functions:
(defun game-from-name (name) (find name *games* :test #'string-equal :key #'name))
Our first function game-from-name is implemented in terms of find . find takes an item and a sequence. Because we're comparing strings I tell find to use the function string-equal for comparison (remember, #' is a short cut to refer to a function). I also specify the key to compare. In this case, we're interested in comparing the value returned by the name function on each game object.
If there's no match find returns NIL , which evaluates to false in a boolean context. That means we can reuse game-from-name when we want to know if a game is stored in the *games* list. However, we want to be clear with our intent:
(defun game-stored? (game-name) (game-from-name game-name))
As illustrated in Figure 1, we want to present the games sorted on popularity. Using Common Lisp's sort function this is pretty straightforward; we only have to take care, because for efficiency reasons sort is destructive. That is, sort is allowed to modify its argument. We can preserve our *games* list by passing a copy to sort . I tell sort to return a list sorted in descending order based on the value returned by the votes function invoked on each game:
(defun games () (sort (copy-list *games*) #'> :key #'votes))
So far the queries. Let's define one more utility for actually adding games to our storage:
(defun add-game (name) (unless (game-stored? name) (push (make-instance 'game :name name) *games*)))
push is a modifying operation and it prepends the game instantiated by make-instance to the *games* list. Let's try it all out at the top level.
RETRO-GAMES>(games) NIL RETRO-GAMES>(add-game "Tetris") (#<GAME @ #x71b943c2>) RETRO-GAMES>(game-from-name "Tetris") #<GAME @ #x71b943c2> RETRO-GAMES>(add-game "Tetris") NIL RETRO-GAMES>(games) (#<GAME @ #x71b943c2>) RETRO-GAMES>(mapcar #'name (games)) ("Tetris")
The values returned to the top level may not look too informative. It's basically the printed representation of a game object. Common Lisp allows us to customize how an object shall be printed, but we will not go into the details. Instead, with this prototypic back end in place, we're prepared to enter the web.
Generating HTML dynamically
The first step in designing an embedded domain specific language is to find a Lisp representation of the target language. For HTML this is really simple as both HTML and Lisp are represented in tree structures, although Lisp is less verbose. Here's an example using the CL-WHO library:
(with-html-output (*standard-output* nil :indent t) (:html (:head (:title "Test page")) (:body (:p "CL-WHO is really easy to use"))))
This code will expand into the following HTML, which is outputted to *standard-output* :
<html> <head> <title>Test page </title> </head> <body> <p> CL-WHO is really easy to use </p> </body> </html>
CL-WHO also allows us to embed Lisp expressions, setting the scene for dynamic web pages.
Macros: Fighting the evils of code duplication
Although CL-WHO does provide a tighter representation than raw HTML we're still facing the potential risk of code duplication; the html, head, and body tags form a pattern that will recur on all pages. And it'll only get worse as we start to write strict and validating XHTML 1.0, where we have to include more tags and attributes and, of course, start every page with that funny DOCTYPE line.
Further, if you look at Figure 1 you'll notice that the retro games page has a header with a picture of that lovely Commodore (photo by Bill Bertram - thanks!) and a strap line. I want to be able to define that header once and have all my pages using it automatically. The problem screams for a suitable abstraction and this is where Lisp differs from other languages. In Lisp, we can actually take on the role of a language designer and extend the language with our own syntax. The feature that allows this is macros. Syntactically, macros look like functions, but are entirely different beasts. Sure, just like functions macros take arguments. The difference is that the arguments to macros are source code, because macros are used by the compiler to generate code.
Macros can be a conceptual challenge as they erase the line between compile time and runtime. What macros do are expanding themselves into code that are actually compiled. In their expansion macros have access to the whole language, including other macros, and may call functions, create objects, etc.
So, let's put this amazing macro mechanism to work by defining a new syntactic construct, the standard-page . A standard-page will abstract away all XHTML boiler plate code and automatically generate the heading on each page. The macro will take two arguments. The first is the title of the page and the second the code defining the body of the actual web-page. Here's a simple usage example:
(standard-page (:title "Retro Games") (:h1 "Top Retro Games") (:p "We'll write the code later..."))
Much of the macro will be straightforward CL-WHO constructs. Using the backquote syntax(the ` character), we can specify a template for the code we want to generate:
(defmacro standard-page ((&key title) &body body) `(with-html-output-to-string (*standard-output* nil :prologue t :indent t) (:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en" (:head (:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8") (:title ,title) (:link :type "text/css" :rel "stylesheet" :href "/retro.css")) (:body (:div :id "header" ; Retro games header (:img :src "/logo.jpg" :alt "Commodore 64" :class "logo") (:span :class "strapline" "Vote on your favourite Retro Game")) ,@body))))
Within the backquoted expression we can use , (comma) to evaluate an argument and ,@ (comma-at) to evaluate and splice a list argument. Remember, the arguments to a macro are code. In this example the first argument title is bound to "Retro Games" and the second argument body contains the :h1 and :p expressions wrapped-up in a list. In the macro definition, the code bound to these arguments is simply inserted on the proper places in our backquoted template code.
The power we get from macros become evident as we look at the generated code. The three lines in the usage example above expands into this (note that Lisp symbols are case-insensitive and thus usually presented in uppercase):
(WITH-HTML-OUTPUT-TO-STRING (*STANDARD-OUTPUT* NIL :PROLOGUE T :INDENT T) (:HTML :XMLNS "http://www.w3.org/1999/xhtml" :|XML:LANG| "en" :LANG "en" (:HEAD (:META :HTTP-EQUIV "Content-Type" :CONTENT "text/html;charset=utf-8") (:TITLE "Retro Games") (:LINK :TYPE "text/css" :REL "stylesheet" :HREF "/retro.css")) (:BODY (:DIV :ID "header" (:IMG :SRC "/logo.jpg" :ALT "Commodore 64" :CLASS "logo") (:SPAN :CLASS "strapline" "Vote on your favourite Retro Game")) (:H1 "Top Retro Games") (:P "We'll write the code later..."))))
This is a big win; all this is code that we don't have to write. Now that we have a concise way to express web-pages with a uniform look, it's time to introduce Hunchentoot.
More than an opera
Named after a Zappa sci-fi opera, Edi Weitz's Hunchentoot is a full featured web-server written in Common Lisp. To launch Hunchentoot, we just invoke its start-server function:
RETRO-GAMES>(start-server :port 8080)
start-server supports several arguments, but we're only interested in specifying a port other than the default port 80. And that's it - the server's up and running. We can test it by pointing a web browser to http://localhost:8080/, which should display Hunchentoot's default page. To actually publish something, we have to provide Hunchentoot with a handler. In Hunchentoot all requests are dynamically dispatched to an associated handler and the framework contains several functions for defining dispatchers. The code below creates a dispatcher and adds it to Hunchentoot's dispatch table:
(push (create-prefix-dispatcher "/retro-games.htm" 'retro-games) *dispatch-table*)
The dispatcher will invoke the function, retro-games , whenever an URI request starts with /retro-games.htm . Now we just have to define the retro-games function that generates the HTML:
(defun retro-games () (standard-page (:title "Retro Games") (:h1 "Top Retro Games") (:p "We'll write the code later...")))
That's it - the retro games page is online. But I wouldn't be quick to celebrate; while we took care to abstract away repetitive patterns in standard-page , we've just run into another more subtle form of duplication. The problem is that every time we want to create a new page we have to explicitly create a dispatcher for our handle. Wouldn't it be nice if Lisp could do that automatically for us? Basically I want to define a function like this:
(define-url-fn (retro-games) (standard-page (:title "Retro Games") (:h1 "Top Retro Games") (:p "We'll write the code later...")))
and have Lisp to create a handler, associate it with a dispatcher and put it in the dispatch table as I compile the code. Guess what, using macros the syntax is ours. All we have to do is reformulate our wishes in a defmacro :
(defmacro define-url-fn ((name) &body body) `(progn (defun ,name () ,@body) (push (create-prefix-dispatcher ,(format nil "/~(~a~).htm" name) ',name) *dispatch-table*)))
Now our "wish code" above actually compiles and generates the following Lisp code (macro arguments highlighted):
(PROGN (DEFUN RETRO-GAMES () (STANDARD-PAGE (:TITLE "Retro Games") (:H1 "Top Retro Games") (:P "We'll write the code later..."))) (PUSH (CREATE-PREFIX-DISPATCHER "/retro-games.htm" 'RETRO-GAMES) *DISPATCH-TABLE*))
There's a few interesting things about this macro:
- It illustrates that macros can take other macros as arguments. The Lisp compiler will continue to expand the macros and standard-page will be expanded too, writing even more code for us.
- Macros may execute code as they expand. The prefix string "/retro-games.htm" is assembled with format during macro expansion time. By using comma, I evaluate the form and there's no trace of it in the generated code - just the resulting string.
- A macro must expand into a single form, but we actually need two forms; a function definition and the code for creating a dispatcher. progn solves this problem by wrapping the forms in a single form and then evaluating them in order.
Putting it together
Phew, that was a lot of Lisp in a short time. But using the abstractions we've created, we're able to throw together the application in no time. Let's code out the main page as it looks in Figure 1 above:
(define-url-fn (retro-games) (standard-page (:title "Top Retro Games") (:h1 "Vote on your all time favourite retro games!") (:p "Missing a game? Make it available for votes " (:a :href "new-game.htm" "here")) (:h2 "Current stand") (:div :id "chart" ; For CSS styling of links (:ol (dolist (game (games)) (htm (:li (:a :href (format nil "vote.htm?name=~a" (name game)) "Vote!") (fmt "~A with ~d votes" (name game) (votes game)))))))))
Here we utilize our freshly developed embedded domain specific language for defining URL functions ( define-url-fn ) and creating standard-pages . The following lines are straightforward XHTML generation, including a link to new-game.htm; a page we haven't specified yet. We will use some CSS to style the Vote! links to look and feel like buttons, which is why I wrap the list in a div -tag.
The first embedded Lisp code is dolist . We use it to create each game item in the ordered HTML list. dolist works by iterating over a list, in this case the return value from the games -function, subsequently binding each element to the game variable. Using format and the access methods on the game object, I assemble the presentation and a destination for Vote!. Here's some sample HTML output from one session:
<div id='chart'> <ol> <li> <a href='vote.htm?name=Super Mario Bros'>Vote!</a> Super Mario Bros with 12 votes </li> <li> <a href='vote.htm?name=Last Ninja'>Vote!</a> Last Ninja with 11 votes </li> </ol> </div>
As the user presses Vote! we'll get a request for vote.htm with the name of the game attached as a query parameter. Hunchentoot provides a parameter function that, just as you might expect, returns the value of the parameter named by the following string. We pass this value to our back end abstraction game-from-name and binds the result to a local variable with let :
(define-url-fn (vote) (let ((game (game-from-name (parameter "name")))) (if game (vote-for game)) (redirect "/retro-games.htm")))
After a vote-for the requested game, Hunchentoot's redirect function takes the client to the updated chart.
Now when we're able to vote we need some games to vote-for . In the code for the retro-games page above, I included a link to new-game.htm. That page is displayed in Figure 2. Basically it contains a HTML form with a text input for the game name:
(define-url-fn (new-game) (standard-page (:title "Add a new game") (:h1 "Add a new game to the chart") (:form :action "/game-added.htm" :method "post" (:p "What is the name of the game?" (:br) (:input :type "text" :name "name" :class "txt")) (:p (:input :type "submit" :value "Add" :class "btn")))))
As the user submits the form, its data is sent to game-added.htm:
(define-url-fn (game-added) (let ((name (parameter "name"))) (unless (or (null name) (zerop (length name))) (add-game name)) (redirect "/retro-games.htm")))
The first line in our URL function should look familiar; just as in our vote function, we fetch the value of the name parameter and binds it to a local variable ( name ). Here we have to guard against an empty name. After all, there's nothing forcing the user to write anything into the field before submitting the form (we'll see in a minute how to add client-side validation). If we get a valid name, we add it to our database through the add-game function.
(:form :action "/game-added.htm" :method "post" :onsubmit (ps-inline (when (= name.value "") (alert "Please enter a name.") (return false)))
Initially we kind of ducked the problem with persistence. To get things up and running as quickly as possible, we used a simple list in memory as "database". That's fine for prototyping but we still want to persist all added games in case we shutdown the server. Further, there are some potential threading issues with the current design. Hunchentoot is multithreaded and requests may come in different threads. We can solve all that by migrating to a thread-safe database. And with Lisp, design decisions like that are only a macro away; please meet Elephant!
Elephant is a wickedly smart persistent object protocol and database. To actually store things on disk, Elephant supports several back ends such as PostGres and SqlLite. In this example I'll use Berkeley DB, simply because it has the best performance with Elephant.
The first step is to open a store controller, which serves as a bridge between Lisp and the back end:
(open-store '(:BDB "/home/adam/temp/gamedb/"))
Here I just specify that we're using Berkely DB ( :BDB ) and give a directory for the database files. Now, let's make some persistent objects. Have a look at our current game class again:
(defclass game () ((name :reader name :initarg :name) (votes :accessor votes :initform 0)))
Elephant provides a convenient defpclass macro that creates persistent classes. The defpclass usage looks very similar to Common Lisp's defclass , but it adds some new features; we'll use :index to specify that we want our slots to be retrievable by slot values. I also add an initial argument to votes , which I use later when transforming our old games into this persistent class:
(defpclass persistent-game () ((name :reader name :initarg :name :index t) (votes :accessor votes :initarg :votes :initform 0 :index t)))
The Elephant abstraction is really clean; persistent objects are created just like any other object:
RETRO-GAMES>(make-instance 'persistent-game :name "Winter Games") #<PERSISTENT-GAME oid:100>
Elephant comes with a set of functions for easy retrieval. If we want all instances of our persistent-game class, it's a simple as this:
RETRO-GAMES>(get-instances-by-class 'persistent-game) (#<PERSISTENT-GAME oid:100>)
We can of course keep a reference to the returned list or, because we know we just instantiated a persistent-game , call a method on it directly:
RETRO-GAMES>(name (first (get-instances-by-class 'persistent-game))) "Winter Games"
We took care earlier to encapsulate the access to the back end and that pays off now. We just have to change those functions to use the Elephant API instead of working with our *games* list. The query functions are quit simple; because we indexed our name slot, we can use get-instance-by-value to get the matching persistent object:
(defun game-from-name (name) (get-instance-by-value 'persistent-game 'name name))
Just like our initial implementation using find , get-instance-by-value returns NIL in case no object with the given name is stored. That means that we can keep game-stored? exactly as it is without any changes. But what about adding a new game? Well, we no longer need to maintain any references to the created objects. The database does that for us. But, we have to change add-game to make an instance of persistent-game instead of our old game class. And even though Elephant is thread-safe we have to ensure that the transactions are atomic. Elephant provides a nice with-transaction macro to solve this problem:
(defun add-game (name) (with-transaction () (unless (game-stored? name) (make-instance 'persistent-game :name name))))
Just one final change before we can compile and deploy our new back end: the games function responsible for returning a list of all games sorted on popularity:
(defun games () (nreverse (get-instances-by-range 'persistent-game 'votes nil nil)))
votes is an indexed slot, so we can use get-instances-by-range to retrieve a sorted list. The last two arguments are both nil , which will retrieve all stored games. The returned list will be sorted from lowest score to highest, so I apply nreverse to reverse the list (the n in nreverse indicates that it is a destructive function).
Remembering the Games
Obviously we want to keep all previously added games. After all, users shouldn't suffer because we decide to change the implementation. So, how do we transform existing games into persistent objects? The simplest way is to map over the *games* list and instantiate a persistent-game with the same slot values as the old games:
RETRO-GAMES>(mapcar #'(lambda (old-game) (make-instance 'persistent-game :name (name old-game) :votes (votes old-game))) *games*)
We could have defined a function for this task using defun but, because it is a one-off operation, I go with an anonymous function aka lambda function (see the highlighted code above). And that's it - all games have been moved into a persistent database. We can now set *games* to NIL (effectively making all old games available for garbage collection) and even make the *games* symbol history by removing it from the package:
RETRO-GAMES> (setf *games* nil) NIL RETRO-GAMES> (unintern '*games*) T
This article has really just scratched the surface of what Lisp can do. Yet I hope that if you made it this far, you have seen that behind all those parenthesis there's a lot of power. With its macro system, Lisp can basically be what you want it to.
Due to the dynamic and interactive nature of Lisp it's a perfect fit for prototyping. And because Lisp programs are so easy to evolve, that prototype may end up as a full-blown product one day.
The full source code for the Retro Games application is available here .
Matthew Snyder wrote a sequel. His article is available here: Lisp for the Web. Part II