I'll bore you just one more time with this: When executing (test) as defined
in the following code, Lisp claimed that the function #:TEST_DIALOG is undefined.
(defun test()
(test_dialog))
(in-package :clausbrod.de)
(use-package :oli)
(sd-defdialog 'test_dialog
:ok-action '(display "test_dialog"))
In
part 3 of this mini-series,
we figured out that the
#: prefix indicates an
uninterned symbol - and now
we can solve the puzzle!
Earlier, I had indicated that
sd-defdialog
automatically exports dialog
names into the default package. To perform this trick, somewhere in the bowels of
the
sd-defdialog
macro, the following code is generated and executed:
(shadowing-import ',name :cl-user) ;; import dialog name into cl-user package
(export ',name) ;; export dialog name in current package
(import ',name :oli) ;; import dialog name into oli package
(export ',name :oli) ;; export dialog name from the oli package
As a consequence, the dialog's name is now visible in three packages:
- The default package (
cl-user
)
- Our Lisp API package (
oli
)
- The package in which the dialog was defined (here:
clausbrod.de
)
This is quite convenient for CoCreate Modeling users - typically
mechanical engineers, not Lisp programmers. They don't want to deal with the
intricacies of Lisp's package handling, but instead simply assume that the
command (dialog) will be at their disposal whenever they need it.
Let's look up what the Common Lisp standard has to say on
shadowing-import:
shadowing-import inserts each of symbols into package as an internal symbol, regardless of whether another symbol of the same name is shadowed by this action. If a different symbol of the same name is already present in package, that symbol is first uninterned from package.
That's our answer! With this newly-acquired knowledge, let's go through our
code example one more and final time:
(defun test()
(test_dialog))
Upon loading this code, the Lisp reader will intern a symbol
called
test_dialog
into the current (default) package. As
test_dialog
has not
been defined yet, the symbol
test_dialog
does not have a value; it's just
a placeholder for things to come.
(in-package :clausbrod.de)
(use-package :oli)
We're no longer in the default package, and can freely use
oli:sd-defdialog
without
a package prefix.
(sd-defdialog 'test_dialog
:ok-action '(display "test_dialog"))
sd-defdialog
performs
(shadowing-import 'test_dialog :cl-user),
thereby shadowing (hiding)
and uninterning the previously interned
test_dialog
symbol.
Until we re-evaluate the definition for
(test)
, it will still refer to the
old definition of the symbol
test_dialog
, which - by now - is a) still without
a value and b) uninterned, i.e. homeless.
Lessons learned:
- Pay attention to the exact wording of Lisp error messages.
- The Common Lisp standard is your friend.
- Those Lisp package problems can be pesky critters.
The good news: If you follow a few rules of thumb, you'll probably never run into
complex package problems like this. One such simple rule is to define your
functions first before referring to them. So in our code example, defining
the dialog first before loading/defining the
(test)
function would have saved
us all that hassle.
Phew.
The other day, I finally bought Peter Norvig's classic
Paradigms of Artificial Intelligence Programming,
which everybody in the Lisp community seems to be raving about.
No book review yet, as I haven't even started to read the book. However, a while ago,
I worked through
Norvig's implementation of the loop
macro,
and ever since then, I knew I had to buy the book. The code contains a good amount of
Lisp macrology, and yet it is clear, concise, and so easy to follow. You can read it
like a novel, from cover to back, while sipping from a glass of
pinot noir.
Impressive work.
If you've soaked up enough
Common Lisp to roughly know what
lambda
and
defmacro
do, this is the kind of
code you should be reading to take the next step in understanding Lisp. This is also
a brilliant way to learn how to use
loop, by the way.
I can't wait to find out what the rest of the book is like!
Update 9/2013: Norvig's (How to Write a (Lisp) Interpreter (in Python))
is just as readable and inspirational as the loop macro code. Highly recommended.
Lisp recently surprised me
with an error message which I had not expected.
(defun test()
(test_dialog))
(in-package :clausbrod.de)
(use-package :oli)
(sd-defdialog 'test_dialog
:ok-action '(display "test_dialog"))
Load the above code, run
(test)
, and you'll get:
In CoCreate Modeling, the
sd-defdialog
macro automatically exports the name of the new
dialog (in this case,
test_dialog
) into the default package. Hence, you'd expect that
the function
(test)
, which is in the default package, would be able to call that dialog!
Astute readers (and CoCreate Modeling's Lisp compiler) will rightfully scold me for using
(in-package)
in the midst of a file. However, the error doesn't go away if you split up
the above code example into two files, the second of which then properly
starts with
(in-package)
. And in fact, the problem originally manifested itself in a
multiple-file scenario. But to make it even easier for readers to run the test themselves,
I just folded the two files into one.
Lisp actually provides us with a subtle hint which I ignored so far: Did you notice
that the complaint is about a symbol
#:TEST_DIALOG
, and not simply
TEST_DIALOG
?
The
#:
prefix adds an important piece to the puzzle. Apparently, Lisp thinks
that
TEST_DIALOG
is not a normal symbol,
but a so-called
uninterned symbol. Uninterned symbols are symbols which don't
belong to
any Lisp package - they are homeless. For details:
Uninterned symbols are beasts which live in a slightly darker corner of Common Lisp, or
at least you don't run into them too often. And in our particular case, it isn't exactly obvious
how
TEST_DIALOG
turned into an uninterned symbol. We would have expected it to
be a symbol interned in the
clausbrod.de
package, which is where the dialog is defined!
Those who are still with me in this series will probably know where this is heading.
Anyway -
next time, we'll finally
solve the puzzle!
Yesterday, I presented some Lisp code
which puzzled me for a little while.
To recap, here's the test code again:
(defun test()
(test_dialog))
(in-package :clausbrod.de)
(use-package :oli)
(sd-defdialog 'test_dialog
:ok-action '(display "test_dialog"))
Here is what happens if you save this code into a file, then load the file into
CoCreate Modeling and call the
(test)
function:
"The function #:TEST_DIALOG is undefined"? Let's review the code so that you
can understand why I found this behavior surprising.
First, you'll notice that the function
test
is defined in the default Lisp package.
After its definition, we switch into a different package (
clausbrod.de
), in
which we then define a CoCreate Modeling dialog called
test_dialog
.
The
(test)
function attempts to call that dialog. If you've had any exposure with
other implementations of Lisp before, I'm sure you will say: "Well,
of course the system
will complain that
TEST_DIALOG
is undefined! After all, you define it in package
clausbrod.de
, but call it from the default package (where
test
is defined).
This is trivial! Go read
The Complete Idiot's Guide to Common Lisp Packages
instead of wasting our time!"
To which I'd reply that
sd-defdialog
, for practical reasons I may go into in a future blog
post, actually makes dialogs visible in CoCreate Modeling's default package. And since
the function
test
is defined in the default package, it should therefore have
access to a symbol called
test_dialog
, and there shouldn't be any error messages, right?
To be continued...
The other day, I spent some time debugging a surprising issue in Lisp code written
for CoCreate Modeling. Turns out that the problem can be shrunk down to
only a few lines, and is actually quite instructive on how Lisp's packages work -
an ideal candidate for this blog!
So here is the innocent-looking code:
(defun test()
(test_dialog))
(in-package :clausbrod.de)
(use-package :oli)
(sd-defdialog 'test_dialog
:ok-action '(display "test_dialog"))
Copy/paste this code into a file called
test.lsp
, then load the
file
into a fresh instance of CoCreate Modeling. Run the
test
function by entering
(test)
in
the user input line. Can you guess what happens now? Can you explain it?
To be continued...
Previous month: Click
here.