30 Jul 2025

feedPlanet Lisp

Joe Marshall: Novice to LLMs — LLM calls Lisp

I'm a novice to the LLM API, and I'm assuming that at least some of my readers are too. I'm not the very last person to the party, am I?

When integrating the LLM with Lisp, we want to allow the LLM to direct queries back to the Lisp that is invoking it. This is done through the function call protocol. The client supplies to the LLM a list of functions that the LLM may invoke. When the LLM wants to invoke the function, instead of returing a block of generated text, it returns a JSON object indicating a function call. This contains the name of the function and the arguments. The client is supposed to invoke the function, but to return an answer, it actually makes a new call into the LLM and it concatenates the entire conversation so far along with the result of the function call. It is bizarro continuation-passing-style where the client acts as a trampoline and keeps track of the continuation.

So, for example, by exposing lisp-implementation-type and lisp-implementation-version, we can then query the LLM:

> (invoke-gemini "gemini-2.5-flash" "What is the type and version of the lisp implementation?")
"The Lisp implementation is SBCL version 2.5.4."

30 Jul 2025 2:49pm GMT

28 Jul 2025

feedPlanet Lisp

Joe Marshall: Pseudo

I was wondering what it would look like if a large language model were part of your programming language. I'm not talking about calling the model as an API, but rather embedding it as a language construct. I came up with this idea as a first cut.

The pseudo macro allows you to embed pseudocode expressions in your Common Lisp code. It takes a string description and uses an LLM to expand it into an s-expression. You can use pseudo anywhere an expression would be expected.

(defun my-func (a b)
  (pseudo "multiply b by factorial of a."))
MY-FUNC

(my-func 5 3)
360

(defun quadratic (a b c)
  (let ((d (sqrt (pseudo "compute discriminant of quadratic equation"))))
    (values (/ (+ (- b) d) (* 2 a)) (/ (- (- b) d) (* 2 a)))))
QUADRATIC

(quadratic 1 2 -3)
1.0
-3.0

The pseudo macro gathers contextual information and packages it up in a big set of system instructions to the LLM. The instructions include

pseduo sets the LLM to use a low temperature for more predictable generation. It prints the "thinking" of the LLM.

Lisp is a big win here. Since Lisp's macro system operates at the level of s-expressions, it has more contextual information available to it than a macro system that is just text expansion. The s-expression representation means that we don't need to interface with the language's parser or compiler to operate on the syntax tree of the code. Adding pseudo to a language like Java would be a much more significant undertaking.

pseudo has the usual LLM caveats:

pseudo has one dependency on SBCL which is a function to extract the lexically visible variables from the macro environment. If you port it to another Common Lisp, you'll want to provide an equivalent function.

pseudo was developed using Google's Gemini as the back end, but there's no reason it couldn't be adapted to use other LLMs. To try it out, you'll need the gemini library, available at https://github.com/jrm-code-project/gemini, and a Google API key.

Download pseudo from https://github.com/jrm-code-project/pseudo.

You'll also need these dependencies.

If you try it, let me know how it goes.

28 Jul 2025 9:41am GMT

21 Jul 2025

feedPlanet Lisp

Marco Antoniotti: EMC makes into melpa

Hello

Emacs Make Compile (EMC) made it into MELPA.

You can now install it directly from the Emacs package manager.

Many thanks to the MELPA curators for accomdating an old, annoying, geezer.

Give it a spin; submit bug reports and suggestions.


'(cheers)

21 Jul 2025 1:23pm GMT

19 Jul 2025

feedPlanet Lisp

Joe Marshall: GitHub updates 19/Jul/2025

https://github.com/jrm-code-project/dual-numbers

This library implements dual numbers for automatic differentiation.


https://github.com/jrm-code-project/function

This library implements higher-order functions, composing, currying, partial-application, etc.


https://github.com/jrm-code-project/generic-arithetic

This library redefines the standard Common Lisp arithemetic with generic functions so that math operations can be extended with defmethod.


https://github.com/jrm-code-project/named-let

This library implements some Scheme-inspired macros.

19 Jul 2025 4:11pm GMT

18 Jul 2025

feedPlanet Lisp

Joe Marshall: A Lot of Packets

A War Story

I once worked for a company that provided mobile data services to public safety organizations: we put PCs in police cars. Our value proposition was that we could connect the PC to the base station using off-the-shelf packet radio on the same frequency as the voice radio. This was attractive to small town police departments that could not afford to install a separate data radio system.

The company was started by a few ham-radio enthusiasts who were trying to leverage their expertise in packet radio to provide a low-cost product to the public safety market. One of the guys had written network code at DEC and wrote the network stack. This was in the days before the web and before TCP/IP was the standard protocol, so he wrote an ad-hoc protocol for the system.

