|
Volume Number: | 4 | |
Issue Number: | 3 | |
Column Tag: | Lisp Listener |
MacScheme vs Coral Lisp
By Paul Snively, ICOM Simulations, MacTutor Contributing Editor
Paul Snively is one of our favorite people; a long time MacTutor fan, and contributor, we are glad to have this contribution from him. Here are some notes Paul sent that will bring all his fans up to date on what he has been doing.
Letter to MacTutor
“Well, here it is--after a period of time that was entirely too long on two counts (too long both in terms of time between payment and delivery (roughly nine months) and time between receipt of software to support this article and delivery (about four months)). Good grief; look at those parenthesis--you can tell I’ve been working in Lisp lately!
Let me congratulate you on the apparent ongoing success of MacTutor as an organization, and upon the consistently high overall quality of the content (my own work notwithstanding). The article on the writing of custom printer drivers was particularly welcome, and there is much more than could be done there. In particular, a LaserWriter driver that doesn’t require the “Laser Prep” file (i.e. one that generates pure PostScript without any Apple-proprietary PostScript macros) would be a most welcome project from a number of standpoints, I think.
In case you might be curious as to what I’ve been up to lately, ICOM Simulations, Inc. has kept me pretty busy from day one, although the situation seems to be intensifying lately. Early on in my time with ICOM, there was “TMON Plus/TMON Professional” (depending upon whom you talked to) to work on. Unfortunately that project met an early demise when it became clear that Waldemar would not be able to complete the kernel before he had to return to MIT after the summer.
After that we concentrated on TMON 2.8, which was released at Boston. Unfortunately, it isn’t everything that we might wish that it was (in particular, the lack of disassembly of ’020 and ’881 opcodes is hurting us, as is the funny-looking display when you’re on a Mac II in multiple bit-planes). However, our customers seem happy to have something that at least will run on a machine with an ’020, and it does have a few additional nicities (such as the INIT loader, the Programmer’s Key INIT, better documentation, no VBL tasks while in TMON, etc.)
Lately, and in some ways concurrently with all of this, we continue to write MacVentures. There’s a certain irony in being the developers of the de facto standard Macintosh debugger and also of a series of adventure games. Apple makes sure that we’re kept on top of new System Software developments (we tested Juggler, of course, for some time) because of TMON, but the applications that benefit from the knowledge first are a bunch of games! (I’ll bet we have the only MultiFinder-aware adventures on the market.) Also ironic is the fact that the MacVenture kernel seems to be a good testing environment for all kinds of weird issues like window management, application-defined events, synchronous and asynchronous I/O, and the like.
Speaking of which, here’s a bug note for MultiFinder 1.0 that you may want to publish. If your application is MultiFinder-aware and has the canBackground bit set in its SIZE resource (i.e. it will run in the background under MultiFinder), and the application posts an application-defined event (appEvt1-appEvt3; appEvt4 is for Suspend, Resume, and mouseMoved events), the event will not necessarily get sent to the application that created it--it may go to some other application running under MultiFinder. Needless to say, this can cause your application and the other application all kinds of headaches if you rely on application-defined events to work properly (the MacVentures historically have used appEvt1 and appEvt2 for won/lost events and sound events; the kernel has been rewritten to be a state machine now that uses no application-defined events).
It’s really been fun Lisping lately; I’d forgotten how nice it is to work in a good Lisp development environment. It’s been so nice and so accessible that I’ve gotten some really interesting ideas that I think (and in some cases know) could be implemented fairly easily in Lisp. One thing that comes immediately to mind is a Display PostScript implementation (Lisp is great for writing interpreters). Another thing that comes to mind is porting Xerox’s “NoteCards” program from their Lisp workstations to a Mac II running Common Lisp. The world seems to love HyperCard, so they should faint over “NoteCards.”
Well, again, thanks for everything! Please say hello to the rest of the gang for me (and please tell Shelley that I enjoyed talking to her again very much). Hopefully I’ll see you in August!” Sincerely, Paul Snively
The Cheap AI Workstation
The Macintosh II has been around for a few months now, and much of the furor has died down, aside from gripes from disgruntled Macintosh II owners that Apple has been less than speedy in coming out with their own color monitor, or that more SIMMs are hard to come by. In some ways, the honeymoon is over, and we’re beginning to hear the complaints. People aren’t as blown away by the performance of the 68020/68881 as they thought they would be. Some software, particularly those packages that insisted on breaking some of Apple’s clearly-defined rules, doesn’t work on the Macintosh II. Virtually all games that do animation of any sophistication at all don’t work because they use the alternate screen buffer, which the Macintosh II hardware lacks.
My personal opinion is that, at least for developers if not for the end users, the honeymoon for the Macintosh II is ending before the marriage has even been consummated. I feel that there’s been too much emphasis on why the Macintosh II is perhaps not as great a “souped-up Mac Plus” as some people might have wished, and not enough emphasis on why the Macintosh II is probably the most bang for the buck that you will be able to get in a low-end workstation for the next few years.
When I was a computer science student in college, I thought the academic scene was pretty boring. By the time I was a freshman in college I’d been programming for four years already. Taking courses called “Advanced Programming Techniques” that consisted of learning the PL/I programming language and learning about such ostensibly innovative data structures as “linked lists” and the algorithms to manipulate them impressed me about as much as learning Latin would have as a high-school student (in fact, I find Latin a great deal more applicable to real-world situations than PL/I)!
That was the obvious side of my college experience. The non-obvious side of my college experience consisted of getting to know two fascinating individuals and the language in which they seemed best able to express their concepts. The people were Douglas Hofstadter, author of Gödel, Escher, Bach: An Eternal Golden Braid, and Daniel Friedman, author of The Little LISPer. The language in question was, of course, Lisp, or at least one or two interesting variations on it.
I learned Franz Lisp and Scheme on the VAXCluster at school on my own time. It was a wonderful environment to work in, and I spent many hours in front of my Model I TRS-80, which was connected to the phone in my dorm, and from there connected to the computing network on campus. I had access to a wonderful Lisp structure editor, and, Lisp being the kind of language that it is, I spent a fair amount of time customizing my copy of the editor to suit my tastes. It was fairly easy, if also fairly dangerous, because I could literally change the editor, and as soon as I entered the change, it would take effect. Needless to say, it was quite possible to goof and do something to make the editor totally unusable this way, so working on a backup copy was always a good idea. But to me, Lisp was always the ultimate interactive development environment, and the concepts behind it were extremely interesting.
By now you’re probably wondering where all of this is leading up to. Well, bear in mind that I was working on a large VAXCluster with lots of disk space and lots of memory--and lots of horsepower. I got a Lisp for my TRS-80 at one point, but it turned out to be a very frustrating thing, what with the limited RAM and CPU power of most 8-bit micros. I basically resigned myself to never working in such an environment again, and since I had become entrenched in the microcomputer world, I figured that I’d never have a good Lisp system.
Time went on, and although decent Lisp systems got smaller and created a whole new class of machines called “workstations,” which had names like Symbolics and Lisp Machine, those, too, were machines with a lot of horsepower and RAM and disk space, dedicated to doing one thing: running Lisp, and running it well enough so that professional AI researchers could do meaningful work. They were obviously still way out of my league as an interested amateur, with price tags well into the five figure range.
But the Macintosh II exists now, a machine with an MC68020 microprocessor, an MC68881 math coprocessor, anywhere from 1 to 8 megabytes of RAM, and as much disk space as you can afford. It’s not a dedicated Lisp machine, but with a couple of Macintosh products available, it might as well be, and it’s a lot cheaper.
I picked up two Lisp implementations at the Boston MacWorld Exposition. One of them is a newcomer, released at the show. The other has been around somewhat longer, but has undergone some evolution and deserves a serious look. What is perhaps most interesting about the two products is that while they both cover extremely similar ground, because of their underlying philosophies they will appeal to different people and for different reasons.
MacScheme+Toolsmith 1.0 is the older of the two products that I have, or at least MacScheme is. In chapter one of the documentation, it says: “MacScheme, introduced in August 1985, was the first microcomputer implementation of any modern Lisp standard.” It’s a rather bold statement, and one open to question if you have used some of the other Lisp offerings on other machines, particularly any of the Golden systems on the IBM PC-class computers. On the other hand, if you accept Semantic Microsystems’ statement that “Scheme is one of the two major dialects of Lisp,” and understand that the other one is Common Lisp, and also know that until very recently there was no such thing as a complete microcomputer implementation of Common Lisp, then the statement is certainly true.
MacScheme historically has been a semi-compiled, byte-code oriented system, but with the addition of the Toolsmith code, MacScheme has become a language capable of compiling to native code and creating standalone applications written entirely in Scheme. Scheme aficionados should find this very exciting. Complete source code for a simple text editor is included to help show how to create applications. The choice of a text editor is an interesting one; I guess the message is that “Lisp isn’t just for AI anymore,” which is certainly a valid point.
If there’s a general way to describe Scheme, it has to include the word “elegant.” Scheme was designed with an eye toward simplicity and power. It tends to be the smallest implementation of Lisp for any given machine, and MacScheme+Toolsmith is no exception to that rule; the kernel is about 75K in size, and a goodly-sized heap image is about 182K. MacScheme+Toolsmith fits pretty well in a one megabyte machine, as long as you’re careful about hogging up valuable RAM with things like debuggers and INITs and sounds and so forth.
MacScheme+Toolsmith 1.0 is shipped on three 800K disks, labelled “Toolsmith,” “Library,” and “Miscellaneous.” Don’t be misled by the fact that the whole product takes 2,400K of disk space to ship; you can copy the Toolsmith disk, boot from it, and accomplish a great deal without ever looking at the other two disks. The Toolsmith disk contains a System Folder, the MacScheme+Toolsmith application, a generic Scheme heap image file, and a Toolsmith heap image file. Double clicking the application will cause MacScheme+Toolsmith to look for “Scheme.heap;” you can double-click any heap image file to cause MacScheme+Toolsmith to load it instead.
Once MacScheme has come up, you’ll see one thing that immediately sets it apart from Allegro CL, the other Lisp environment that I wish to look at: MacScheme has a fairly spartan user interface. It’s too easy to be put off by the minimalist appearance of the product. Avoid the temptation. While the tools to tinker with MacScheme may not be as obviously up front and “Macish” as they are in Allegro CL, they are, for the most part, there. MacScheme is a complete Scheme implementation, and, like Scheme as a dialect, is minimalist in philosophy, preferring to be extended via available source code (of which there is plenty with MacScheme+Toolsmith) rather than providing everything up to and including the kitchen sink as part of the language kernel, which is more Common Lisp’s style.
MacScheme offers a transcript window and four menus as its user interface upon booting. The transcript accepts invocations of Scheme expressions and returns their value in a fashion that should be familiar to anyone who has used a command-line interface before. The four menus are pretty much what you’d expect, especially considering that any Macintosh application that claims to follow the user interface guidelines must have the first three. The four are the Apple, File, Edit, and Command menus.
The Apple menu simply has the standard fare: an “About ” box that tells a bit about who’s behind MacScheme with some copyright information, and whatever desk accessories are available on your system.
The other menus can, and do, vary somewhat from application to application, so let’s look at them more closely. Here’s the File menu:
Figure 1
There’s certainly nothing radically different about this File menu; it contains pretty much what you expect to see in a Mac application. “New” creates a new Scheme source file for you to put code in. “Open ” opens an existing source file. “Close” will close the current file if there is one. “Save” saves the file’s contents. “Save as ” allows you to save a file’s contents in another file. “Page Setup ” allows you to define your printing options. “Print ” prints the file’s contents on the chosen printer, and “Quit” ends your Scheme session.
Here’s the Edit menu:
Figure 2
All of the Edit menu’s functions are standard with the exception of the last two: “Pick” and “Indent.” Both of these functions are Scheme-specific. “Pick” uses a simple heuristic to pick the nearest Scheme expression. For example, if the vertical bar is blinking behind a closing parenthesis, using “Pick” will select back to the matching open parenthesis, assuming that the parenthesis enclose a complete Scheme expression. Note that there are a couple of ways to invoke Pick: you can use the menu or the Command-key equivalent.
“Indent” can be used when you’re editing some Scheme code to reformat its appearance. Like most Lisp editing systems, the editor in MacScheme will automatically indent as you enter the code so as to keep it readable. This is fine for when you’re entering the code from scratch, but editing can ruin the format. Hence the necessity for something like “Indent,” which operates on the current selection. The Tab key also serves this purpose.
Now let’s look at the Command menu:
Figure 3
This menu is all Scheme specific, as you might expect. The first function is the most obvious one for a Lisp system: “Eval.” It evaluates the currently selected Scheme expression, printing the value in the transcript.
The second item, “Pick & Eval,” is the most frequently used one. It does what it says, combining the functionality of “Pick” from the Edit menu and “Eval” just mentioned. Since this combination is so frequently used, it can be invoked three ways: by choosing the menu item, by pressing the Command-key equivalent, or by pressing the Enter (not Return) key.
The next item, “Reset,” is handy when some process has been interrupted and seems irrevocable. Resetting the system is the next best thing to quitting and starting again. It eliminates all your definitions and side effects from the time you brought up MacScheme.
Next comes “Break.” This is used to interrupt a Scheme process. It puts you into MacScheme’s debugger so that you can examine variables, the stack, or results of evaluating expressions. You can continue execution from the debugger as well.
“Trim” is a housekeeping function that cuts off all but the last page of the transcript. It’s useful for when the transcript gets too long and seriously degrades the performance of the system.
“Pause” allows you to stop whatever is going on so that you can see what’s on the screen. “Continue” is its counterpart, and doesn’t become active until “Continue” has been used. They have the same Command-key equivalent so that you can pause and go with a single Command-keystroke.
“Show Transcript” is a convenient function for bringing the transcript window to the front when you have several windows open. That’s a desirable thing, since that’s where all the action is.
Believe it or not, that’s really all there is to MacScheme’s user interface. Of course, with MacScheme+Toolsmith, you have access to the Toolbox and, in the case of some user interface elements such as menus and windows, you have a pleasant pseudo-object-oriented interface to them, so you can certainly add your own menus with your own tools; indeed, I’d recommend it. In particular, if someone has a Scheme version of Common Lisp’s “Apropos,” I’d certainly love to see it.
Now let’s contrast this user interface with that of Allegro CL. Before I do, I’d like to point out to those who might be wondering why I’m concentrating so much on user interfaces that I am an environment freak, which is to say that I believe that the programming environment is what makes the system usable/more productive/more fun or what have you. A good example of a great environment to work in is Smalltalk-80’s. Smalltalk-80 as a language is a very nice object-oriented one with a lot to recommend it--things like consistency, flexibility, and power. Smalltalk-80, however, is also very huge and very complex by most language standards. To make matters worse, programs that are written by a Smalltalk-80 user simply become part of the overall environment. There isn’t any distinction made between code that the language provides and code that the user writes. So if you extend an already huge language by adding several megabytes of user code, you wind up with a potentially larger mess.
That’s why Smalltalk-80 includes several tools for doing what, in total, I call “managing complexity.” In the most general sense, anytime a programmer creates a tool or environment that doesn’t have a direct effect on the functionality of the code that s/he is writing, s/he is managing complexity (MacApp, for example, is a very good attempt to manage the complexity of writing a Macintosh application with a good, reliable, standard user interface).
There are essentially two schools of thought on programming environments: the school that says “thou shalt conserve disk and memory space and gain development efficiency and productivity by disciplines such as structured programming,” and the school that says “given sufficient disk and memory space, gain development efficiency and productivity by having all system source online at all times; have a programmable, customizable editor; have excellent stepping, tracing, stack frame analysis, and other debugging tools; have tools that make the many megabytes of mess easy to access so as to get what you need, and have all this in one consistent environment.” (Of course, some people will argue that there are two kinds of people: those who believe that there are two kinds of people and those who don’t, but that is another story.)
The first school of thought is that espoused by virtually every common development environment in use today. Think about it. Disk space is considered an expensive “plus,” with memory being treated similarly. Most popular languages today (Pascal, C, assembler) do not encourage the idea of extension of the language by the user; languages that do (FORTH, Smalltalk-80, Lisp) are out on the “lunatic fringe” of programming society. Virtually no development environment includes its complete source code (and, to be fair, there are valid commercial reasons for wishing to avoid this), but Smalltalk-80, for example, likes to present you with complete source code online at all times--one reason that it usually takes at least 10 meg of disk space to run Smalltalk-80. High-level implementations for the Smalltalk-80 byte-code primitives (the things actually implemented in the machine language of the target computer) are even included so that you can see how the algorithms work.
When dealing with such large quantities of code, something more than a classical text editor is needed. Smalltalk-80’s answer to this is the browser, which is a special kind of editor that divides all of the code in the system into manageable chunks (in Smalltalk-80’s case, this is facilitated by its object-oriented nature). A simple means of getting to any particular piece of code in the system is provided.
Common Lisp is the Lisp world’s answer to Smalltalk-80, and it tends to reflect that fact by being somewhat larger than other implementations. To give you some idea as to what I mean by this, the Allegro CL application just barely fits on an 800K disk. Contrast this 750+K kernel to MacScheme’s 75K.
You may wonder why an implementation like Allegro CL is so large. Most of the answer to this question can be found by looking at a copy of Common LISP: The Language, by Guy L. Steele, Jr. This book completely defines the Common Lisp language standard, and it’s not the sort of thing you’re going to finish reading over a, say, six hour airline flight unless you’re an incredibly fast reader.
At least two or three other Lisps for the Macintosh (the public-domain XLISP, and ExperLisp and ExperCommonLisp, the latter two from ExperTelligence) have been subsets of Common Lisp to one extent or another, and some people who use a complete Common Lisp on a workstation, mini, or mainframe at work are leery of these products, particularly if they plan to convert their Common Lisp code to run on the Macintosh. Users of Allegro CL have fewer worries, since Allegro CL is the only complete implementation of Common Lisp on any mass-marketed microcomputer as of this writing. (When doing conversions, you still have to worry about the things you always have to worry about, even when going from Common Lisp to Common Lisp--things like macro translation, what packages are available in any given implementation, etc.)
So what does this Common Lisp environment look like? Let’s find out. First, the menus:
Figure 4
This is a pretty standard Apple menu, with the “About ” item for Allegro CL and, in my case, oodles and oodles of desk accessories, thanks to Prof. Mac (aka Steve Brecher) and Suitcase. The “About ” box for Allegro CL is a little unusual because it does more than just tell you about Allegro CL; it also tells you what you’re running on:
Well, it isn’t perfect. First of all, when it has too much information to present, it wraps it around without re-indenting, which isn’t good aesthetically. More importantly, it thinks that my Mac II has an MC68851 installed, which it definitely does not.
Next is the ubiquitous File menu:
Figure 6
This looks a lot like the File menu from other development environments, with the typical “New,” “Open ,” “Close,” “Save,” “Save As ,” “Page Setup ,” “Print,” and “Quit” choices. The “Open Selected File” and “Revert” choices are desirable but not found in most environments, so seeing them here is a pleasant surprise.
Next is Edit:
Figure 7
Here’s another menu with few surprises, with the possible exception of “Insert Killed String ” which relates to the Allegro CL editor, called FRED (more about FRED later). I won’t insult your intelligence by going through all of these common functions.
Now for Eval:
Figure 8
This menu is a little more Lisp-specific, but still pretty intuitively apparent. “Eval Selection” does just that, and it’s not particularly picky about where the selection is, either. If the selection is a valid s-expression, it will be evaluated.
“Eval Buffer” is handy when you’ve made some fairly extensive changes to a source file. Just make the file’s window the active window and choose this menu item. It’s equivalent to choosing “Select All” from the Edit menu, then choosing “Eval Selection.” This probably won’t be used often, however, since Lisp is incrementally compiled, which encourages editing and recompiling individual definitions since the effects are system-wide, rather than forcing you to recompile all definitions following the one that you changed in the source code.
“Load ” puts up the Standard File Dialog showing Allegro CL source files so that you can load one. Loading a file is simply a quick way of opening it and eval’ing the buffer; the only difference is that it’s a one-step operation and doesn’t create a window for the file in question.
“Compile File ” allows you to create a file of Lisp that’s compiled down to MC68000 machine code. Such files have the extension “.fasl” as opposed to “.lisp,” and have the advantage of loading considerably faster than their textual counterparts, as well as making it tough to figure out how something does what it does (Coral Software distributes their Dialog Designer software with Allegro CL 1.1 as a “.fasl” file; apparently the Dialog Designer is proprietary). Note that Allegro CL does not currently create standalone applications, though Coral has promised that capability for the near future.
The last three items are familiar and basic debugging tools, good for stopping a Lisp process in midstream, tinkering around in the current lexical context, and continuing (or not) as you see fit.
“Tools” is where most Allegro CL users will probably find themselves spending some time:
Figure 9
The first item is rather interesting; it’s a function that lists all definitions within a particular FRED buffer (whichever one is active when the menu item is chosen). In order to do this, it must go through the entire buffer looking for DEFUN and DEFOBJFUN and so on (can you say “heuristic?” I knew you could)! It then creates a dialog box containing the names of the definitions:
Figure 10
You can then select a function and click on “Go To Def” to rapidly see the source code for the definition (double-clicking on the name will also rapidly get you there). You can view the names by their order in the buffer or alphabetically. You can also rescan the buffer in case it’s changed since the last scan. This is part of that “management of complexity” that I talked about earlier (easily getting to definitions in large bodies of code).
“Edit Definition ” is similar; it brings up a dialog into which you type the name of the function to edit. In this case, the file in which the function was defined doesn’t even have to be open; Allegro will open it and find the definition for you. In fact, if the function has been defined more than once and from different sources, another dialog will open indicating the order of the multiple definitions (with the most recent being last) and let you choose which version to edit. I find this feature very impressive.
“Apropos ” is for people like me who just can’t remember what the name of that function is I use the extended version of Apropos which is included with Allegro CL 1.1 as source code. It brings up this dialog:
Figure 11
As you can see, you have a lot of choices to make about what Apropos will and will not look for. You can choose what Value Binding Types to look among, and/or what Function Binding Types. You can select the packages to look among, and whether to look for inherited, internal, or external symbols. You can limit your search by first character (thereby only searching things that are, by generally accepted typographical standards, global variables, for example). You can optionally sort the output, and either print it or inspect it.
For all that, what this is really all about is typing some substring into the “Substring” field and letting Allegro list all the symbols that a) meet the criteria specified in the dialog and b) contain the substring that you specified. For example, with the above dialog, here’s the list that the substring “button” gets you:
:RADIO-BUTTON-CLUSTER constant value: :RADIO-BUTTON-CLUSTER :BUTTON2 constant value: :BUTTON2 :BUTTON1 constant value: :BUTTON1 :BUTTON-STRING constant value: :BUTTON-STRING :DEFAULT-BUTTON-ENABLED constant value: :DEFAULT-BUTTON-ENABLED :DEFAULT-BUTTON constant value: :DEFAULT-BUTTON :OK-BUTTON-TITLE constant value: :OK-BUTTON-TITLE :RADIO-BUTTON-PUSHED-P constant value: :RADIO-BUTTON-PUSHED-P :DIALOG-BUTTON-ENABLED constant value: :DIALOG-BUTTON-ENABLED CCL::RADIO-BUTTON-DI-TO-SOURCE function CCL::BUTTON-DI-TO-SOURCE function *RADIO-BUTTON-DIALOG-ITEM* value: #<Object #77, *RADIO-BUTTON-DIALOG-ITEM*, a CCL::*CONTROL-DIALOG-ITEM*> *BUTTON-DIALOG-ITEM* value: #<Object #75, *BUTTON-DIALOG-ITEM*, a CCL::*CONTROL-DIALOG-ITEM*> _BUTTON constant value: 43380macro
Next comes “Inspect,” Common Lisp’s answer to Smalltalk-80’s browser (at least in most respects). Choosing it brings up this dialog:
Figure 12
Coral Software has wisely made “Help” the first choice in the dialog. It brings up a text file with some helpful, if somewhat terse, pointers on how to take advantage of Inspect.
The next item, “Inspect System Data,” is extremely useful, especially for those just getting into Allegro CL. It shows this window:
Figure 13
This window allows you to “inspect” information (and, in some cases, manipulate it) that can be of some value to you and to the system. The first choice in the list is “(devices),” which, as you might expect, tells you about all currently online volumes:
Figure 14
As you can see, I only have one device: my internal hard disk, “Tumbolia.” Double-clicking on it gets me this dialog:
Figure 15
The inspector can perform some Finder-like functions through the Common Lisp file system. There are dialogs for file operations (for those items that don’t end with a colon, i.e. those that are files) and directory operations for the ones that do, i.e. are directories. You can copy, delete, load, compile, edit (if it’s a text file), and so on, all without leaving Allegro CL.
Other file-related functions available from “Inspect” include the inspecting of logical pathnames (Common Lisp was designed to work on large systems with hierarchical directory structures, e.g. UNIX-based machines and VAXes running VMS. Macintoshes from the 512K Enhanced up fall neatly into this category as well). Common Lisp defines some logical pathnames; Allegro CL provides some more, and you, the programmer, can define still others to make your life easier (much as MPW users can define symbols to refer to complex paths to various files).
A pathname can also be searched for a string, and the pathname being searched can contain wildcard characters, allowing you to define clearly the places to look. “Inspect” can also list all pathnames that match a certain wildcard string.
“Inspect” can also tell you about several system data structures, such as the currently open windows:
Figure 16
At the time that I double-clicked on “(windows)” in “Inspect,” there were only three (at least in that MultiFinder layer)--“System Data,” “Inspect,” and “Listener.” In Allegro CL, you see things referred to as “objects” quite a bit, and they don’t mean Lisp data objects, either. Allegro CL is an Object Lisp implementation, and when it says that “Listener” is a “CCL::*LISTENER*,” it means that “Listener” is a specific instance of an object called “*LISTENER*” that was defined in the “CCL” package.
Let’s see what “Inspect” can tell us about “Listener:”
The window gives us the object’s ancestry in the object hierarchy along with the object’s variable and function bindings. Values are shown for the variables bound to the object.
The list goes on and gets quite long. Let’s move on from “Inspect” and look at some other things.
Back in the “Tools” menu, “Backtrace” is something that can become enabled under certain customizable conditions, such as on an error. Choosing it gets you something like this:
Figure 18
When we got here we had a message indicating that there were too few arguments to MAPCAR (the one immediately preceding the ERROR in the Backtrace). The Backtrace shows us everything that had been evaluated until the error occurred, including Allegro’s top-level loop, all the way down to the function that reported the error.
Each function has a frame in which bindings are stored, and those frames can be examined by selecting the function whose frame you’re interested in. In the case of the MAPCAR that had the proper number of parameters passed (or so we think--MAPCAR takes optional parameters), the frame shows us three things: a compiled lexical closure that doesn’t have a name (because it was written as a LAMBDA expression and passed straight to MAPCAR) and two lists, the first (reading from the bottom up) of which contains the atom “PHONE” and the second of which contains several lists of atoms. Clicking on one of the values shown here will assign that value to the global variable “*DEBUG-VALUE*” so that you may do with it whatever you wish or need to aid in debugging.
“Documents ” is a shortcut to opening a file in the logical pathname “ccl-doc;” which contains several files documenting certain aspects of Common Lisp and Allegro CL in particular, including such Common Lisp esoterica as reader macros, the character set of the language, argument lists for exported symbols in the LISP and CCL packages, and so on. Some of these things are brought up automatically when other functions are performed (for example, a keystroke while the cursor is within most Common Lisp functions will get you its argument list in case you need reminding), but this is a good way to do it explicitly or to get other kinds of information.
Next is “Fred Commands.” This is a useful function not only because it can remind you of the standard FRED commands, but it can remind you of non-standard ones as well. I should explain that comment by pointing out that “FRED” stands for “FRED Resembles EMACS Deliberately.” The powerful EMACS editor was originally written as a collection of TECO macros, but was eventually made standalone by RMS (Richard M. Stallman), the last of the true hackers from MIT. EMACS has since been commercialized and rewritten (mostly in C) and become almost respectable, but for Allegro CL, FRED remains what EMACS really was: a programmable Lisp editor.
A key idea behind EMACS (and therefore FRED) is that a function can be bound to each and every key on the keyboard. The bindings are kept in a data structure called a COMTAB (command table). For most normal keystrokes the function simply inserts the proper ASCII value at the current buffer position, thereby making the keys behave as you would expect.
EMACS expects several other keystrokes to be available, though, thanks to two modifier keys, control and meta. The Macintosh keyboard historically has had command and option keys, with control being a recent addition not present on all keyboards.
For this reason, Allegro CL offers two modes of operation: Macintosh mode and EMACS mode. In Macintosh mode, the command key is the command key and shift-command is the control key. In EMACS mode, this functionality is reversed. In either case, the option key serves as EMACS’ meta key. If the keyboard has a control key, it will serve as the control key as well (on such keyboards, using Macintosh mode is a good idea).
Since EMACS can redefine the function of the keys, the “Fred Commands” function must determine what those functions are at run-time, and that is what it does. Here is what you see when you choose this function:
Figure 19
This isn’t a complete look (the scroll bar is active), but it will give you some idea as to what can be done in EMACS. Pressing the Enter key will evaluate the function “ED-EVAL-OR-COMPILE-CURRENT-SEXP,” which in English sounds like “Evaluate or compile the current symbolic expression.” In other words, depending upon the context, the s-expression pointed to by the cursor will either be eval’ed or compiled. Note that when EMACS says Enter, it means Enter, not Return.
The Backspace key evaluates “ED-RUBOUT-CHAR,” which makes sense. Meta-Backspace does “ED-RUBOUT-WORD,” which in light of what the prefix “meta” literally means also makes sense. Control-meta-Backspace eval’s “ED-DELETE-BWD-DELIMITERS,” a function that deletes backwards until it finds a matching delimiter (good for removing entire parenthesized expressions).
The function for the Tab key is interesting: “ED-INDENT-FOR-LISP.” The implication is that you could bind a function to Tab that would indent for C, or for Pascal, or what have you, and of course, that is the case.
EMACS also supports the concept of a “kill-ring,” which you can conceptualize as kind of round-robin clipboards. The most recent kill-ring is the clipboard, as a matter of fact, which brings us back to the “Edit” menu and “Insert Killed String ” This menu item will open a dialog showing the entire kill-ring, and allow you to select any of the available killed strings to be pasted into the current buffer. Using the standard “Paste” menu item will use the most recent string.
The “Print Options ” and “Environment ” choices allow you some control over the Lisp world that you live in. You can choose to pretty-print or not, to print circular structures or not (to do so can hang your machine, however), to use EMACS mode or not, and so on. Note that many of these values can be set in the “init.lisp” file that Allegro CL loads automatically when it is launched. FRED programming can also be done within “init.lisp” for further customization of the environment. Menus can be added and/or changed from “init.lisp” and so forth.
Another great feature of Allegro CL is their single-stepper. The “STEP” function is defined in the Common Lisp standard, but the implementation is left pretty much up to the individual Common Lisp vendor. Allegro CL’s stepper is wonderful. Suppose that I have a function called “FIND-ROUTE” that, when given a maze in the form of a list of connected rooms, a starting point, and an ending point, prints the most efficient route(s) to get from start to finish. Let’s also suppose that I have a maze whose symbolic name is “CASTLE,” with starting point “K” and ending point “PHONE.” To step through this function, I would type:
(step (find-route castle ‘k ‘phone))
and would see something like that shown in figure 20, after a few steps.
Figure 20
The stepper tells things the way they really are, which can be confusing at times. For example, I practically never use “IF” in Lisp, preferring the more complex, but more flexible “COND” instead. Allegro CL fools me--“COND” is a macro which translates to nested “IF”s, which is why you see “IF”s in figure 20 and none in the Common Lisp code below.
The stepper also evaluates things that are about to be used in a conditional so that you can see why the conditional is about to evaluate to what it is about to evaluate to. In the above example, the code asks “(ATOM STRUCTURE),” so the stepper obligingly mentions that “STRUCTURE” is a list of lists, and so the question “ATOM” is answered “NIL.”
The stepper has several functions. The most obvious one is “step.” A step in the function can also be “skip”ped. You can “eval” something in the current lexical environment, should you need to. You can also “replace” a step with another expression. You can issue a “break” in case you need to do something like look at a Backtrace. You can “finish” stepping and let the function complete without further ado, or you can “quit” stepping and cancel the current function’s evaluation. For any of these choices, you can also keep the current contents of the stepping window after the stepping is over in case you need to scroll through it to see what was going on. As a newcomer to Common Lisp, I have found this stepping power to be indispensable.
Believe it or not, I’m finally finished talking about the Common Lisp programming environment. For the next (and last) few pages, I’d like to point out some differences between MacScheme and Allegro CL as dialects of Lisp rather than as programming environments.
Scheme and Common Lisp both have similar roots. The commonality of them probably stems from basically one person who was highly influential in the creation of both dialects: Guy L. Steele, Jr. Both Scheme and Common Lisp are lexically scoped languages, which sets them apart from older dialects of Lisp, which were dynamically scoped. Both Scheme and Common Lisp lend themselves to solving thorny problems in artificial intelligence, logic, mathematics, and other symbolic tasks. Both dialects were specified with speed in mind (e.g. to be a complete Common Lisp, an implementation must include both an interpreter and a compiler).
Scheme differs from Common Lisp and other Lisp dialects by making functions first-class data objects, i.e. by putting them on equal footing with everything else. LAMBDA expressions in Scheme evaluate to a function, which can be assigned to a variable, passed to MAPCAR or other functions that take a function as a parameter, etc.
Common Lisp doesn’t make it quite that simple. If you intend to pass a function to another function, you’d better call it a function first, even if it is obviously one (because it’s a LAMBDA expression or whatever).
Scheme’s simplicity allows it to support direct access to some relatively deep internal structures, such as continuations. For us C and Pascal folks, we might want to think of continuations as being analogous to stack frames. A continuation represents the state of a function’s execution as it calls other functions and they make their continuations, etc. In Scheme you have explicit access to the current continuation, and can pass it as a parameter to a function that you define. This is a good way to implement escaping from loops, blocks, and procedures, but can also be used to implement fancy control structures such as agendas in artificial intelligence applications. (I cannot tell a lie; the last sentence is a paraphrase of one found in MacScheme+Toolsmith’s excellent documentation.)
MacScheme+Toolsmith offers a lot to wander through with their library files and files that define access to virtually all of Inside Macintosh. There’s multitasking support, access to machine code definitions for those tricky or time-consuming processes, and standalone application support. MacScheme+Toolsmith may have little in the way of user interface, but it is an excellent example of the elegance and simplicity of Scheme as a dialect of Lisp, and with its Inside Macintosh, multitasking, and machine code support, it packs a lot of power into a very small package. To top that off, they haven’t forgotten the beginners out there: each copy of MacScheme+Toolsmith includes a copy of Dan Friedman and Matthias Felleisen’s The Little LISPer, Trade Edition, from The MIT Press. I consider this book to be the beginner’s platform for learning Lisp (although the trade edition, which eventually covers such things as the applicative-order Y-combinator and writing a Lisp interpreter in Lisp, does get well beyond the beginning stages).
Scheme also enjoys great popularity in some academic computer-science circles, such as MIT and Stanford.
Common Lisp isn’t as conceptually pure as Scheme, perhaps partly because it was designed by committee. It does, however, have a very broad and very rich background, having drawn on ideas from many other dialects of Lisp, including ZetaLisp (the dialect used on Symbolics workstations), MacLisp (developed at MIT for Project Mac--not Macintosh-related at all), InterLisp (found on some Xerox Lisp machines, among other places), and yes, including Scheme as well.
Common Lisp is intended to be a good computational language, with support for everything up to and including complex numbers. In Allegro CL, support for BigNums seems quite speedy compared to MacScheme’s BigNum support, and Allegro uses the MC68881 (if one is present) for its floating-point calculations.
Some data types and functions that Scheme lacks are present in Common Lisp. For example, support for sets is part of the Common Lisp standard, as will be seen in the sample code below.
Common Lisp is being widely accepted, partially because it does cover so much intellectual turf, partially because there are many implementations of it available, and partially because a lot of well-known people and companies in the industry had input on it (including some who are in somewhat bitter competition with each other, such as Symbolics, Inc. and LMI).
To sort of highlight one or two of the differences between Scheme and Common Lisp, I’ve taken a program that was originally written in Scheme and translated it to Common Lisp so that we can look at them side-by-side. The program was originally written in MacScheme by André Marc van Meulebrouck, and is copyrighted by him. He generously posted it to the GEnie network for the rest of us Lispers to enjoy and learn from. Its purpose is, given a data structure that represents a maze as a list of lists of connected rooms, a starting room, and a destination room, to print the most efficient route(s) for getting from start to destination. Here’s the Scheme code:
{1} ; Copyright (c) 1986 by Andre’ Marc van Meulebrouck. ; All rights reserved worldwide. ; ; _________________________________________ ; | _|_ | _|_ | ; | ___ M | L ___ K | ; | | | | | ; | |____| |_____ |__| |__|__| |_| ; | | | | | | ; | | PHONE | | N | J | ; | |___________|__| |__| |_| |_|_ | ; | | _|_ _|_ _|_ | ; | O | ___ H ___ I ___ | ; | | |___| |____|______|____| ; | _|_ A _|_ _|_ _|_ | ; | ___ ___ D ___ E ___ | ; | | |_________|_______| | ; | | _|_ | F | ; | | ___ C | | ; | |____| |____|________| |____|__| |_| ; | | _|_ | | ; | _|_ X ___ B | G | ; | ___ | | | ; |____|___________|_______________|______| ; (set! castle ‘((a x) (a c) (a d) (a o) (a h) (m o) (x o) (xb) (b c) (d e) (e f) (f g) (k l) (ji) (i h) (i n) (n l) (n h) (h d) (mh) (k j) (m phone))) ; (define (find-route structure start goal) (define (find-route-aux routes) (define (goal? routes goal) (member #!true (appender (mapcar (lambda (x) (cond ((member goal (last x)) (list #!true)) (else #!null))) routes)))) (define (find-route-io x) (cond ((null? x) #!true) (else (display (car x)) (newline) (find-route-io (cdr x))))) (define (goals-only routes goal) (appender (mapcar (lambda (x) (cond ((equal? (car (last x)) goal) (list x)) (else #!null))) routes))) (cond ((null? routes) #!null) ((null? (goal? routes goal)) (find-route-aux (rid-dead-ends (make-routes routes)))) (else (find-route-io (goals-only routes goal))))) (define (appender x) (cond ((null? x) #!null) (else (append (car x) (appender (cdr x)))))) (define (make-routes routes) (define (make-routes-aux route) (define (choices room) (define (set-difference set1 set2) (appender (mapcar (lambda (x) (cond ((member x set2) #!null) (else (list x)))) set1))) (define (extricate x) (cond ((null? x) #!null) ((member (car x) (cdr x)) (extricate (cdr x))) (else (append (list (car x)) (extricate (cdr x)))))) (set-difference (extricate (appender (mapcar (lambda (x) (cond ((equal? (car x) room) (cdr x)) ((equal? (cadr x) room) (list (car x))) (else #!null))) structure))) route)) (appender (mapcar (lambda (x) (list (append route (list x)))) (choices (car (last route)))))) (appender (mapcar (lambda (x) (make-routes-aux x)) routes))) (define (rid-dead-ends routes) (appender (mapcar (lambda (x) (cond ((null? (car (last x))) #!null) (else (list x)))) routes))) (define (last x) (cond ((null? x) #!null) (else (list (car (reverse x)))))) (cond ((or (atom? structure) (null? structure) (pair? start) (pair? goal)) #!null) ((equal? start goal) goal) (else (find-route-aux (rid-dead-ends (make-routes (list (list start))))))))
There it is, in all its splendor. Notice the heavy use of implicit iteration via MAPCAR. Also notice the use of LAMBDA expressions to pass functions to MAPCAR on the fly, without the bother of defining them and assigning them to a symbol and whatnot. Also note that what the LAMBDA expressions evaluate to is already a function ready to go to MAPCAR. No additional sugar is necessary.
Here’s my Common Lisp translation of the same code:
{2} ; Copyright (c) 1986 by Andre’ Marc van Meulebrouck. ; All rights reserved worldwide. ; ; Common Lisp translation by Paul F. Snively ; ; _________________________________________ ; | _|_ | _|_ | ; | ___ M | L ___ K | ; | | | | | ; | |____| |_____ |__| |__|__| |_| ; | | | | | | ; | | PHONE | | N | J | ; | |___________|__| |__| |_| |_|_ | ; | | _|_ _|_ _|_ | ; | O | ___ H ___ I ___ | ; | | |___| |____|______|____| ; | _|_ A _|_ _|_ _|_ | ; | ___ ___ D ___ E ___ | ; | | |_________|_______| | ; | | _|_ | F | ; | | ___ C | | ; | |____| |____|________| |____|__| |_| ; | | _|_ | | ; | _|_ X ___ B | G | ; | ___ | | | ; |____|___________|_______________|______| ; (setq castle ‘((a x) (a c) (a d) (a o) (a h) (m o) (x o) (xb) (b c) (d e) (e f) (f g) (k l) (ji) (i h) (i n) (n l) (n h) (h d) (mh) (k j) (m phone))) ; (defun find-route (structure start goal) (defun find-route-aux (routes) (defun goalp (routes goal) (member T (appender (mapcar (function (LAMBDA (x) (cond ((member goal (last x)) (listT)) (T NIL)))) routes)))) (defun find-route-io (x) (cond ((null x) T) (T (print (car x)) (find-route-io (cdr x))))) (defun goals-only (routes goal) (appender (mapcar (function (LAMBDA (x) (cond ((equalp (car (last x)) goal) (list x)) (T NIL)))) routes))) (cond ((null routes) NIL) ((null (goalp routes goal)) (find-route-aux (rid-dead-ends (make-routes routes)))) (T (find-route-io (goals-only routes goal))))) (defun appender (x) (cond ((null x) NIL) (T (append (car x) (appender (cdr x)))))) (defun make-routes (routes) (defun make-routes-aux (route) (defun choices (room) (defun extricate (x) (cond ((null x) NIL) ((member (car x) (cdr x)) (extricate (cdr x))) (T (append (list (car x)) (extricate (cdr x)))))) (set-difference (extricate (appender (mapcar (function (LAMBDA (x) (cond ((equalp (car x) room) (cdr x)) ((equalp (cadr x) room) (list (car x))) (T NIL)))) structure))) route)) (appender (mapcar (function (LAMBDA (x) (list (append route (list x))))) (choices (car (last route)))))) (appender (mapcar (function (LAMBDA (x) (make-routes-aux x))) routes))) (defun rid-dead-ends (routes) (appender (mapcar (function (LAMBDA (x) (cond ((null (car (last x))) NIL) (T (list x))))) routes))) (cond ((or (atom structure) (null structure) (not (atom start)) (not (atom goal))) NIL) ((equalp start goal) goal) (T (find-route-aux (rid-dead-ends (make-routes (list (list start))))))))
It looks much the same, doesn’t it? If you look closely, you’ll see a lot of minor syntactical differences, such as the use of “T” instead of “#!true.” More importantly, however, you’ll notice that the definition of “set-difference” is missing, since Common Lisp already has it, and you’ll notice the “(function )” around the LAMBDA expressions, because Common Lisp wants it that way. Also, there’s no “pair?” in Common Lisp, so the question was “What is he really looking for?” and the answer was “He wants to make sure that the start and destination are atoms.” Hence the “(not (atom ))” lines near the end.
Someone who is a bit more of a “Common Lisp Rambo” than I am at the moment might think in terms of restructuring this program so that you can eliminate the definition of “appender” and replace the numerous “(appender (mapcar ))” invocations with “(mapcan )” ones. The problem with that is that “MAPCAN” is a destructive operator (one that performs surgery on lists, rather than copying them) that can exhibit some pretty weird side-effects if you aren’t careful with it. The optimization is left as an exercise for the reader.
Well, that wraps up this look at two excellent Lisp systems for the Macintosh. I expect to see both of these products evolve over time. MacScheme has been evolving, and has become a product that we Scheme fans can really appreciate, especially with its good documentation, large amounts of source code to tinker with and learn from, and its support of the Toolbox, multitasking, machine code interfacing, and standalone applications.
Allegro CL is remarkable as the first full Common Lisp for any mass-marketed microcomputer, and for its extensive and powerful user interface. It also offers excellent Toolbox access and several interesting example programs, including color examples and Cosell, a Common Lisp spreadsheet. The lion’s share of the documentation is Common LISP: The Language, which in Common Lisp circles is referred to as “The Silver Bible.” A copy is included with the product. Also included is a copy of Common LISP: The Index, by Coral’s own Rosemary Simpson, which attempts to improve (and succeeds at improving) upon the index that is in the book. The remaining documentation is a somewhat terse description of Allegro CL specifics that would benefit from a less dry, “just the facts, ma’am,” more lively style and from more examples. Issues remaining to be resolved are multitasking, interfaces to other languages, and standalone applications. However, Coral has clearly positioned this product as being in line with the likes of Symbolics’ work, especially with the introduction of Allegro Flavors and the promised support of CLOS and Common Windows.
I’m lucky--I’ve got ’em both. Other folks might not be so lucky. If you’re looking for a Lisp system, both are recommended highly, albeit for different reasons. Scheme fans, MacScheme+Toolsmith will let you do just about anything you want, up to and including creating a standalone application that you can then sell or give away or whatever royalty-free.
If you use Common Lisp professionally and have been looking for a real Common Lisp for your Macintosh, Allegro CL is it. It’s 100% bona-fide genuine Common Lisp, not some little subset. Speaking of which, if you’re looking for a good, inexpensive subset, keep your eyes on Coral, who are talking about doing “Coral Lisp,” a subset of Allegro CL, for about $100.00. In fact, it should be out by the time you read this. This sounds like it’ll be a great entry-level system for the first-time Lisper. Maybe Coral will take a hint from Semantic Microsystems and bundle The Little LISPer with their introductory Lisp.
MacScheme+Toolsmith 1.0
$395.00
Semantic Microsystems, Inc.
4470 S.W. Hall, Suite 340
Beaverton, OR 97005
(503) 643-4539
Allegro CL 1.1
$599.95
Coral Software, Inc.
P.O. Box 307
Cambridge, MA 02142
Orders: (800) 521-1027
Tech Support: (617) 547-2662
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine