Saving Your Work
You could argue that this is a complete “hello, world” program of sorts. However, it still has a problem. If you exit Lisp and restart, the function definition will be gone. Having written such a fine function, you’ll want to save your work.
Easy enough. You just need to create a file in which to save the definition. In Emacs you can create a new file by typing C-x C-f
and then, when Emacs prompts you, entering the name of the file you want to create. It doesn’t matter particularly where you put the file. It’s customary to name Common Lisp source files with a .lisp
extension, though some folks use .cl
instead.
Once you’ve created the file, you can type the definition you previously entered at the REPL. Some things to note are that after you type the opening parenthesis and the word **DEFUN**
, at the bottom of the Emacs window, SLIME will tell you the arguments expected. The exact form will depend somewhat on what Common Lisp implementation you’re using, but it’ll probably look something like this:
(defun name varlist &rest body)
The message will disappear as you start to type each new element but will reappear each time you enter a space. When you’re entering the definition in the file, you might choose to break the definition across two lines after the parameter list. If you hit Return and then Tab, SLIME will automatically indent the second line appropriately, like this:11
(defun hello-world ()
(format t "hello, world"))
SLIME will also help match up the parentheses—as you type a closing parenthesis, it will flash the corresponding opening parenthesis. Or you can just type C-c C-q
to invoke the command slime-close-parens-at-point
, which will insert as many closing parentheses as necessary to match all the currently open parentheses.
Now you can get this definition into your Lisp environment in several ways. The easiest is to type C-c C-c
with the cursor anywhere in or immediately after the **DEFUN**
form, which runs the command slime-compile-defun
, which in turn sends the definition to Lisp to be evaluated and compiled. To make sure this is working, you can make some change to hello-world
, recompile it, and then go back to the REPL, using C-c C-z
or C-x b
, and call it again. For instance, you could make it a bit more grammatical.
(defun hello-world ()
(format t "Hello, world!"))
Next, recompile with C-c C-c
and then type C-c C-z
to switch to the REPL to try the new version.
CL-USER> (hello-world)
Hello, world!
NIL
You’ll also probably want to save the file you’ve been working on; in the hello.lisp
buffer, type C-x C-s
to invoke the Emacs command save-buffer
.
Now to try reloading this function from the source file, you’ll need to quit Lisp and restart. To quit you can use a SLIME shortcut: at the REPL, type a comma. At the bottom of the Emacs window, you will be prompted for a command. Type quit
(or sayoonara
), and then hit Enter. This will quit Lisp and close all the buffers created by SLIME such as the REPL buffer.12 Now restart SLIME by typing M-x slime
.
Just for grins, you can try to invoke hello-world
.
CL-USER> (hello-world)
At that point SLIME will pop up a new buffer that starts with something that looks like this:
attempt to call `HELLO-WORLD' which is an undefined function.
[Condition of type UNDEFINED-FUNCTION]
Restarts:
0: [TRY-AGAIN] Try calling HELLO-WORLD again.
1: [RETURN-VALUE] Return a value instead of calling HELLO-WORLD.
2: [USE-VALUE] Try calling a function other than HELLO-WORLD.
3: [STORE-VALUE] Setf the symbol-function of HELLO-WORLD and call it again.
4: [ABORT] Abort handling SLIME request.
5: [ABORT] Abort entirely from this process.
Backtrace:
0: (SWANK::DEBUG-IN-EMACS #<UNDEFINED-FUNCTION @ #x716b082a>)
1: ((FLET SWANK:SWANK-DEBUGGER-HOOK SWANK::DEBUG-IT))
2: (SWANK:SWANK-DEBUGGER-HOOK #<UNDEFINED-FUNCTION @ #x716b082a> #<Function SWANK-DEBUGGER-HOOK>)
3: (ERROR #<UNDEFINED-FUNCTION @ #x716b082a>)
4: (EVAL (HELLO-WORLD))
5: (SWANK::EVAL-REGION "(hello-world)
" T)
Blammo! What happened? Well, you tried to invoke a function that doesn’t exist. But despite the burst of output, Lisp is actually handling this situation gracefully. Unlike Java or Python, Common Lisp doesn’t just bail—throwing an exception and unwinding the stack. And it definitely doesn’t dump core just because you tried to invoke a missing function. Instead Lisp drops you into the debugger.
While you’re in the debugger you still have full access to Lisp, so you can evaluate expressions to examine the state of our program and maybe even fix things. For now don’t worry about that; just type q
to exit the debugger and get back to the REPL. The debugger buffer will go away, and the REPL will show this:
CL-USER> (hello-world)
; Evaluation aborted
CL-USER>
There’s obviously more that can be done from within the debugger than just abort—we’ll see, for instance, in Chapter 19 how the debugger integrates with the error handling system. For now, however, the important thing to know is that you can always get out of it, and back to the REPL, by typing q
.
Back at the REPL you can try again. Things blew up because Lisp didn’t know the definition of hello-world
. So you need to let Lisp know about the definition we saved in the file hello.lisp
. You have several ways you could do this. You could switch back to the buffer containing the file (type C-x b
and then enter hello.lisp
when prompted) and recompile the definition as you did before with C-c C-c
. Or you can load the whole file, which would be a more convenient approach if the file contained a bunch of definitions, using the LOAD
function at the REPL like this:
CL-USER> (load "hello.lisp")
; Loading /home/peter/my-lisp-programs/hello.lisp
T
The **T**
means everything loaded correctly.13 Loading a file with LOAD
is essentially equivalent to typing each of the expressions in the file at the REPL in the order they appear in the file, so after the call to **LOAD**
, hello-world
should be defined:
CL-USER> (hello-world)
Hello, world!
NIL
Another way to load a file’s worth of definitions is to compile the file first with **COMPILE-FILE**
and then **LOAD**
the resulting compiled file, called a FASL file, which is short for fast-load file. **COMPILE-FILE**
returns the name of the FASL file, so we can compile and load from the REPL like this:
CL-USER> (load (compile-file "hello.lisp"))
;;; Compiling file hello.lisp
;;; Writing fasl file hello.fasl
;;; Fasl write complete
; Fast loading /home/peter/my-lisp-programs/hello.fasl
T
SLIME also provides support for loading and compiling files without using the REPL. When you’re in a source code buffer, you can use C-c C-l
to load the file with slime-load-file
. Emacs will prompt for the name of a file to load with the name of the current file already filled in; you can just hit Enter. Or you can type C-c C-k
to compile and load the file represented by the current buffer. In some Common Lisp implementations, compiling code this way will make it quite a bit faster; in others, it won’t, typically because they always compile everything.
This should be enough to give you a flavor of how Lisp programming works. Of course I haven’t covered all the tricks and techniques yet, but you’ve seen the essential elements—interacting with the REPL trying things out, loading and testing new code, tweaking and debugging. Serious Lisp hackers often keep a Lisp image running for days on end, adding, redefining, and testing bits of their program incrementally.
Also, even when the Lisp app is deployed, there’s often still a way to get to a REPL. You’ll see in Chapter 26 how you can use the REPL and SLIME to interact with the Lisp that’s running a Web server at the same time as it’s serving up Web pages. It’s even possible to use SLIME to connect to a Lisp running on a different machine, allowing you—for instance—to debug a remote server just like a local one.
An even more impressive instance of remote debugging occurred on NASA’s 1998 Deep Space 1 mission. A half year after the space craft launched, a bit of Lisp code was going to control the spacecraft for two days while conducting a sequence of experiments. Unfortunately, a subtle race condition in the code had escaped detection during ground testing and was already in space. When the bug manifested in the wild—100 million miles away from Earth—the team was able to diagnose and fix the running code, allowing the experiments to complete.14 One of the programmers described it as follows:
Debugging a program running on a $100M piece of hardware that is 100 million miles away is an interesting experience. Having a read-eval-print loop running on the spacecraft proved invaluable in finding and fixing the problem.
You’re not quite ready to send any Lisp code into deep space, but in the next chapter you’ll take a crack at writing a program a bit more interesting than “hello, world.”
1Superior Lisp Interaction Mode for Emacs
2If you’ve had a bad experience with Emacs previously, you should treat Lisp in a Box as an IDE that happens to use an Emacs-like editor as its text editor; there will be no need to become an Emacs guru to program Lisp. It is, however, orders of magnitude more enjoyable to program Lisp with an editor that has some basic Lisp awareness. At a minimum, you’ll want an editor that can automatically match ()
s for you and knows how to automatically indent Lisp code. Because Emacs is itself largely written in a Lisp dialect, Elisp, it has quite a bit of support for editing Lisp code. Emacs is also deeply embedded into the history of Lisp and the culture of Lisp hackers: the original Emacs and its immediate predecessors, TECMACS and TMACS, were written by Lispers at the Massachusetts Institute of Technology (MIT). The editors on the Lisp Machines were versions of Emacs written entirely in Lisp. The first two Lisp Machine Emacs, following the hacker tradition of recursive acronyms, were EINE and ZWEI, which stood for EINE Is Not Emacs and ZWEI Was EINE Initially. Later ones used a descendant of ZWEI, named, more prosaically, ZMACS.
3Practically speaking, there’s very little likelihood of the language standard itself being revised—while there are a small handful of warts that folks might like to clean up, the ANSI process isn’t amenable to opening an existing standard for minor tweaks, and none of the warts that might be cleaned up actually cause anyone any serious difficulty. The future of Common Lisp standardization is likely to proceed via de facto standards, much like the “standardization” of Perl and Python—as different implementers experiment with application programming interfaces (APIs) and libraries for doing things not specified in the language standard, other implementers may adopt them or people will develop portability libraries to smooth over the differences between implementations for features not specified in the language standard.
4Steel Bank Common Lisp
5CMU Common Lisp
6SBCL forked from CMUCL in order to focus on cleaning up the internals and making it easier to maintain. But the fork has been amiable; bug fixes tend to propagate between the two projects, and there’s talk that someday they will merge back together.
7The venerable “hello, world” predates even the classic Kernighan and Ritchie C book that played a big role in its popularization. The original “hello, world” seems to have come from Brian Kernighan’s “A Tutorial Introduction to the Language B” that was part of the Bell Laboratories Computing Science Technical Report #8: The Programming Language B published in January 1973. (It’s available online at http://cm.bell-labs.com/cm/cs/who/dmr/bintro.html
.)
8These are some other expressions that also print the string “hello, world”:
9Well, as you’ll see when I discuss returning multiple values, it’s technically possible to write expressions that evaluate to no value, but even such expressions are treated as returning NIL
when evaluated in a context that expects a value.
10I’ll discuss in Chapter 4 why the name has been converted to all uppercase.
11You could also have entered the definition as two lines at the REPL, as the REPL reads whole expressions, not lines.
12SLIME shortcuts aren’t part of Common Lisp—they’re commands to SLIME.
13If for some reason the **LOAD**
doesn’t go cleanly, you’ll get another error and drop back into the debugger. If this happens, the most likely reason is that Lisp can’t find the file, probably because its idea of the current working directory isn’t the same as where the file is located. In that case, you can quit the debugger by typing q
and then use the SLIME shortcut cd
to change Lisp’s idea of the current directory—type a comma and then cd
when prompted for a command and then the name of the directory where hello.lisp
was saved.
14http://www.flownet.com/gat/jpl-lisp.html