[Openmcl-devel] Getting started with Lisp
gb at clozure.com
Sat Sep 23 02:19:45 EDT 2006
On Fri, 22 Sep 2006, Joe Chan wrote:
> I just started learning Lisp using OpenMCL, and I'm looking for
> pointers to documentation/tutorial about some non-coding related
> development issues in Lisp. By that I mean the following:
> 1. How the various options in the OpenMCL break works. I usually just
> type code directly into the ? prompt, and when something goes wrong,
> it goes into the > prompt, and I have no idea how to interpret all
> those #<RESTART ABORT-BREAK #x294626>, etc. lines. I looked at the
> OpenMCL doc, and it seems to cater to seasoned Lisp users and
> describes the various APIs.
At either a "?" (toplevel) prompt or a "n >" (break) prompt, typing
:? will print one-line syntax summaries of available colon-commands
(so called because the command names are all keyword symbols, which
are usually printed and read with a leading colon.) As the summaries
show, some colon commands take arguments. In 1.0, it's necesssary to
surround any colon command that takes arguments and those arguments
with parens; in 1.1, the parens will be optional as long as the keyword
and arguments are typed on the same line.
Knowing the syntax of available break loop commands is only helpful
if you have some higher-level notion of what your options are. One
option that's always available from a break loop is to exit all pending
break loops and return to the toplevel loop:
? (break) ; enter a break loop deliberately
> Break in process listener(1)
> Type :? for other options
1> (break) ; enter another break loop
> Break ...
2> :q ; exit all nested break loops
? ; and return to "toplevel"
[At some point around the 1.0 release, the :Q command was broken. I don't
remember whether it was broken in 1.0 or not; I do remember that it had
been broken for a while before anyone reported it. I think that that
means that most people use SLIME or some similar environment these days,
and rarely interact with the REPL directly.]
Depending on how the break loop was entered, it may be possible to continue
execution at the point right after the (implicit or implicit) call that
invoked the break loop. A SIGINT (^C interrupt) usually causes the lisp
listener to enter a break loop.
Here's a contrived example:
? (defun countdown-slowly (n)
(unless (zerop n)
(countdown-slowly (1- n))))
? (countdown-slowly 1000)
;;; Wait a few seconds, then hit ^C
> Break in process listener(1):
> While executing: #<Anonymous Function #x8107DB6>
> Type :GO to continue, :POP to abort.
> If continued: Return from BREAK.
Type :? for other options.
It'd have been more meaningful if the message that was printed said that
we were executing COUNTDOWN-SLOWLY or 1- or SLEEP when the interrupt
happened; I think that 1.1 will be better about that sort of thing.
Unless there was a bug in the definition of COUNTDOWN-SLOWLY, we should
find that it's (slowly) counting down from 1000 to 0. We can verify this
(at least verify that it's made some progress) by looking at a stack
backtrace. By just using the :b break loop command with no arguments,
we'll get some indication of what functions are awaiting return on the
stack. (The 1.0 backtrace is pretty messy and often shows uninteresting
internal frames; the SLIME Emacs environment and 1.1 both try to make
the call history a little more intelligible.)
In 1.0, we'd see something like:
(F01351A0) : 0 "Anonymous Function #x8107DB6" 120
(F01351C0) : 1 NIL NIL
(F01351D0) : 2 "APPLY" 76
(F01351E0) : 3 "THREAD-HANDLE-INTERRUPTS" 420
(F0135200) : 4 NIL NIL
(F0135210) : 5 "FUNCALL-WITH-XP-STACK-FRAMES" 248
(F0135220) : 6 "XCMAIN" 1600
(F0135230) : 7 NIL NIL
(F0135240) : 8 "%PASCAL-FUNCTIONS%" 140
(F0135260) : 9 NIL NIL
(F0135B90) : 10 "%NANOSLEEP" 264
(F0135B90) : 11 "COUNTDOWN-SLOWLY" 52
In this case, COUNTDOWN-SLOWLY is in the 11th stack frame (counting from the most
recent and including a lot of junk that we really don't care about.) We can
use the :F command to look at the contents of that frame:
1 > (:f 11)
(F0135B90) : 11 "COUNTDOWN-SLOWLY" 52
0 N: 998 ("required")
That's trying to say that the value of the required argument N to the function
COUNTDOWN-SLOWLY is 998 at this point in time.
Satisfied that we are indeed counting down (slowly ...), we can continue the
interrupted program. The easiest way to do so is to use the :GO break loop
1 > :go
and at this point we've resumed the not-very-interesting process of
decrementing a small integer and sleeping. (Mostly sleeping, of
course.) If we wanted to wait another 998 seconds or so, we'd
eventually return to the toplevel read-eval-print loop. If we didn't
want to wait, we could use ^C to interrupt the "computation" (probably,
to interrupt a call to SLEEP) and use :Q in the resulting break loop
to abort out of the not-very-interesting ongoing computation.
The :GO break loop command actually calls a CL function called
CONTINUE; the standard CONTINUE function "invokes the most recently
established restart named CONTINUE." A restart is basically a function
that can be used to transfer control - often in response to an error
or other exceptional situation - and possibly return values or have
other side-effects along the way.
Suppose you want to acess the value of the special variable *FOO*,
and mistakenly forget to type the last "*". This will be interpreted
as an attempt to access the value of *FOO, and *FOO is probably unbound:
> Error in process listener(1): Unbound variable: *FOO
> While executing: "Unknown"
> Type :GO to continue, :POP to abort.
> If continued: Retry getting the value of *FOO.
Type :? for other options.
:GO at this point isn't very useful; we'd just return to the attempt to
access *FOO's value and be right back in the break loop. We can,
however, see if other restarts (besides the CONTINUE restart that :GO
invokes) are available:
0. Return to break level 1.
1. #<RESTART ABORT-BREAK #x294976>
2. Retry getting the value of *FOO.
3. Specify a value of *FOO to use this time.
4. Specify a value of *FOO to store and use.
5. Return to toplevel.
6. #<RESTART ABORT-BREAK #x294CBE>
7. Reset this process
8. Kill this process
This is another contrived example; the simplest response to a typo at the
toplevel is usually to return to return to toplevel and try again. If
we'd gotten this sort of error when debugging a long-running application,
it might be simpler to try to resume execution after invoking restart 3
or 4 in the list above:
1 > (:c 3)
Invoking restart: Specify a value of *FOO to use this time.
New value for *FOO : nil
> 2. Once I get into the break, it seems I can easily pick restart
> options to get completely stuck (e.g., picking "Reset this process"
> invariably cause me to lose the ? prompt forever, and I have to kill
> the process).
You probably don't want to reset the current process (thread). In
some cases (e.g., when the current process is something other than
the initial listener or for some more atypical reason.)
Now that you know that resetting the listener thread is something
that you usually don't want to do, you'll also know that you have
enough rope in the relatively obscure cases where this might be
useful. (Sort of like touching a hot stove.)
> 3. How to load source files into OpenMCL. I tried loading the
> examples using (require "blah") and some of them worked. But when I
> load my own file, I get error like:
LOAD is the standard way of loading a (source or binary) file into
a running lisp. It takes a PATHNAME (or a string or something else
that designates a PATHNAME) as a required argument.
? (load "/Users/gb/fact.lisp")
REQUIRE is a different thing; it can often be used to load files, but
what it actually tries to do is to ensure that some named piece of
functionality (a "MODULE") has been made available ("PROVIDEd"). The
exact mechanism that REQUIRE uses to make these modules available
is implementation-dependent. In OpenMCL, the default implementation
is to search a list of directories (the value of the variable
CCL:*MODULE-SEARCH-PATH*) for a file whose (lowercased) name matches
the module name and, if successful, to try to LOAD that file. (More
generally, any function on the list which is the value of
CCL:*MODULE-PROVIDER-FUNCTIONS* can perform arbitrary actions - like
downloading and installing code from the net - in order to provide
a module; the function that searches CCL:*MODULE-SEARCH-PATH* is, by
default, the only function on the CCL:*MODULE-PROVIDER-FUNCTIONS* list.)
REQUIRE and PROVIDE are often used as system-construction tools: in
order to compile X, it may be necessary that functionality defined
in some other system component (file or set of files). It's not
quite the same thing (it doesn't involve text substitution), but
one might think of the C idiom
#include <foo.h> /* Find foo.h in the filesystem somewhere. We don't
care where (too much); we just want the functionality
that it offers */
(require "FOO") ;; If FOO hasn't already been provided, go find it
;; and make the functionality that it offers available
as being sort of distant cousins.
> > Error in process listener(1): Module test was not provided by any
> function on *MODULE-PROVIDER-FUNCTIONS*.
> > While executing: REQUIRE
> It seems like it's not finding the file, even if I specify the full
REQUIRE's first argument is a module name, not a pathname.
> 3. May be once I figure out how to do (2), this will be obvious, but
> what about building applications with multiple source files? I don't
> suppose I use make some how? How do I build an executable that can
> just be run?
The one example of this that comes with the lisp is in
"ccl:examples;cocoa-application.lisp", which creates a
double-clickable GUI application.
To back up a bit: I think that in practice people often make
"applications" by simply passing a few --load and/or --eval arguments
on the command line. It's also possible to load application-specific
code into a running lisp and then save a memory image via the function
CCL:SAVE-APPLICATION (possibly specifying that that application do
something other than start a REPL when it's invoked.) In a lot of
cases, which general approach is better may depend on how long-lived
the application is (it might be harder to debug something built
Some people use make to build lisp applications; it's sometimes preferable
to use lisp-specific system construction tools (such as DEFSYSTEM and
ASDF). A lot of modern freely-available lisp packages (things that
one might find on common-lisp.net or via cliki.net) are intended to
be built with ASDF; they're typically distributed with .asd files which
serve a similar role to Makefiles in C packages.
> I come from C/C++ background, tinkered with Python, Ruby, etc., so I
> don't find the language itself to be so hard to learn at this point,
> but not knowing how to use the tools properly is really getting in my
> way of learning Lisp. I suppose being a Vim user doesn't help either :).
> Any pointers are welcome.
> Joe Chan
> firstian at rcn.com
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
More information about the Openmcl-devel