What we get out of lisp


In some way, lisp's facility for its programmers to traverse three score miles and ten by candlelight appears to be very important.

This note seeks to describe some fast and dirty useage of the loop facility, formatted output, common lisp interface manager, common lisp object system and the condition system.

This note does not seek to address any advertisement you have received.

1. the loop facility

Lisp doesn't portably include tail-call style recursion optimisation because it makes debugging harder.

Lisp does have the loop facility. You should always choose the loop facility, and you should use all of the loop facility. Even if your compiler supports otherwise, write the loop keywords correctly as keywords.

(loop :initially
  (defvar *ht* (make-hash-table))
  (loop :for list :in '((:foo bar)
                        (baz 1)
                        (buz "biz"))
        :for (key val) := list
        :for x :from -3 :by 3
        :unless (oddp x)
          :collect val :into vals
        :else
          :collect x :into vals
        :nconcing `(,key) :into keys
        :finally
           (loop :repeat 2
                 :for k :in keys
                 :for v :in vals
                 :for k.v := (cons k v)
                 :Do
                    (setf (gethash k *ht* ) v)))
      :for hk :being :each :hash-key :of *ht*
        :using (hash-value hv)
      :for cons = `(,hk . ,hv)
      :collecting cons :into alist
      :finally (return (cons :2-entries-as-alist alist)))

Okay, it's a bit silly and there's plenty of loop I didn't scratch. The loop facility is lisp's domain specific language for iteration. Trying to roll your own low power iteration is unidiomatic.

Put your cursor (?) on the word loop (yes, that one) and press M-x c-l-hy <ret>

That's what I referred to to check on the hash-table stuff.

2. Formatted output

Lisp has a domain specific language for formatting data as text. Don't ever not use it.

  (format nil "~@(~{~a~^ ~}!~)~% ~
~@(the ~a ~r is #o~@2*~o in~) ~a."
          '(what ho) "NUMBER" #x10 '|the natural base|)

Formatted output is also featurose (https://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm to save you clicking the right hyperlink in format's page).

Here's an example of ~/../ and ~? to get you started.

(in-package :cl-user)

(defun reverse-digits (stream arg colon at)
  (print (list stream arg colon at))
  (let ((string (format nil "~?" (cond ((and colon at) "#x~x")
                                       (colon "#b~b")
                                       (at "#o~o")
                                       (t "~d"))
                        arg)))
    (format stream "~a" (reverse string))))

(format t "~@/cl-user:reverse-digits/~?"
        '(#o46) "~:/cl-user:reverse-digits/" '((52)))

3. clim (mcclim)

I'm going to refer you to mcclim's codeberg for how to get mcclim. https://codeberg.org/mcclim/mcclim

Clim spec implementations generate interfaces for lisp programs. Strandh told me that the most important idea in clim was its presentations (there are different ways to present the same piece of data). Clim works by recording and replaying output (presentation) records.

You normally have an interactor, maybe and menus and buttons of various kinds. The interactor is a command shell. I identify commands as more-or-less satisfying Sandewall's desire for a language to have a first class notion of actions. If you ask me, commands, and command tables are my most important feature of clim. Though I guess one presents commands.

(ql:Quickload :mcclim)

(in-package :Clim-user)

(define-application-frame number-application
    ()
  (current-no)
  (:panes
   (title :title :title-string "Great number application")
   (int :interactor)
   (no-display :application :display-function 'display-no-display))
  (:layouts
   (default (vertically () title
                        (horizontally () int no-display)))))

(defun display-no-display (frame pane)
  (with-slots (current-no) frame
    (present current-no 'number :stream pane)))

3.1. Make one.

(in-package :clim-user)
(defparameter *no-app* (make-application-frame 'number-application))
(run-frame-top-level *no-app*)

3.2. A new command

(in-package :Clim-user)
(define-number-application-command (com-change-no :menu t :name "change no")
    ((no 'number))
  (with-slots (current-no) *application-frame*
    (setf current-no no)))

3.3. Another way of starting (an anonymous one)

(in-package :clim-user)
(find-application-frame 'number-application)

Notice that the restart skip redisplay lets the app continue functioning in a variety of problem scenarios.

try executing the command change no in various ways:

  • Press the menu button
  • Type change no and press enter
  • Type change no 42 and press enter

4. Common lisp object system

We get double service out of CLOS since application frames both have CLOS classes and mcclim is implemented using powerful CLOS useage. Let's separate out some lisp stuff from clim stuff in the example above. While I like many fine granularity classes, I'm obviously overworking this to give you a taste.

(defclass numbered ()
  ((current-no :accessor current-no)))

(defclass worded-number (numbered)
  (number-word))

(defmethod (setf current-no) :after (val (obj worded-number))
  (with-slots (number-word current-no) obj
    (setf number-word (format nil "~r" (truncate current-no)))))

(defclass inits-number (numbered)
  ((current-no :initarg :current-no))
  (:default-initargs :current-no 0))

(defmethod shared-initialize :after ((obj inits-number) slot-names &rest initargs)
  (with-slots (current-no) obj (setf (current-no obj) current-no)))

4.1. slightly varired frame def

(define-application-frame no-app-2
    (worded-number inits-number standard-application-frame)
  ()
  (:panes
     (title :title :title-string "extra classy app")
     (int :interactor)
     (no-display :application :display-function 'display-no-display)
     (word-display :application :display-function 'display-word-display))
  (:layouts
   (default (vertically () title
              (horizontally () int no-display word-display)))))


(defun display-no-display (frame pane)
    (with-slots (current-no) frame
      (present current-no 'number :stream pane)))

(defun display-word-display (frame pane)
    (with-slots (number-word) frame
      (present number-word 'string :stream pane)))

5. Let's make an instance with a starting number.

(defparameter *nf* (make-application-frame 'no-app-2 :current-no 42))
(push (find-command-table 'number-application)
      (command-table-inherit-from (Frame-command-table *nf*)))

(define-no-app-2-command (com-change-no-2 :menu t :name "change no 2")
    ((new-no 'number))
  (setf (current-no *application-frame*) new-no))

(run-frame-top-level *nf*) ; I changed it to 4.

(reinitialize-instance *nf* :current-no 72)

I said this was going to be quick and dirty.

6. Condition handling

I've only just started getting a good handle on this. Previously, I remembered conditions from suspecting that I was not using the usocket networking package correctly, rightly so as it turns out.

The best source on conditions is Kent Pitman's text file, Revision-18.txt : https://www.nhplace.com/kent/CL/Revision-18.txt

Really do go read that. It explains why we want these nonlocal exits, and what you are telling other programmers you are going to do for them when you signal typed conditions (spoilers; like skip-redisplay helped us in clim, it means that your program has a way of dealing with the signal interactive or non), and why not just have error strings.

</div>

Author: screwlisp as named by mousebot

Created: 2024-08-31 Sat 23:52

Validate

Leave a comment

Log in with itch.io to leave a comment.