A portable Common Lisp toolkit for building inspectors
github.com
github.com
I was excited to learn how building inspectors were using Common Lisp!
But then I thought, that’s still pretty cool and I went word tripping for free.
(I recall once talking to a java dev who considered perl a complete disaster for debugging because ... whatever tool he'd picked to help him produced debug output that suited neither the codebase nor his brain, and the java code he was working with at least managed representations that suited the codebase ... meanwhile, I often get frustrated at how tricky I found it to convince java to let me have multiple different debug stringifications ... such is life ;)
There are certainly other interactive languages, there are other interactive object oriented languages, but few rose to the level of Lisp when it became not just the systems programming language of specialized operating systems, but also the command shell and development tool for such systems.
It was also during a time where keyboard interaction was still the primary means of interface. Even with the creation of the Lisp machines and their large graphic displays, those displays were dominated by "terminal" windows.
Java has "toString". But Java has no heritage of interacting with it via a terminal interface. In the modern world of graphic displays, windowed environments, and IDEs, those larger tools expose the objects of the JVM (which are made quite transparent by the debugger infrastructure). Outside of "toString", there's not much call for language level specialized handling. IDEs did all of the heavy lifting.
I, too, have done things like "toDebugString" or something equivalent, that I could put in a watch expression in the debugger. And, that was "enough".
In Lisp, Lisp was "the tool", it was "the debugger", there wasn't really a separate artifact peering into the image. So, it required the ability to do all of that in and of itself.
A few years later, and Lisp likely would not have done this. Even Smalltalk doesn't do this. Its inspector is a generic tool. But ST was mostly a GUI based system. And, sure, you can just override STs inspect method. I'm guessing most don't.
In Symbolics Genera a Lisp Listener (the window which runs a combination of a REPL and a command line interpreter) is a full graphics window with the same drawing capabilities as PostScript. It's also a command interpreter taking Lisp objects as arguments to commands.
The UI isn't using much direct manipulation with the mouse (like dragging a file between folder windows), though for example in the graphics editor one can do that for graphics objects. One also interacts with graphical scrollbars. Much of the UI is based on displaying textual and/or graphical objects AND running commands on them. Application windows all have a (often visible) command interpreter, where interacting with objects is done via commands on objects (and not only on text), using textual commands, mouse gestures or command menus.
The Listener (as do other applications) also records all output and keeps the relationship with the objects which a drawn/printed.
Thus the Genera UI does not use "toString", but calls PRESENT (or similar), a function which outputs Lisp objects in various ways, records the output (text and graphics) and keeps the relationship between object and output. Thus the Listener is not only a graphical stream, but also a graphical object store (-> it stores all output using a complex object-oriented framework). (similar -> McCLIM -> https://mcclim.common-lisp.dev -> example for tools/apps https://mcclim.common-lisp.dev/excite.html ).
One can return/print a data structure in the Listener and then one can point with the mouse at sub-objects and run commands on them (like 'describe'). This makes a separate inspector mostly unnecessary, though there is a inspector window. As there is a debugger window with backtrace, code display, etc. There is also a PEEK window, which is used to inspect things in the OS: Windows, File Systems, Memory, Processes, Network, ...
(for those following along at home, Interlisp was a work of art and I cached a couple of the relevant manuals under https://trout.me.uk/lisp/ so I didn't keep having to go find them yet again to re-read for inspiration)
(defun inspect-in-slime ()
(interactive)
(slime-inspect
(format "%S" `',(eval
(elisp--preceding-sexp)))))
(add-hook 'lisp-interaction-mode-hook
(lambda ()
(local-set-key (kbd "C-c I") #'inspect-in-slime)))
You only cover a subset of your Lisp with this (without additional work), but it's a useful* subset and you get it essentially for free, for anything that "looks close enough" like a Lisp. And you get SLIME's polished, feature-rich inspection TUI, also for free.*(Particularly in Elisp, since Emacs uses lots of large cons-cell data structures that are very unpleasant to read!)
> `trivial-inspect` exposes a set of utils useful in building inspectors akin to standard `inspect` and `describe`.
Here is the documentation for `inspect`, <https://www.lispworks.com/documentation/HyperSpec/Body/f_ins...> and `describe`, <https://www.lispworks.com/documentation/HyperSpec/Body/f_des...>.
I’ll quote the relevant bits here. First `describe`:
> `describe` displays information about object to stream.
> For example, `describe` of a symbol might show the symbol's value, its definition, and each of its properties. `describe` of a float might show the number's internal representation in a way that is useful for tracking down round-off errors. In all cases, however, the nature and format of the output of `describe` is implementation-dependent.
> `describe` can describe something that it finds inside the object; in such cases, a notational device such as increased indentation or positioning in a table is typically used in order to visually distinguish such recursive descriptions from descriptions of the argument object.
And finally `inspect`:
> `inspect` is an interactive version of `describe`. The nature of the interaction is implementation-dependent, but the purpose of `inspect` is to make it easy to wander through a data structure, examining and modifying parts of it.
Essentially these are built–in methods of introspection. One particularly nice thing about them is that `describe` calls a generic function named `describe-object`. Because this is a generic function, you can add specialized methods to it for your own classes. This lets you customize the output to best suit your own needs.
In this case, the standard only requires high level functions "inspect" and "describe", but most implementations have packages (SBCL provides a "sb-introspect") to get more detail.
It's generally done for things like threading, garbage collection settings, CFFI, etc. that would be difficult to implement outside of the compiler or runtime.