23 September 2006
While strange at first, only now am I beginning to understand the Lisp way.
So far, I’ve yet to uncover a way to get line numbers from a debugger in SLIME via ACL, OpenMCL, CLISP, ECL or SBCL.
The Lisp way is that you’re actually interacting with the Condition System. http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
The idea is odd at first but elegant: think of GUI software architecture use of model-view-controller pattern. Now apply a similar concept to exception handling that’s much more powerful than throw-catch. While Lisp also offers thow-catch, Conditions allow you to keep multiple, various recovery methods along with the same body of code where the error was triggered.
So when SLIME gives that odd error prompt listing various choices, and at first we might tend to just take the “slime abort” option, had we invoked an error with proper conditions, we’d get a much richer selection of options.
For example, if it were HTTP client code, you might reach an error: host not found. But there might be a handler for “try again but with prefix of ‘www.’ in front of domain name.” And so on.
That paper mentioned above does a better job at an overview.
Hopefully the Condition system of lisp makes sense. There’s a slightly different take on it within Practical Common Lisp and probably more in either of Graham’s books as compared to that article in my last message.
Also, with macros, you can use conditional definitions so that for an optimized runtime, you would omit custom assertions.
I’ve found my coding style to be much cleaner when peppering with printf because those are more likely to have accurate statements than comments after code matures. So now, my coding ethos involves: if i’m inclined to add a comment for clarity, I’ll instead add an assert or conditional debug print statement with meaningful description. (That is, of course, while following a literate programming style where identifiers are verbose and meaningful.)
The next piece to this puzzle is one of methodology.
I’ve been doing it all wrong, as I suspect you have too:
We’ve been doing the familiar edit-compile-run cycle, but where we’ve gone wrong is doing it on a file level…
…rather than function level!!!
That hopefully triggers a recollection of Graham’s and Gabriel’s articles.
When they speak of lisp development cycles as being interactive, it’s nothing like using the Python or Ruby interpreters. While, yes, that’s one way of doing it, it’s not quite what they mean.
Unleash the full power of SLIME:
And hence, that’s /one/ meaning of “functional programming” where each function should be more or less stand-alone, so line numbers are less significant.
Also, regarding lack of line numbers, just as you and I probably strive to keep our functions small enough to fit within a single page on screen, think about 1985 and doing that with a 24 line green screen terminal or earlier with a paper teletype.
Now, dig deeper.
What does this “functional programming” really mean?
Well, you’ve done some GUI work, so you undoubtedly know “model-view-controller”, yes?
Think of the event-handling code within the controller piece: it merely dispatches calls into the model. If the model– the main reason for the app to exist– is done in a functional programming way, it can be essentially just a set of cascading transforms.
(If you’ve ever seen a modern 3D modeling tool, they literally have an editable stack of “transforms,” and that really drove the point home for me. Seek out someone using Softimage, Maya or 3ds Max, all currently products under AutoDesk.)
I see this as a parallel to the way #‘let works and special vars like those from #'defparam: first, Lisp uses special variables rather than globals as in Perl or C; second, these “bindings” are contextual in that different threads of execution could see different values for the same named “global” variable.
So too with the functional programming approach especially when mixed with m-v-c in a GUI app: the mode triggered from a menu selection provides a context much like #‘let offers.
Within the GUI app, you’d still need to tag your content with the transform queue. In a modern word processor, for example, you’d want to track the sequence of transformations for each point, and thenn before displaying that bit of text, you’d apply the transform queue. Certain optimizations may be applied so as to minimize the stack, but the approach remains the same.
So my understanding of “functional versus OO-as-changing-program-state” debate comes down to this:
It’s not an all-or-nothing deal.
“Functional” means creating a list of transforms. Modern use of “OO” in C++/Java tends to mean creating a custom data type for every little thing inside the program (or worse, making the entire program a single data type), but relax that concept just a tad (i.e., undo the 1990’s). Rather than inventing data types, keep it as data with methods.
So then, Functional Programming consists of these “data with methods” as contextually safe global variables, which provide metadata to a sequence of transforms.
At least, it’s the Lisp way for this new wave Lisp geek.
It’s no wonder that the first gen Lisp hackers wouldn’t settle for mere message-passing style of OO and fought hard for generic programming. CLOS is the mechanism by which you get that list of transforms (and I say “list” rather than “queue” specifically because of :before, :after and :around minipulators).
But back to debugging:
No, I don’t know how to enable line numbers, yet that is a feature of AllegroCL, LispWorks and CormanCL. But is $1,100 for LispWorks or $2,500 for ACL ($5k on Solaris!) worth the price for debugging with line numbers? Maybe.
At first, I intended to just throw money at the problem. That’s back when I saw the starting price for acl7 was $599. Turns out, that was just the individual academic price (students and teachers– not even the school price).
At those prices, I think I’ll make a stronger effort at learning the Lisp way first.
So far, so good!
One trick, however is use of #‘unintern. When you attempt to run a function
that hasn’t been compiled yet, you may need to unintern it first before
calling it again. Otherwise, the system may have interned it as #
There’s a note in Practical Common Lisp about this in the chapter on “Packages and Symbols”.
And of course, I tend to do this a lot:
*slime-events*
This keeps slime from replaying my past mistakes from earlier in the development session.