Basic LISP Techniques - Part2
Basic LISP Techniques - Part2
Basic LISP Techniques - Part2
Operating a CL Development
Environment
This chapter covers some of the hands-on techniques used when working with a CL devel-
opment environment, specifically, the Allegro CL
R
environment from Franz Inc. If you
are completely unfamiliar with any Lisp language, you may wish to reference Chapter 3 as
you read this chapter.
Ideally, while reading this chapter, you should have access to an actual Common Lisp
session and try typing in some of the examples.
When working with Common Lisp, it helps to think of the environment as its own
operating system, sitting on top of whatever operating system you happen to be working
with. In fact, people used to build entire workstations and operating systems in Lisp. The
Symbolics Common Lisp environment still runs as an emulated machine on top of the 64-bit
Compaq/DEC Alpha CPU, with its own Lisp-based operating system.
A complete and current listing of available CL systems can be found on the Association
of Lisp Users website, at
http://www.alu.org/table/systems.htm
These systems follow the same basic principles, and most of what you learn and write on one
system can be applied and ported over to others. The examples in this guide were prepared
using Allegro CL for Linux. Where possible, we will note Allegro CL-specific syntax and
extensions.
5
6 CHAPTER 2. OPERATING A CL DEVELOPMENT ENVIRONMENT
alisp
from the Unix (or DOS) command line. Assuming your shell execution path is set up
properly and CL is installed properly, you will see some introductory information, then will
be presented with a Command Prompt which should look very similar to the following:
CL-USER(1):
CL has entered its read-eval-print loop, and is waiting for you to type something which it
will then read, evaluate to obtain a return-value, and finally print this resulting return-value.
The USER printed before the prompt refers to the current CL package (more on Packages
later), and the number (1) simply keeps track of how many commands have been entered
at the prompt.
(excl:exit)
at the Command Prompt. This should return you to the shell. In very rare cases, a stronger
hammer is required to stop the CL process, in which case you can try typing
(excl:exit 0 :no-unwind t)
Try It: Now try starting and stopping CL several times, to get a feel for it. In general, you
should notice that CL starts much more quickly on subsequent invocations. This is because
the entire executable file has already been read into the computer’s memory and does not
have to be read from the disk every time.
A shortcut for the (excl:exit) command is the toplevel command :exit. Toplevel
commands are words which are preceded by a colon [:] that you can type at the CL
Command Prompt as a shorthand way of making CL do something.
Try It: Start the CL environment. Then, using your favorite text editor, create a file
/tmp/hello.lisp and place the following text into it (don’t worry about understanding
the text for now):
2.2. RUNNING CL IN A SHELL WINDOW 7
(in-package :user)
(defun hello ()
(write-string "Hello, World!"))
Save the file to disk. Now, at the CL prompt in the shell where you started the CL
environment, compile and load this file as follows (text after the “USER” prompt represents
what you have to type; everything else is text printed by CL):
By default, the compile-file command will look for files with the .lisp suffix, and the
load command will load the resulting compiled machine-code (binary) file, which by default
will have the extension .fasl. The extension “.fasl” stands for “FASt Loading” file.
Note that, in general, simply loading an uncompiled Lisp file (using load) will function-
ally have the same effect as loading a compiled binary (fasl) file. But the fasl file will load
faster, and any functions, objects, etc. defined in it will perform faster, since they will have
been optimized and translated into language which is closer to the actual machine. Unlike
Java “class” files, CL fasl files usually represent native machine-specific code. This means
that compiled CL programs will generally run much faster than compiled Java programs,
but CL programs must be compiled separately for each type of machine on which you want
to run them.
Finally, try executing the newly defined function hello by typing the following at your
command line:
CL-USER(5): (hello)
Hello, World!
"Hello, World!"
You should see the String Hello, World! printed (without double-quotes), then the re-
turned String (with double-quotes) will be printed as well. In the next chapter, we will
learn more about exactly what is going on here. For now the main point to understand is
the idea of compiling and loading definitions from files, and invoking functions by typing
expressions at the CL command line.
8 CHAPTER 2. OPERATING A CL DEVELOPMENT ENVIRONMENT
• M-x means to hold down the Meta key, and press (in this case) the “X” key
• C-x means to hold down the Control key, and press (in this case) the “X” key
• C-M-q means to hold down the Control key and the Meta key at the same time , and
press (in this case) the “Q” key
1
Gnu Emacs comes pre-installed with all Linux systems, and for other systems should come as part
of your Allegro CL distribution. In any case, Emacs distributions and documentation are available from
http://www.gnu.org
2.3. RUNNING CL INSIDE A TEXT EDITOR 9
• M-A would mean to hold down the Meta key, and the Shift key (because the “A” is
uppercase), and press (in this case) the “A” key.
emacs
or
gnuemacs
then start a Unix (or DOS) shell within emacs by typing M-x shell. Now type
lisp
inside this shell’s window to start a CL session there. To shut down a session, exit CL
exactly as above by typing
(excl:exit)
exit
and finally exit from Emacs by typing C-x C-c or M-x kill-emacs. Note that it is good
practice always to exit from CL before exiting from Emacs. Otherwise the CL process may
be left as an “undead” (zombie) process.
When in the CL process, you can move forward and backward in the “stack” (history)
of expressions that you have typed, effectively recalling previous commands with only a few
keystrokes. Use M-p to move backward in the history, and M-n to move forward. You can
also use all of the editor’s text processing commands, for example to cut, copy, and paste
expressions between the command line and other windows and open files (“buffers”) you
may have open.
Try It: try typing the following expressions at the CL prompt. See what return-values
are printed, and try recalling previous expressions (commands) using M-p and M-n:
(list 1 2 3)
(+ 1 2 3)
(> 3 4)
(< 3 4)
Now, of course, you can edit, compile, load, and test your hello.lisp file as we did in
Section 2.2. But this time you are staying within a single environment (the text editor
environment) to achieve all these tasks.
10 CHAPTER 2. OPERATING A CL DEVELOPMENT ENVIRONMENT
(setq load-path
(cons "/usr/local/acl62/eli" load-path))
(load "fi-site-init")
As above, you may have to modify this pathname to point to your actual installed location
of Allegro CL (acl62). Once you have added these lines to your .emacs file, you should be
able to restart emacs, then issue the command:
M-x fi:common-lisp
to start CL running and automatically establish a network connection between the Emacs
process and the CL process (in theory, the two processes could be running on different
machines, but in practice they usually will run on the same machine). Accept all the
defaults when prompted.
You can also start emacs with the CL subprocess automatically from a terminal window,
by invoking emacs with an optional “function” argument, as follows:
emacs -f fi:common-lisp
This will start Emacs, which itself will immediately launch CL.
• Compile and load files into the running CL process, directly from the open Emacs
buffer you are editing (no need explicitly to save the file, call the compile-file
function, then call the load function, as with the “in-a-shell” techniques above)
• Query the running CL process for information about objects currently defined in it
— for example, querying the CL, directly from Emacs, for the argument List of a
particular function
• Locate the source code file corresponding to any defined CL object, such as a function
or parameter, and automatically open it in an Emacs buffer
• Perform various debugging actions and other general CL help capabilities, directly
from within the Emacs session
Now, split your Emacs frame into two windows, with the command C-x 2. In both
windows you should now see *common-lisp*. Now move the point2 to the other window
with C-x o, and open the hello.lisp file you created for the previous exercise3 . If you
don’t have that file handy, create it now, with the following contents:
(defun hello ()
(write-string "Hello, World!"))
Now, compile and load the contents of this function definition with C-M-x. Finally, switch
to the *common-lisp* window again with C-x o, and try invoking the hello function by
calling it in normal Lisp fashion:
CL-USER(5): (hello)
Hello, World!
"Hello, World!"
You should see the results printed into the same *common-lisp* buffer.
Try it: Now try the following: go back to the hello.lisp buffer, make an edit to
the contents (for example, change the String to "Hello, CL World!"). Now compile the
buffer with this change (Note that you do not necessarily have to save the file). Finally,
return to the *common-lisp* buffer, and try invoking the function again to confirm that
the redefinition has taken effect.
You have now learned the basic essentials for working with the Emacs-Lisp interface.
See Appendix C for more information about Emacs customization and convenient Emacs
commands specific to the Lisp editing mode.
.clinit.cl
2
In technical Emacs parlance, the point is the insertion point for text, and the cursor is the mouse
pointer.
3
Remember that pressing the space bar will automatically complete filenames for you when opening files
in Emacs.
2.7. USING CL AS A SCRIPTING LANGUAGE 13
c:\clinit.cl
on Windows systems.
For example, the init file can be used to define library functions and variables that the
user employs frequently, or to control other aspects of the CL environment. An example
init file is:
(in-package :user)
(defun load-project ()
(load "~/lisp/part1")
(load "~/lisp/part2"))
This will have the effect of defining a convenient function the user can then invoke to
load the files for the current project. Note the call to the in-package function, which
tells CL which Package to make “current” when loading this file. As we will see in more
detail in Section 3.7, CL Packages allow programs to be separated into different “layers,” or
“modules.” Notice also that in the calls to load we do not specify a file “type” extension,
e.g. .lisp or .fasl. We are making use of the fact that the load function will look first
for a binary file (of type fasl), and if this is not found it will then look for a file of type
cl or lisp. In this example we assume that our Lisp files will already have been compiled
into up-to-date binary fasl files.
In Section 2.9 we will look at some techniques for managing larger projects, and au-
tomating both the compiling and loading process for the source files making up a project.
Note: the IDE mentioned in Section 2.5 is also a full-featured and powerful project
system. The IDE is described in its own documentation, and is beyond the scope of this
document.
it will load all of the function definitions and other expressions in file script.fasl or
script.lisp, will process datafile1 on its Standard Input, and will create datafile2
with anything it writes to its Standard Output. Here is an example of a simple script.lisp
file:
(in-package :user)
(defun process-data ()
14 CHAPTER 2. OPERATING A CL DEVELOPMENT ENVIRONMENT
(let (x j)
;;Read a line of input from datafile1
(setq x (read-line))
;;Remove leading spaces from X
(setq j (dotimes (i (length x))
(when (not (string-equal " " (subseq x i (+ i 1))))
(return i))))
(setq x (subseq x j))
;;Write the result to datafile2
(format t "~A~%" x)))
(process-data)
This file defines a function to process the datafile, then calls this function to cause the
actual processing to occur.
Note the use of the semicolon (“;”), which is a reader macro which tells CL to treat
everything between it and the end of the line as a comment (to be ignored by the CL
Reader).
2.8 Debugging in CL
CL provides one of the most advanced debugging and error-handling capabilities of any
language or environment. The most obvious consequence of this is that your application
will very rarely “crash” in a fatal manner. When an error occurs in a CL program, a break
occurs. CL prints an error message, enters the debugger, and presents the user with one
or more possible restart actions. This is similar to running inside a debugger, or in “debug
mode,” in other languages, but in CL this ability comes as a built-in part of the language.
For example, if we call the + function with an alphabetic Symbol instead of a Number,
we generate the following error:
CL-USER(95): (+ ’r 4)
Error: ‘R’ is not of the expected type ‘NUMBER’
[condition type: TYPE-ERROR]
The number [1] at the front of the prompt indicates that we are in the debugger, at Level 1.
The debugger is itself a CL Command Prompt just like the toplevel Command Prompt, but
it has some additional functionality. The next section provides a partial list of commands
available to be entered directly at the debugger’s Command Prompt:
• :down Moves the currency pointer down one frame in the stack
Franz Inc.’s Allegro Composer product also offers graphical access to these commands,
as well as additional functionality.
2.8.4 Profiling
CL has built-in functionality for monitoring its own performance. The most basic and
commonly used component is the time Macro. You can “wrap” the time Macro around
any expression. Functionally, time will not affect the behavior of the expression, but it will
print out information about how long the expression took to be evaluated:
It is often interesting to look at the difference in time between interpreted and compiled
code. The example above is interpreted, since we typed it directly at the Command Line,
so CL had no opportunity to optimize the dotimes. In compiled form, the above code
consumes only one millisecond as opposed to 72 milliseconds.
In addition to the basic time Macro, Allegro CL provides a complete Profiler package,
which allows you to monitor exactly what your program is doing, when it is doing it, how
much time it takes, and how much memory it uses.
1. Go through your project’s Codebase, finding source files (in the proper order when
necessary, as noted above)
2. For each file found, either load the corresponding binary file (if it is up-to-date), or
compile the source file to create a new binary file and load it (if the source file has
changed since the most recent compilation)
CL does not dictate one standard way of accomplishing this task, and in fact, several
options are available. One option is to use a defsystem package, which will allow you to
prepare a special file, similar to a “make” file, which lists out all your project’s source
files and their required load order. Allegro CL contains its own Defsystem package as
an extension to CL, and the open-source MK:Defsystem is available through Sourceforge
(http://www.sourceforge.net).
Using a Defsystem package requires you to maintain a catalog listing of all your project’s
source files. Especially in the early stages of a project, when you are adding, renaming, and
deleting a lot of files and directories, maintaining this catalog listing by hand can become
tiresome.
For this purpose, you may wish to use a lighter-weight utility for compiling and loading
your files, at least in the early stages of development. For very small projects, you can use
the Allegro CL function excl:compile-file-if-needed, an extension to CL, by writing a
simple function which calls this function repeatedly to compile and load your project’s files.
The Bootstrap package, available at http://gdl.sourceforge.net, provides the func-
tion cl-lite, which will traverse a codebase, compiling and loading files, and observing
simple “ordering directive” files which can be placed throughout the codebase to enforce
correct load ordering.
For a new project, the best option is probably to start with a simple but somewhat
manual loading technique, then graduate into more automated techniques as they become
necessary and as you become more familiar with the environment.
excl:dumplisp
The Image File is typically named with a .dxl extension. Once you have created an Image
File, you can start a session simply by starting CL with that Image File, as follows:
lisp -I <image-file-name>.dxl
You must then compile/load any files that have changed since the Image File was created.
Starting CL with the Image File has the same effect as starting a base CL and loading all
your application files, but is simpler and much faster.
cd $MYAPP_HOME
lisp -I image.dxl -q
In this example, both the application’s init file and the application’s image file would be
housed in the directory named by the environment variable $MYAPP HOME.
92 INDEX
tables
hash, 45
test-expression forms
for cond, 37
threads, 1
toplevel, 19
turning off evaluation, 22
Typing
Dynamic, 22
union, 34
Unix, 44
variables
global, 25
special, 25
webserver, 62
With-open-file, 45