(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:
cl-user
)
oli
)
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:
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.
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.
(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!
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?
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?
Previous month: Click here.