The less-familiar parts of Lisp for beginners — defconstant

Next in the series of commands that the novice Lisp programmer arriving from C++ might overlook is defconstant.  Now, this macro is actually something that the programmer possibly started using, then discarded because of perceived inconvenience.

There’s a basic difference between C++ programs and Lisp programs.  C++ programs typically run in isolation.  While you can certainly write code to allow them to communicate with their environment, other programs, or with files, the conceptual model of a C++ program is something that you write in an editor, compile into a binary, and then run.  If it becomes necessary to modify the code, you go back to the editor, recompile the binary, terminate the running code and then run the modified version.

Lisp is, in typical use, a very different model.  Your Lisp program is really a collection of connected functions running on a platform.  If you take care to avoid namespace issues, you can easily load several different “programs” into a single Lisp image, and have them all running in separate threads.  You can edit a function in the Lisp image, and the running code will immediately start using the modified function.

So, you came to Lisp from C++.  In C++, constants were simple to understand.  You declared them constant, and everywhere the definition was in scope, the compiler likely knew, if the information was available at compile time, what the value would be.  Your linker might even have put the data into a read-only page.  You used constants instead of macros for reasons of visibility, and because you wanted to be able to pass around references or pointers, and wanted access to the symbol in the debugger.

Then, you got to Lisp.  You wrote some code with defconstant.  You compiled it, loaded it, tested it, and decided to change the constant to a different value.  Once again, you compiled it, but when you tried to load it, you got an error.  This isn’t like C++, where your execution environment starts from scratch when you run the code…your Lisp image doesn’t get reset just because you happened to recompile a file somewhere in your system, all it can tell is that a piece of code set the constant to one value, and then later on you loaded another piece of code that tried to set the same constant to a different value.  That’s an error.  So, you exited your Lisp image, restarted, and kept going, but after a while you decided that defparameter was just easier.

Your solution to this particular disappointment is to use the unintern function.  Here’s a transcript of a Lisp session trying to modify a constant.
 

CL-USER> (defconstant +abc+ 12345)
+ABC+
CL-USER> (defconstant +abc+ 12346)
; Evaluation aborted on #<DEFCONSTANT-UNEQL {10140BA103}>.
CL-USER> +abc+
12345
CL-USER> (unintern '+abc+)
T
CL-USER> (defconstant +abc+ 12346)
+ABC+
CL-USER> +abc+
12346

You don’t see here, because SLIME shows it in a separate window, the error that came up, with possible restarts, when the attempt was made to modify a constant value.  You’ll note also that I used the common Lisp convention for constants, that the name should be enclosed in ‘+’ characters.

Now, look at the earlier post about declare.  You, the C++ programmer, might say, “If I made those parameters in the code into constants, then I wouldn’t have to use declare, they’re in scope for the compiler and it would know to make those optimizations itself.”  If you try this, though, you’ll discover that it isn’t true.  Again, it the concept of the Lisp image that your code’s running in.  Your .lisp file isn’t like a C++ compilation unit, because after the code has been compiled and loaded, it’s still possible for other code to modify those constants with unintern.  The compiler, when operating on the function to produce the next random number, cannot assume anything about constants that might appear to you to be “in scope”, unless they are part of the defun itself.

OK, so now the new Lisp programmer has decided to make some use of constants, keeping in mind the limitations and restrictions I’ve outlined above.  And, the new Lisp programmer puts in what looks like a fairly straight-forward constant list.  Later, he or she modifies some other code in the file, and re-loads it, and is amazed that the defconstant now fails:
 

CL-USER> (unintern '+abc+)
T
CL-USER> (defconstant +abc+ '(1 2 3))
+ABC+
CL-USER> (defconstant +abc+ '(1 2 3))
; Evaluation aborted on #<DEFCONSTANT-UNEQL {1014B67A03}>.

The Lisp system refused to allow you to reload exactly the same line, without changes.  What happened?  Understanding this is actually important in avoiding a lot of mysterious behaviour in your code.  The defconstant form, once established, must be constant.  For scalars like numbers or symbols, that’s simple enough, but for lists, or objects, it means you have to use the same object.  Not another object that looks like it, but the same object.  It’s important to realize that two literal lists are different objects, even if they have the same contents.  Once more, unintern will be useful to you here, in allowing you to reload the offending .lisp file and continue your work.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

反垃圾邮件 / Anti-spam question * Time limit is exhausted. Please reload CAPTCHA.