I was called to one on-site installation to solve a vexing problem. The system would work for a while, but then it would hang and need to be completely reset. It only happened when a certain file was being sent. There was nothing unusual about the file, it was a text file.

The way the system worked was, let us say, imaginative. The base station had a command line interface, and the PC in the car would send commands to the base station over packet radio. It would literally type the received command into the base station's prompt. This violated all the best practices for network protocols. It was vulnerable to dropped packets, injection attacks, replay attacks, etc. It had no privacy, no integrity, and no authentication. I was horrified. But it was a working system, so I had to work with it.

After a few hours of debugging, I found that the base station was getting stuck in the packet receive loop. The cause was amusing, if pathetic. The command protocol was sloppy. About half the time, a command would be sent with one or more trailing newlines. The base station dealt with this by stripping trailing newlines, if present from the command before executing it.

The file transfer command would include the length of file in blocks. The length was sent in binary over the ascii channel.

The problem was a file that was 13 blocks long. The command would be sent as file \x0d. But the base station would recognize the \x0d as a trailing newline and strip it. The file command, expecting a length byte, would read off the end of the end of the command and get the null byte. This started the file receive loop which would pre-decrement the unsigned length (to account that first packet had been sent) and ended up with a value of 65535. It then would sit and wait for the next 65535 packets to arrive.

So the system wasn't exactly hung, it was waiting for the rest of the file.

There was no way I was going to try to repair this monstrosity. I suggested that we use a proper network protocol, like TCP/IP, but I pushed this back to the guy that wrote this mess. As a founder of the company, he wasn't about to change the perfectly good protocol that he had authored, so he came up with a workaround.

I didn't last too long at that company. I saw the writing on the wall and moved on.

18 Jul 2025 6:58pm GMT

16 Jul 2025

feedPlanet Lisp

Joe Marshall: Dual numbers

Let's build some numbers. We begin with the natural numbers, which capture the idea of magnitude. Adding them is simple: on a number line, it's a consistent shift to the right. But what about shifting left? To handle this, we could invent a separate flag to track direction and a set of rules for its manipulation. Or we can be clever and augment the numbers themselves. By incorporating a "sign," we create positive and negative integers, baking the concept of direction directly into our numerical system and embedding its logic into the rules of arithmetic.

This pattern of augmentation continues as we move from the number line to the number plane. Beyond simple shifts, we want to introduce the concept of rotation. Again, we could track rotation as an external property, but a more integrated solution emerges with complex numbers. By augmenting real numbers with a so-called "imaginary" unit, i, we create numbers of the form a + bi. If b is zero, we have a standard signed number. If b is non-zero, the number represents a rotation in the plane. A 90-degree counter-clockwise rotation is represented by i, and a clockwise rotation by -i. Notably, two 90-degree rotations result in a 180-degree turn, which is equivalent to flipping the number's sign. This geometric reality is captured by the algebraic rule i² = -1. Once again, we don't just track a new property; we weave it into the fabric of the numbers themselves.

Now, let us turn to the world of calculus and the concept of differentiation. When we analyze a function, we are often interested in its value and its slope at a given point. Following our established pattern, we could track the slope as a separate piece of information, calculating it with the familiar rules of derivatives. Or, we can be clever and augment our numbers once more, this time to contain the slope intrinsically. This is the innovation of dual numbers.

To do this, we introduce a new entity, ε (epsilon), and form numbers that look like a + bε. Here, a represents the number's value, and b will represent its associated slope or "infinitesimal" part. The defining characteristic of ε is unusual: we assert that ε is greater than zero, yet smaller than any positive real number. This makes ε an infinitesimal. Consequently, ε², being infinitesimally small squared, is so negligible that we simply define it as zero. This single rule, ε² = 0, is all we need. Our rules of arithmetic adapt seamlessly. Adding two dual numbers means adding their real and ε parts separately: (a + bε) + (c + dε) = (a + c) + (b + d)ε. Multiplication is just as straightforward, we distribute the terms and apply our new rule:

(a + bε)(c + dε) = ac + adε + bcε + bdε² = ac + (ad + bc)ε

Notice how the ε² term simply vanishes.

Extending the arithmetic to include division requires a method for finding the reciprocal of a dual number. We can derive this by adapting a technique similar to the one used for complex numbers: multiplying the numerator and denominator by the conjugate. The conjugate of a + bε is a - bε. To find the reciprocal of a + bε, we calculate 1 / (a + bε):

1 / (a + bε) = (1 / (a + bε)) * ((a - bε) / (a - bε))
= (a - bε) / (a² - abε + abε - b²ε²)
= (a - bε) / (a² - b²ε²)

Using the defining property that ε² = 0, the denominator simplifies to just a². The expression becomes:

(a - bε) / a² = 1/a - (b/a²)ε

Thus, the reciprocal is 1/a - (b/a²)ε, provided a is not zero. This allows for the division of two dual numbers by multiplying the first by the reciprocal of the second, completing the set of basic arithmetic operations.

But what is it good for? Based on the principles of Taylor series or linear approximation, for a very small change bε, a differentiable function's behavior can be described as:

F(a + bε) = F(a) + F'(a)bε

The result is another dual number. Its "real" part is F(a), the value of the function at a. Its "infinitesimal" part is F'(a)b, which contains the derivative of the function at a. If we set b=1 and simply evaluate F(a + ε), the ε part of the result is precisely the derivative, F'(a). This gives us a direct way to compute a derivative, as captured in this conceptual code:

(defun (derivative f)
  (lambda (x)
    (infinitesimal-part (f (+ x ε)))))

This method provides an alternative to traditional numerical differentiation. Standard finite-difference methods, such as calculating (F(x+h) - F(x))/h, force a difficult choice for h. A large h leads to truncation error from the approximation, while a very small h can introduce significant rounding error from subtracting two nearly identical floating-point numbers. Dual numbers sidestep this issue entirely. The process is algebraic, not approximative. The derivative is computed numerically, but exactly, with no truncation error and without the instability of manipulating a vanishingly small h.

By extending our number system to include an infinitesimal part, we have baked the logic of differentiation - specifically, the chain rule - into the rules of arithmetic. We no longer need a separate set of symbolic rules for finding derivatives. By simply executing a function with dual numbers as inputs, the derivative is calculated automatically, as a natural consequence of the algebra. Just as the sign captured direction and i captured rotation, ε captures the essence of a derivative

If we want to combine dual and complex numbers, we have a choice: dual numbers with complex standard and infinitesimal parts, or complex numbers with dual real and imaginary parts. From an implementation standpoint, the former is easier because complex numbers are already supported.

16 Jul 2025 4:53pm GMT

13 Jul 2025

feedPlanet Lisp

Quicklisp news: June 2025 Quicklisp dist now available

Hi! There's been an update available for a couple weeks, but I'm having trouble with the software I use to make blog announcements. I hope to have it fixed for the next list update for July.

In the meantime, you can get Quicklisp updates with the usual command:

(ql:update-dist "quicklisp")

Enjoy!

13 Jul 2025 3:19pm GMT

11 Jul 2025

feedPlanet Lisp

Joe Marshall: Gemini experiment

I took my sent email archive, cleaned it, and uploaded it to Gemini. I now have a specialized chat box (a "Gem") that can answer emails like me. If you send me email with "VIRTUAL JOE" in the subject, I'll feed it through.

11 Jul 2025 2:59pm GMT

10 Jul 2025

feedPlanet Lisp

Joe Marshall: An observation

Go programs make a lot more sense if you pronounce if err != nil as "inshallah".

10 Jul 2025 11:53pm GMT

08 Jul 2025

feedPlanet Lisp

vindarel: Lisp error handling (advanced): how handler-bind doesn't unwind the stack

I updated the Condition Handling page on the Common Lisp Cookbook to show what it means that handler-bind "doesn't unwind the stack", along with a couple real-world use cases.

This time I must thank Ari. I originally wrote the page (with contributions from jsjolen, rprimus and phoe), starting from Timmy Jose "z0ltan"'s article (linked in the introduction) and the books I had at my disposal. These don't show handler-bind's power and use-cases like this, focusing on restarts (which this page certainly could explain better, it shows they aren't part of my daily toolbelt). So I learned about the real goodness by chance while reading @shinmera's code and, shame on me, I didn't update the Cookbook. My video course has been well and complete for years though ;) I needed fresh eyes and that happened with Ari. He asked if I offered 1-1 video lisp mentoring. I didn't, but now I do!. So, as we talked about a million things (lisp and software development in general) and eventually worked on condition handling, I realized this page was lacking, and I took an extra 1h 40min to update it, "backporting" content from my video course.

These days my attention is more turned towards my tutorials for web development in Common Lisp, my CL projects and libraries, and less to the Cookbook alone like before, but for this edit to the Cookbook 2 things were new and important:

So what is this all about? handler-bind, unlike handler-case, doesn't unwind the stack: it shows us the full backtrace and gives us absolute control over conditions and restarts.

It's particularly necessary if you want to print a meaningful backtrace. We'll give another development tip, where you can decide to either print a backtrace (production mode) or accept to be dropped into the debugger (invoke-debugger).

Feel free to leave feedback, in the comments or in the Cookbook issues or PR.

Table of Contents

Absolute control over conditions and restarts: handler-bind

handler-bind is what to use when we need absolute control over what happens when a condition is signaled. It doesn't unwind the stack, which we illustrate in the next section. It allows us to use the debugger and restarts, either interactively or programmatically.

Its general form is:

