Posted October 02, 2024 by screwtape
#common lisp #programming #mcclim #presentation-types #REPL #show-adjacent
My pre-recorded episode on https://anonradio.net today was about the presentation concept of types in common lisp (interface manager).
I got a bit muddled trying to jam during the show, so here's everything in slow motion hashed out after for you.
If you need to start lisp as a beginner, read an article like my friend Andrew Kravchuk's. I kinda assume you can start lisp. I get that it's hard for people from other languages.
Lisp happens interactively. Again, read Andrew.
This is the digest version of how I (automatically) started lisp:
ecl
(require "asdf") (asdf:load-system :mcclim)
(load #p"~/.emacs.d/slime/start-swank.lisp")
(slime-connect "localhost" 4005)
Onwards to making the type theory people weep tears and gnash teeth!
(in-package :clim-user)
User packages are made to absorb package users' (your) creations. The gist is that exported symbols of the package you're using are mixed to become internal symbols of the -user package, so you can use it as intended but not accidentally break anything.
(defvar *tree-san* (make-instance 'standard-tree-output-history))
CLIM's central concept is updating panes by replaying its mutable output history. This is the bit we want to claw at.
By the way, we're using these naked as just in-memory streams without trying to draw them. We could graft it into a frame where it hooks up with machinery to paint itself, but here now I am specifically not doing that. We've got to get its vibration.
(defvar *inter* (make-instance 'interactor-pane :output-record *tree-san*))
While there are no restraints on using either for either, interactor-panes are informally identified with standard-input and application-panes with standard-output, so let's just use them as though they're like that.
(defvar *appli* (make-instance 'application-pane :output-record *tree-san*))
Now we have two panes (in-memory streams in some sense) that we are kinda pretending are a front and back.
Remember present is like a type-aware advancement of print.
(present '("foo" "bar") '(sequence string) :stream *inter*)
(let ((*standard-output* *inter*)) (present '("foo" "bar")))
Clim tries to guess the presentation type we want if we don't tell it, and we lexically shadowed the default stream (standard-output) with our inter.
Oh, interesting. It guessed (sequence t) instead of (sequence string). (t is a catch-all presentation-type).
Remembering (I forgot in the show) that we are using appli as our output
(map-over-output-records
(lambda (x)
(terpri)
(present (presentation-object x)
(presentation-type x)))
(stream-output-history *appli*))
So the leaves are of presentation-type (and CLOS class) PRESENTATION, which carries an object, being the data and a presentation-type, being how the data was presented to it. I don't know if you had one of those scarves that could be worn as a scarf or as a hat as a child, but the presentation-type carries the extra information that the scarf is being used as a hat, which is absent from simply saying you have a scarf. Of course it could be reconfigured to being a hat. Making reconfigurability intrinsic is an interesting and non-trivial innovation I attribute to clim. I'm looking forward to hearing from you, type theorists.
Here, I choked a bit during the show. Sorry dinner was late.
(define-presentation-type tree-presentation () :inherit-from '(presentation))
It's really convenient to derive from an existing presentation-type. Here I tried to get a new standard-tree-output-record into its children by presenting one to it, but I ended up with a presentation whose data was the output record rather than just the output record, which flummoxed me. So we'll define its presentation-type
We have to be able to distinguish members of this type!
(define-presentation-method presentation-typep :around (object (type tree-presentation)) (and (call-next-method) (typep (presentation-object object) 'output-record)))
My tree-presentation satisfies however the presentation presentation-type is established with call-next-method, and adds the requirement that its presentation-object is suitable for map-over-output-records (ie is a node).
After some thought, I generalised standard-tree-output-record to just be output-record, since it could also be a standard-sequence-output-record.
(defvar *traversal-indent* 0) (defvar *traversal-spacing* 4) (defun traverse (output-record) (map-over-output-records (lambda (x) (terpri) (format t "~@,,v,' a" *traversal-indent* #\{) (cond ((presentation-typep x 'tree-presentation) (incf *traversal-indent* *traversal-spacing*) (traverse (presentation-object x)) (decf *traversal-indent* *traversal-spacing*)) ((presentation-typep x 'presentation) (present (presentation-object x) (presentation-type x)))) (princ "}")) output-record))
It's a bit annoying that there isn't a presentation-typecase macro by clim, though perhaps this is meant to be done by type specifiers on present in the first place.
Let's just make a different output-history for a different pane
(defvar *branch* (make-instance 'standard-tree-output-history)) (defvar *branch-pane* (make-instance 'interactor-pane :output-record *branch*)) (present "Beeblebrox" 'string :stream *branch-pane*) (let ((*standard-output* *inter*)) (present *branch*))
I guess deeper would be more interesting, and maybe a way to detect circularity.