25 Apr 2025
Planet Lisp
Joe Marshall: Get Over It (ai content)
I'm tired of people complaining about all the parentheses in Lisp, so I told Gemini to vent for me. This came out pretty good.
I suppose I'm guilty of contributing to the glut of AI slop, but while the text and tone are generated, the core idea and sentiment is mine, so it isn't pure slop.
Alright, let's drop the hand-holding. You - yes, you - the one still whimpering about the parentheses in Lisp. It's time someone told you bluntly: Get over it.
Your constant refrain about "too many parentheses" isn't some profound insight into language design; it's a confession of intellectual laziness. It screams that you encountered something different, something that didn't conform to the aesthetic you were spoon-fed, and you threw up your hands rather than engage your brain.
Are parentheses really an insurmountable cognitive barrier? Are matching pairs of symbols truly beyond your capacity to track? Let's be honest. The core issue isn't difficulty; it's unfamiliarity coupled with a stubborn refusal to adapt. You've decided what code ought to look like, and anything else is dismissed as "hard" or "ugly" without genuine effort.
Thousands upon thousands of programmers - people with brains presumably no bigger than yours - have not only managed Lisp's syntax but have thrived with it. They've built complex, powerful systems. They didn't possess some secret anti-parenthesis superpower; they simply possessed the willingness to learn and adapt. They saw the structure, the consistency, the power behind the syntax, instead of getting stuck on the superficial shape of the characters.
Your complaint isn't about usability; it's about preference, and a remarkably shallow one at that. It's like refusing to read a brilliant novel because you don't like the font. It demonstrates a lack of curiosity, an unwillingness to look past the surface.
So, here's the unvarnished truth: The parentheses aren't the problem. Your rigid thinking is. Your demand that every language cater to your pre-existing biases is.
Stop whining. Stop making excuses based on trivial syntax. Either put in the minimal mental effort required to see the structure beneath the curves, recognise the direct representation of the abstract syntax tree, and understand why it is the way it is - or don't. But don't pretend your aesthetic preference is a legitimate technical critique.
The Lisp world isn't waiting for you to overcome your parenthesis phobia. It's moving on, building things, leveraging a unique and powerful paradigm. You can either engage your intellect and join in, or you can stay on the sidelines complaining about the punctuation.
Frankly, it's time to grow up and get over the parentheses.
25 Apr 2025 7:00am GMT
24 Apr 2025
Planet Lisp
Marco Antoniotti: Emacs Lisp Programming with DeepSeek: A New Widget
The Emacs widget
library is useful; alas its guts are ... semi-documented and most of its inner working a bit mysterious. I wanted a column widget where I could insert and remove a few "line-like" widgets. The editable-list
widget does not cut it (too many extra things: the INS and DEL buttons) and the group
widget is too inflexible.
After too much time trying to understand all the intricacies of the widget
library (see my rant in my previous blog post, which perfectly applies in this case) I asked DeepSeek to help me out. The result, the dynamic-group
widget (after several iterations and mistakes on part of DeepSeek) is below. It works satisfactorlly, although it could be improved by anybody with a better understanding of the widget
library. What is does is to manage a colimn of line-like widgets adding and removing from the end of the :children
list. Check the demo-dynamic-group
for a test run.
It has been fun. Although I still want a better widget! That's why I am posting this for anybody to pitch in. Any help is welcome.
BTW. There still are some warts in the code. Can you spot them?
;;; Code: (require 'widget) (require 'wid-edit) (define-widget 'dynamic-group 'default "A container widget that dynamically manages child widgets in a column." :format "%v" :value () :tag "Dynamic Group" :args nil ;; Core widget methods :create (lambda (widget) (let ((inhibit-read-only t)) (widget-put widget :from (point)) (dolist (child (reverse (widget-get widget :children))) (widget-create child)) (widget-put widget :to (point)))) :value-get (lambda (widget) (mapcar (lambda (child) (widget-apply child :value-get)) (widget-get widget :children))) :value-set (lambda (widget value) (widget-put widget :value value)) :value-delete (lambda (widget) (dolist (child (widget-get widget :children)) (widget-apply child :value-delete))) :validate (lambda (widget) (let ((children (widget-get widget :children))) (catch :invalid (dolist (child children) (when (widget-apply child :validate) (throw :invalid child))) nil))) ) (defun dynamic-group-add (widget type &rest args) "Add a new widget (of TYPE and ARGS to the WIDGET group." (let ((inhibit-read-only t)) (save-excursion (goto-char (widget-get widget :to)) (let ((child (apply 'widget-create (append (list type) args)))) (widget-put widget :children (cons child (widget-get widget :children))) (widget-put widget :to (point)) (widget-value-set widget (cons (widget-value child) (widget-value widget))))) (widget-setup))) (defun dynamic-group-remove (widget) "Remove the last widget from the WIDGET group." (when-let ((children (widget-get widget :children))) (let ((inhibit-read-only t) ;; (child (car children)) ) (save-excursion (goto-char (widget-get widget :from)) (delete-region (point) (widget-get widget :to)) (widget-put widget :children (cdr children)) (dolist (c (reverse (widget-get widget :children))) (widget-create c)) (widget-put widget :to (point)) (widget-value-set widget (mapcar 'widget-value (widget-get widget :children))) (widget-setup))))) (defun demo-dynamic-group () "Test the dynamic-group widget." (interactive) (switch-to-buffer "*Dynamic Group Demo*") (kill-all-local-variables) (let ((inhibit-read-only t)) (erase-buffer) (widget-insert "* Dynamic Group Demo\n\n") ;; Now I create the `dynamic-group'. (let ((group (widget-create 'dynamic-group))) (widget-insert "\n") ;; The rest are just two buttons testing the widget's behavior, ;; invoking`dynamic-group-add' and `dynamic-group-remove'. (widget-create 'push-button :notify (lambda (&rest _) (dynamic-group-add group 'string :format "Text: %v\n" :value (format "Item %d" (1+ (length (widget-get group :children)))))) "(+) Add Field (Click Anywhere)") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest _) (dynamic-group-remove group)) "(-) Remove Last") (widget-insert "\n")) ;; Wrap everything up using the `widget-keymap' and `widget-setup' ;; functions. (use-local-map widget-keymap) (widget-setup))) (provide 'emc-dynamic-group)
'(cheers)
24 Apr 2025 8:52pm GMT
Joe Marshall: Lisp Debugger Wins
I'm gathering statistics from thousands of pull requests in GitHub. I wrote a little Lisp program to do that. It's taking a long time because it has to make a lot of API calls to GitHub and the calls are rate limited.
After about half an hour, I got an unexpected error. A user who made the pull request I was looking at had deleted their account. No problem. I used the Lisp debugger to walk up the stack to a convenient frame and returned NIL from that frame, causing that particular PR to be skipped. The program continued running from where it left off. I didn't have to restart from the beginning and lose a half hour of work.
The Lisp debugger for the win!
24 Apr 2025 6:00pm GMT
23 Apr 2025
Planet Lisp
Gábor Melis: PAX and DRef v0.4
Version 0.4 of PAX, the documentation system, and DRef, the definition reifier, was released. There were large refactorings, bug fixes, minor features, cosmetics, documentation and performance improvements too numerous to list. Here is a summary of the new features and notable changes.
-
DRef now supports
DTYPE
s, which allow filteringDEFINITIONS
andDREF-APROPOS
results according to the locative type hierarchy:(definitions 'print) ==> (#<DREF PRINT FUNCTION> --> #<DREF PRINT (UNKNOWN (:DEFOPTIMIZER PRINT SB-C:DERIVE-TYPE))> --> #<DREF PRINT (UNKNOWN --> (DECLAIM PRINT --> SB-C:DEFKNOWN))>)
(definitions 'print :dtype '(and t (not unknown))) ==> (#<DREF PRINT FUNCTION>)
The
AND T
bit restricts the query to definitions in the running Lisp. The top of theDTYPE
hierarchy isDREF:TOP
, which includes external definitions such as theCLHS
, that comes with PAX:(definitions 'print :dtype '(not unknown)) ==> (#<DREF PRINT (CLHS FUNCTION)> #<DREF PRINT FUNCTION>)
(dref-apropos "method" :package :dref :external-only t :dtype 'class) ==> (#<DREF METHOD CLASS> #<DREF METHOD-COMBINATION CLASS>)
The locative type hierarchy can be queried programmatically, and this information is included in their documentation (see for example the
GENERIC-FUNCTION
locative type). -
The PAX Live Home Page better supports exploration without having to leave the browser.
-
It lists packages grouped by ASDF systems that define them (when this can be determined from the source locations).
-
It links to apropos pages for each locative type.
-
It has an input box for looking up documentation right from the browser (as if with
mgl-pax-document
from Emacs). -
It has an input box for looking up apropos right from the browser (as if with
mgl-pax-apropos
from Emacs). -
The web server can be started without Emacs.
-
-
Completion of names and locatives in Emacs is much improved.
-
New aliases were added to the
CLHS
pages documenting format directives (e.g.~F
), standard macro characters (#A
) and loop keywords (sum
,:sum
,loop:sum
), so that one can justC-.
(mgl-pax-document
) them. See the documentation of theCLHS
locative. -
The DRef extension api has been cleaned up.
23 Apr 2025 12:00am GMT
21 Apr 2025
Planet Lisp
Paolo Amoroso: DandeGUI, a GUI library for Medley Interlisp
I'm working on DandeGUI, a Common Lisp GUI library for simple text and graphics output on Medley Interlisp. The name, pronounced "dandy guy", is a nod to the Dandelion workstation, one of the Xerox D-machines Interlisp-D ran on in the 1980s.
DandeGUI allows the creation and management of windows for stream-based text and graphics output. It captures typical GUI patterns of the Medley environment such as printing text to a window instead of the standard output. The main window of this screenshot was created by the code shown above it.
The library is written in Common Lisp and exposes its functionality as an API callable from Common Lisp and Interlisp code.
Motivations
In most of my prior Lisp projects I wrote programs that print text to windows.
In general these windows are actually not bare Medley windows but running instances of the TEdit rich-text editor. Driving a full editor instead of directly creating windows may be overkill, but I get for free content scrolling as well as window resizing and repainting which TEdit handles automatically.
Moreover, TEdit windows have an associated TEXTSTREAM
, an Interlisp data structure for text stream I/O. A TEXTSTREAM
can be passed to any Common Lisp or Interlisp output function that takes a stream as an argument such as PRINC
, FORMAT
, and PRIN1
. For example, if S
is the TEXTSTREAM
associated with a TEdit window, (FORMAT S "~&Hello, Medley!~%")
inserts the text "Hello, Medley!" in the window at the position of the cursor. Simple and versatile.
As I wrote more GUI code, recurring patterns and boilerplate emerged. These programs usually create a new TEdit window; set up the title and other options; fetch the associated text stream; and return it for further use. The rest of the program prints application specific text to the stream and hence to the window.
These patterns were ripe for abstracting and packaging in a library that other programs can call. This work is also good experience with API design.
Usage
An example best illustrates what DandeGUI can do and how to use it. Suppose you want to display in a window some text such as a table of square roots. This code creates the table in the screenshot above:
(gui:with-output-to-window (stream :title "Table of square roots")
(format stream "~&Number~40TSquare Root~2%")
(loop
for n from 1 to 30
do (format stream "~&~4D~40T~8,4F~%" n (sqrt n))))
DandeGUI exports all the public symbols from the DANDEGUI
package with nickname GUI
. The macro GUI:WITH-OUTPUT-TO-WINDOW
creates a new TEdit window with title specified by :TITLE
, and establishes a context in which the variable STREAM
is bound to the stream associated with the window. The rest of the code prints the table by repeatedly calling the Common Lisp function FORMAT
with the stream.
GUI:WITH-OUTPUT-TO-WINDOW
is best suited for one-off output as the stream is no longer accessible outside of its scope.
To retain the stream and send output in a series of steps, or from different parts of the program, you need a combination of GUI:OPEN-WINDOW-STREAM
and GUI:WITH-WINDOW-STREAM
. The former opens and returns a new window stream which may later be used by FORMAT
and other stream output functions. These functions must be wrapped in calls to the macro GUI:WITH-WINDOW-STREAM
to establish a context in which a variable is bound to the appropriate stream.
The DandeGUI documentation on the project repository provides more details, sample code, and the API reference.
Design
DandeGUI is a thin wrapper around the Interlisp system facilities that provide the underlying functionality.
The main reason for a thin wrapper is to have a simple API that covers the most common user interface patterns. Despite the simplicity, the library takes care of a lot of the complexity of managing Medley GUIs such as content scrolling and window repainting and resizing.
A thin wrapper doesn't hide much the data structures ubiquitous in Medley GUIs such as menus and font descriptors. This is a plus as the programmer leverages prior knowledge of these facilities.
So far I have no clear idea how DandeGUI may evolve. One more reason not to deepen the wrapper too much without a clear direction.
The user needs not know whether DandeGUI packs TEdit or ordinary windows under the hood. Therefore, another design goal is to hide this implementation detail. DandeGUI, for example, disables the main command menu of TEdit and sets the editor buffer to read-only so that typing in the window doesn't change the text accidentally.
Using Medley Common Lisp
DandeGUI relies on basic Common Lisp features. Although the Medley Common Lisp implementation is not ANSI compliant it provides all I need, with one exception.
The function DANDEGUI:WINDOW-TITLE
returns the title of a window and allows to set it with a SETF
function. However, the SEdit structure editor and the File Manager of Medley don't support or track function names that are lists such as (SETF WINDOW-TITLE)
. A good workaround is to define SETF
functions with DEFSETF
which Medley does support along with the CLtL macro DEFINE-SETF-METHOD
.
Next steps
At present DandeGUI doesn't do much more than what described here.
To enhance this foundation I'll likely allow to clear existing text and give control over where to insert text in windows, such as at the beginning or end. DandeGUI will also have rich text facilities like printing in bold or changing fonts.
The windows of some of my programs have an attached menu of commands and a status area for displaying errors and other messages. I will eventually implement such menu-ed windows.
To support programs that do graphics output I plan to leverage the functionality of Sketch for graphics in a way similar to how I build upon TEdit for text.
Sketch is the line drawing editor of Medley. The Interlisp graphics primitives require as an argument a DISPLAYSTREAM
, a data stracture that represents an output sink for graphics. It is possible to use the Sketch drawing area as an output destination by associating a DISPLAYSTREAM
with the editor's window. Like TEdit, Sketch takes care of repainting content as well as window scrolling and resizing. In other words, DISPLAYSTREAM
is to Sketch what TEXTSTREAM
is to TEdit.
DandeGUI will create and manage Sketch windows with associated streams suitable for use as the DISPLAYSTREAM
the graphics primitives require.
#DandeGUI #CommonLisp #Interlisp #Lisp
Discuss... Email | Reply @amoroso@fosstodon.org
21 Apr 2025 7:17pm GMT
20 Apr 2025
Planet Lisp
Joe Marshall: Well This Was Interesting
I was playing with notebooklm.google.com and for fun I entered a few of my recent blog posts. I asked it to generate a conversation. The AI generated what sounds a lot like a podcast discussing my blog. One host sounds a bit like Mike Rowe. They get a lot of the detail right, but there are a couple of glaring mistakes. (Read - EVIL - Print - Loop) I suppose I am contributing to the soup of AI slop, but I was suprised at how natural this sounds. Podcast
I also tried giving it some text files about Tic Tac Toe representations. It generated a remarkably good "Deep Dive" that really sounds as if it has an understanding of the text: Deep Dive
There are some "tells" that this is AI and not human, but I wouldn't be surprised if this could fool the average layman. In a few years, it's going to be really hard to detect, though.
20 Apr 2025 8:15pm GMT
18 Apr 2025
Planet Lisp
Joe Marshall: Stupid reader tricks
Here are some stupid reader tricks for Lisp. I've tested them on SBCL, and they are of questionable portability and utility.
Run Shell Commands from the Lisp Prompt
(set-macro-character #\! (lambda (stream char) (declare (ignore stream char)) (uiop:run-program (read-line stream) :output *standard-output*)) t) > !ls -als total 4068 4 drwxr-x--- 21 jrm jrm 4096 Apr 18 06:42 . 4 drwxr-xr-x 4 root root 4096 Mar 22 17:27 .. 1900 -rwx--x--x 1 jrm jrm 1940604 Apr 17 19:10 .bash_history 4 -rw-r--r-- 1 jrm jrm 220 Mar 19 12:16 .bash_logout 8 -rw-r--r-- 1 jrm jrm 4961 Apr 1 11:13 .bashrc 4 drwx------ 6 jrm jrm 4096 Mar 21 07:52 .cache 0 lrwxrwxrwx 1 jrm jrm 51 Mar 24 05:20 .config -> /mnt/c/Users/JosephMarshall/AppData/Roaming/.config 0 lrwxrwxrwx 1 jrm jrm 50 Mar 26 03:12 .emacs -> /mnt/c/Users/JosephMarshall/AppData/Roaming/.emacs 4 drwx------ 6 jrm jrm 4096 Apr 17 12:13 .emacs.d ... etc ... >
Make λ an alias for lambda
(set-macro-character #\λ (lambda (stream char) (declare (ignore stream char)) 'cl:lambda) t) > ((λ (x) (+ x 4)) 3) 7
If you do this you might want to add a print function for the lambda symbol:
(defmethod print-object ((obj (eql 'cl:lambda)) stream) ;; doubt this is portable (format stream "λ")) > '(λ (x) (+ x 4)) (λ (x) (+ x 4)) > (symbol-name (car *)) "LAMBDA"
18 Apr 2025 2:38pm GMT
Joe Marshall: DES Machine
The MIT CADR Lisp Machine had a number of static RAMs that were used in the processor for various things such as state machines and register files. The core parts of the LMI Lambda Lisp Machine were similar to the CADR (similar enough that they could run the same microcode) but technology had advanced such that the static RAM chips were typically double the size of the CADR's. The LMI Lambda thus had twice as many registers as the CADR, but because there weren't any extra bits in the instruction set, you couldn't address half of them. The extra address bit from the RAM was wired to a status register. So the LMI Lambda had, in effect, two banks of registers which you could swap between by toggling the bit in the status register. This was not normally used - the microcode would set the bit to zero and leave it there.
A friend of mine was interested in security and he had written a high performance version of the encryption algorithm used by Unix to encrypt passwords. He was experimenting with dictionary attacks against passwords and one bottleneck was the performance of the password encryption algorithm.
It occurred to me that I could store the DES S-boxes in the alternate register bank of the LMI Lambda. With some special microcode, I could turn an LMI Lambda into a DES machine that could churn through a dictionary attack at a high speed. I added a special Lisp primitive that would swap the register banks and then run several hundred rounds of the DES algorithm before swapping back and returning to Lisp. Then I wrote a Lisp program that would feed a dictionary into the DES primitives when the processor was idle.
I was able to discover a few passwords this way, but I was more interested in the proof of concept that the actual results. A microcoded DES machine would work, but you'd get better performance out of dedicated hardware.
18 Apr 2025 6:13am GMT
Thomas Fitzsimmons: Lisp ELF toolkit
I recently needed to generate an ELF
binary with both RPATH
and RUNPATH
entries. I could not figure out how to produce this using linker command line arguments.
I was considering attempting a linker script, but first I switched to my Lisp
REPL
buffer 1 and found that (ql:quickload "elf")
loaded a promising-looking Common Lisp ELF
library.
I created a stub library with RPATH
using gcc
and an empty C
file, then loaded it with (elf:read-elf)
.
With the SLIME
inspector (M-x slime-inspect
) I could traverse the structure of the ELF
headers. I eventually found the RPATH
entry.
In the REPL
I built up a function to search for RPATH
then push
a new RUNPATH
entry alongside it.
It turned out the ELF
library had no support for the RUNPATH
entry, so I redefined its dyn-tag
dictionary to include it.
After adding RUNPATH
, I wrote the modified ELF
structures to a file using (elf:write-elf)
. The generated ELF
file sufficed for the test case.
I thought this was an interesting use case to share, demonstrating unique properties of the Lisp
environment. I published the result (I realize now I should have written generate-example-library.sh
in Lisp
instead of shell!; oh well).
- Which I have been trying to keep open lately, inspired by this post.
18 Apr 2025 4:35am GMT
13 Apr 2025
Planet Lisp
Joe Marshall: Mea Culpa
OH NO! There's something wrong on the Internet!
It is often me. Mea culpa. Mea maxima culpa. I'm only human.
But this is a blog, not a reference manual. I use it to organize my thoughts, get things off my chest, provoke discussion, maybe entertain a few fellow hackers, and show others how much fun I'm having playing with computers. Accuracy and precision aren't the primary objectives although I try not to be egregiously incorrect.
Mostly I see people misinterpreting something I say casually. I gloss over some details that some find important but I consider boring. I make the first-order error of assuming everyone has the same backgroud as I do and will interpret what I say in the way I had in mind. Clearly this isn't the case, yet I persist in thinking this way. Oh well.
I'll try to correct errors that are brought to my attention and will elaborate things in more detail if asked, but if my blog irritates you with its broad generalizations, inattention to detail, omission of specifics, and things that are just plain wrong, why are you reading it?
13 Apr 2025 3:35pm GMT