(handler-bind ((a-condition #'function-to-handle-it)
               (another-one #'another-function))
    (code that can...)
    (...error out...)
    (... with an implicit PROGN))

For example:

(defun handler-bind-example ()
  (handler-bind
        ((error (lambda (c)
                  (format t "we handle this condition: ~a" c)
                  ;; Try without this return-from: the error bubbles up
                  ;; up to the interactive debugger.
                  (return-from handler-bind-example))))
      (format t "starting example...~&")
      (error "oh no")))

You'll notice that its syntax is "in reverse" compared to handler-case: we have the bindings first, the forms (in an implicit progn) next.

If the handler returns normally (it declines to handle the condition), the condition continues to bubble up, searching for another handler, and it will find the interactive debugger.

This is another difference from handler-case: if our handler function didn't explicitely return from its calling function with return-from handler-bind-example, the error would continue to bubble up, and we would get the interactive debugger.

This behaviour is particularly useful when your program signaled a simple condition. A simple condition isn't an error (see our "conditions hierarchy" below) so it won't trigger the debugger. You can do something to handle the condition (it's a signal for something occuring in your application), and let the program continue.

If some library doesn't handle all conditions and lets some bubble out to us, we can see the restarts (established by restart-case) anywhere deep in the stack, including restarts established by other libraries that this library called.

handler-bind doesn't unwind the stack

With handler-bind, we can see the full stack trace, with every frame that was called. Once we use handler-case, we "forget" many steps of our program's execution until the condition is handled: the call stack is unwound (or "untangled", "shortened"). handler-bind does not rewind the stack. Let's illustrate this.

For the sake of our demonstration, we will use the library trivial-backtrace, which you can install with Quicklisp:

(ql:quickload "trivial-backtrace")

It is a wrapper around the implementations' primitives such as sb-debug:print-backtrace.

Consider the following code: our main function calls a chain of functions which ultimately fail by signaling an error. We handle the error in the main function with hander-case and print the backtrace.

(defun f0 ()
  (error "oh no"))

(defun f1 ()
  (f0))

(defun f2 ()
  (f1))

(defun main ()
  (handler-case (f2)
    (error (c)
      (format t "in main, we handle: ~a" c)
      (trivial-backtrace:print-backtrace c))))

This is the backtrace (only the first frames):

CL-REPL> (main)
in main, we handle: oh no
Date/time: 2025-07-04-11:25!
An unhandled error condition has been signalled: oh no

Backtrace for: #<SB-THREAD:THREAD "repl-thread" RUNNING {1008695453}>
0: [...]
1: (TRIVIAL-BACKTRACE:PRINT-BACKTRACE ... )
2: (MAIN)
[...]

So far so good. It is trivial-backtrace that prints the "Date/time" and the message "An unhandled error condition...".

Now compare the stacktrace when we use handler-bind:

(defun main-no-stack-unwinding ()
  (handler-bind
      ((error (lambda (c)
                (format t "in main, we handle: ~a" c)
                (trivial-backtrace:print-backtrace c)
                (return-from main-no-stack-unwinding))))
    (f2)))
CL-REPL> (main-no-stack-unwinding)
in main, we handle: oh no
Date/time: 2025-07-04-11:32!
An unhandled error condition has been signalled: oh no

Backtrace for: #<SB-THREAD:THREAD "repl-thread" RUNNING {1008695453}>
0: ...
1: (TRIVIAL-BACKTRACE:PRINT-BACKTRACE ...)
2: ...
3: ...
4: (ERROR "oh no")
5: (F0)
6: (F1)
7: (MAIN-NO-STACK-UNWINDING)

That's right: you can see all the call stack: from the main function to the error through f1 and f0. These two intermediate functions were not present in the backtrace when we used handler-case because, as the error was signaled and bubbled up in the call stack, the stack was unwound, and we lost information.

When to use which?

handler-case is enough when you expect a situation to fail. For example, in the context of an HTTP request, it is a common to anticipate a 400-ish error:

;; using the dexador library.
(handler-case (dex:get "http://bad-url.lisp")
  (dex:http-request-failed (e)
    ;; For 4xx or 5xx HTTP errors: it's OK, this can happen.
    (format *error-output* "The server returned ~D" (dex:response-status e))))

In other exceptional situations, we'll surely want handler-bind. For example, when we want to handle what went wrong and we want to print a backtrace, or if we want to invoke the debugger manually (see below) and see exactly what happened.

Invoking the debugger manually

Suppose you handle a condition with handler-bind, and your condition object is bound to the c variable (as in our examples above). Suppose a parameter of yours, say *devel-mode*, tells you are not in production. It may be handy to fire the debugger on the given condition. Use:

(invoke-debugger c)

In production, you can print the backtrace instead and have an error reporting tool like Sentry notify you.

Closing words

This is yet another CL feature I wish I had known earlier and learned by chance. I hope you learned a thing or two!

08 Jul 2025 9:45am GMT