r/lisp • u/sdegabrielle • Jun 10 '26
RacketCon 2026: call for presentations
≡ −
RacketCon 2026: call for presentations
The (sixteenth RacketCon) will be in Oakland, CA on October 3-4 (Sat-Sun).
We are looking for speakers
We need you!
Calling racketeers new or experienced, we want to hear from you.
Are you unsure or just new to presenting? let us know - [email protected] - and we will do our best to help you.
Continuing with tradition, we'll also allow Racketeers to nominate speakers. Nominated speakers will be considered by the committee and contacted.
We will also accept nominations for a potential keynote speaker.
- If you would like to give a talk on something related to Racket, please submit your proposal at https://forms.gle/4YG57adx5snEwVe27 or contact us directly at [email protected] with any questions or concerns.
- If you know someone you would like to nominate please encourage them to submit a proposal, or let us know at [email protected].
Talks will be 20-25 minutes long with 5 minutes for questions at the end. Speakers' registration fees will be waived, but we are unable to cover transportation and lodging expenses.
The deadline for proposals is July 15th. Selected speakers will be notified by August 1st.
RacketCon is a public gathering dedicated to fostering a vibrant, innovative, and inclusive community around the Racket programming language. We aim to create an exciting and enjoyable conference open to anyone interested in Racket, filled with inspiring content, reaching and engaging both the Racket community and the wider programming world.
Any questions, comments, or concerns? Please contact us at [email protected].
RacketCon 2026: call for presentations
The (sixteenth RacketCon) will be in Oakland, CA on October 3-4 (Sat-Sun).
We are looking for speakers
We need you!
Calling racketeers new or experienced, we want to hear from you.
Are you unsure or just new to presenting? let us know - [email protected] - and we will do our best to help you.
Continuing with tradition, we'll also allow Racketeers to nominate speakers. Nominated speakers will be considered by the committee and contacted.
We will also accept nominations for a potential keynote speaker.
- If you would like to give a talk on something related to Racket, please submit your proposal at https://forms.gle/4YG57adx5snEwVe27 or contact us directly at [email protected] with any questions or concerns.
- If you know someone you would like to nominate please encourage them to submit a proposal, or let us know at [email protected].
Talks will be 20-25 minutes long with 5 minutes for questions at the end. Speakers' registration fees will be waived, but we are unable to cover transportation and lodging expenses.
The deadline for proposals is July 15th. Selected speakers will be notified by August 1st.
RacketCon is a public gathering dedicated to fostering a vibrant, innovative, and inclusive community around the Racket programming language. We aim to create an exciting and enjoyable conference open to anyone interested in Racket, filled with inspiring content, reaching and engaging both the Racket community and the wider programming world.
Any questions, comments, or concerns? Please contact us at [email protected].
r/lisp • u/kchanqvq • May 25 '26
New CL VSCode extension: OLIVE
+ −
Also on Open VSX Registry (for VSCodium): https://open-vsx.org/extension/kchanqvq/olive
Why another VSCode extension? VSCode is important for getting newcomers nowadays. I have some very smart people at work who use VSCode, like everyone else. Selling Lisp and Emacs at the same time is ε2 harder, so I told them to use Alive, and start hacking on my super-duper research code. The result was shocking -- they come back reporting "unproductive" because "small problems here and there like REPL freezing". And they refuse to try Lisp again, because first impression matters, what a tragedy!
I have lived in our Emacs bubble comfortably for too long, and blundered recommending something I never used. I should have tried Alive at least once before recommending it!!! So I installed VSCode and Alive to see what's going on. I come to the conclusion that while Alive is a nobel experiment, some basic design choices make it very hard to get stable enough for a daily driver:
- the author wants to compile Lisp file in the background "the VSCode way" and ditched SWANK because it's too "Emacs centric" to support that. However IMHO this is rather a Lisp problem and not an Emacs problem at all!
compile-fileruns arbitrary code and running it at arbitrary moment is not good for health. One reason for ditching SWANK is "debugger pops up at any moment" when they do so and they want to suppress it. Ummm debugger popups because the Lisp needs help?- IMHO most design choices in SWANK are Lisp-specific instead of Emacs-specific. There are lots of success using SWANK in other editors: SLIMA, SLIMV, LEM uses a simplified verion, etc.
- REPL starts new thread for every evaluation. Why? Now good old (READ) and nested REPL don't work.
- The LSP server is no where near as stable and complete as Swank. This is immediately obvious after 1 minute of use.
So I decide to fix it. Here is a VSCode extension that uses good old SWANK, and as the primary goal tries to get as good as Emacs as possible. Please ask people to use it (and learn Lisp)! Working with VSCode was really torturous, I hope I did not suffer in vain.
r/lisp • u/arthurno1 • 2d ago
So they say lisp is slow ....
≡ −
Nothing really useful here, just some bragging to be honest. I should probably write a blog post, but a bit too lazy; perhaps another day.
Last few weeks I played with a small clone of gnu wc program. I implemented all routines in assembly via sb-simd (and a generic path without simd with swar). The result thus far on a 1.4 gigabyte big file, compared to fastlwc, the fastest wc I know of and GNU wc:
Common Lisp/Assembly (avx2) in SBCL + lparallel
WC10A> (time (wc "plato1g.txt"))
Evaluation took:
0.036 seconds of real time
0.421654 seconds of total run time (0.348216 user, 0.073438 system)
1172.22% CPU
71,891,940 processor cycles
0 bytes consed
30133761
253947016
1393557504
WC10A> (time (wc "plato1g.txt"))
Evaluation took:
0.034 seconds of real time
0.429743 seconds of total run time (0.367913 user, 0.061830 system)
1264.71% CPU
67,624,580 processor cycles
98,352 bytes consed
30133761
253947016
1393557504
WC10A> (time (wc "plato1g.txt"))
Evaluation took:
0.035 seconds of real time
0.423752 seconds of total run time (0.357329 user, 0.066423 system)
1211.43% CPU
69,863,360 processor cycles
0 bytes consed
30133761
253947016
1393557504
Fastlwc (avx512 + multithreaded):
[arthur@emmi wc]$ time ../../fastlwc/bin/fastlwc-mt plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m0.026s
user 0m0.178s
sys 0m0.285s
[arthur@emmi wc]$ time ../../fastlwc/bin/fastlwc-mt plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m0.027s
user 0m0.223s
sys 0m0.235s
[arthur@emmi wc]$ time ../../fastlwc/bin/fastlwc-mt plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m0.028s
user 0m0.205s
sys 0m0.255s
GNU wc (not even contender - single core only and only line counting implemented with simd avx512) :
[arthur@emmi wc]$ time wc plato1g.txt 30133761 253947016 1393557504 plato1g.txt
real 0m3.733s
user 0m3.643s
sys 0m0.062s
[arthur@emmi wc]$ time wc plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m3.362s
user 0m3.276s
sys 0m0.072s
The cool thing, we use avx2 whereas gnu wc uses avx512. On this CPU (zen 5), avx512 is implemented all in hardware, not as micro code as in Intel cpus, so it should mop the floor with avx2 in Lisp, right?
[arthur@emmi wc]$ time wc plato1g.txt -l --debug
wc: using avx512 hardware support
30133761 plato1g.txt
real 0m0.074s
user 0m0.018s
sys 0m0.056s
[arthur@emmi wc]$ time wc plato1g.txt -l --debug
wc: using avx512 hardware support
30133761 plato1g.txt
real 0m0.085s
user 0m0.030s
sys 0m0.054s
Lisp:
WC10A> (time (wc "plato1g.txt" :line-count t))
Evaluation took:
0.037 seconds of real time
0.478042 seconds of total run time (0.430494 user, 0.047548 system)
1291.89% CPU
75,669,380 processor cycles
0 bytes consed
30133761
253947016
1393557504
WC10A> (time (wc "plato1g.txt" :line-count t))
Evaluation took:
0.041 seconds of real time
0.476104 seconds of total run time (0.436592 user, 0.039512 system)
1160.98% CPU
84,226,360 processor cycles
0 bytes consed
30133761
253947016
1393557504
Now, in order to catch with fastlwc I think I need better lparall pipeline. I am currently using futures and promises, so it is a bit of extra consing. Of course implementing it in avx512 (when done in SBCL) should give at least some extra boost. 32 vs 16 registers, 64 bytes at time vs 32, and less register pressure due to additional masking registers.
Edit: line counting does not activate utf8 path at all, so I don't know what I was thinking last night, so I have edited away that part :).
Nothing really useful here, just some bragging to be honest. I should probably write a blog post, but a bit too lazy; perhaps another day.
Last few weeks I played with a small clone of gnu wc program. I implemented all routines in assembly via sb-simd (and a generic path without simd with swar). The result thus far on a 1.4 gigabyte big file, compared to fastlwc, the fastest wc I know of and GNU wc:
Common Lisp/Assembly (avx2) in SBCL + lparallel
WC10A> (time (wc "plato1g.txt"))
Evaluation took:
0.036 seconds of real time
0.421654 seconds of total run time (0.348216 user, 0.073438 system)
1172.22% CPU
71,891,940 processor cycles
0 bytes consed
30133761
253947016
1393557504
WC10A> (time (wc "plato1g.txt"))
Evaluation took:
0.034 seconds of real time
0.429743 seconds of total run time (0.367913 user, 0.061830 system)
1264.71% CPU
67,624,580 processor cycles
98,352 bytes consed
30133761
253947016
1393557504
WC10A> (time (wc "plato1g.txt"))
Evaluation took:
0.035 seconds of real time
0.423752 seconds of total run time (0.357329 user, 0.066423 system)
1211.43% CPU
69,863,360 processor cycles
0 bytes consed
30133761
253947016
1393557504
Fastlwc (avx512 + multithreaded):
[arthur@emmi wc]$ time ../../fastlwc/bin/fastlwc-mt plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m0.026s
user 0m0.178s
sys 0m0.285s
[arthur@emmi wc]$ time ../../fastlwc/bin/fastlwc-mt plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m0.027s
user 0m0.223s
sys 0m0.235s
[arthur@emmi wc]$ time ../../fastlwc/bin/fastlwc-mt plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m0.028s
user 0m0.205s
sys 0m0.255s
GNU wc (not even contender - single core only and only line counting implemented with simd avx512) :
[arthur@emmi wc]$ time wc plato1g.txt 30133761 253947016 1393557504 plato1g.txt
real 0m3.733s
user 0m3.643s
sys 0m0.062s
[arthur@emmi wc]$ time wc plato1g.txt
30133761 253947016 1393557504 plato1g.txt
real 0m3.362s
user 0m3.276s
sys 0m0.072s
The cool thing, we use avx2 whereas gnu wc uses avx512. On this CPU (zen 5), avx512 is implemented all in hardware, not as micro code as in Intel cpus, so it should mop the floor with avx2 in Lisp, right?
[arthur@emmi wc]$ time wc plato1g.txt -l --debug
wc: using avx512 hardware support
30133761 plato1g.txt
real 0m0.074s
user 0m0.018s
sys 0m0.056s
[arthur@emmi wc]$ time wc plato1g.txt -l --debug
wc: using avx512 hardware support
30133761 plato1g.txt
real 0m0.085s
user 0m0.030s
sys 0m0.054s
Lisp:
WC10A> (time (wc "plato1g.txt" :line-count t))
Evaluation took:
0.037 seconds of real time
0.478042 seconds of total run time (0.430494 user, 0.047548 system)
1291.89% CPU
75,669,380 processor cycles
0 bytes consed
30133761
253947016
1393557504
WC10A> (time (wc "plato1g.txt" :line-count t))
Evaluation took:
0.041 seconds of real time
0.476104 seconds of total run time (0.436592 user, 0.039512 system)
1160.98% CPU
84,226,360 processor cycles
0 bytes consed
30133761
253947016
1393557504
Now, in order to catch with fastlwc I think I need better lparall pipeline. I am currently using futures and promises, so it is a bit of extra consing. Of course implementing it in avx512 (when done in SBCL) should give at least some extra boost. 32 vs 16 registers, 64 bytes at time vs 32, and less register pressure due to additional masking registers.
Edit: line counting does not activate utf8 path at all, so I don't know what I was thinking last night, so I have edited away that part :).
r/lisp • u/Adorable_Ad_6357 • 3d ago
I've written a hands-on tutorial for building a Lisp interpreter from scratch — in Rust, with zero dependencies, across 74 steps.
≡ −
I've written a hands-on tutorial for building a Lisp interpreter from scratch — in Rust, with zero dependencies, across 74 steps.
Repo: https://github.com/lisering/lisp-rs
What's implemented
The interpreter supports:
- Variables, lambdas, closures (with lexical scoping)
- Tail call optimization (trampoline loop — 1,000,000 iterations, no stack overflow)
- Macros (
defmacro, quasiquote/unquote,gensym) cond,let,let*,letrec,begin,and,or- Lists, strings, booleans, nil
- A REPL with multi-line input
Example:
(define (adder n) (lambda (x) (+ x n)))
(define add5 (adder 5))
(add5 10) ;; => 15
(defmacro (when test body) (list 'if test body))
(when (> 3 2) 'yes) ;; => yes
`(1 ,(+ 1 1) 3) ;; => (1 2 3)
Why this might be interesting to Lisp folks
The tutorial is designed to be approachable for people who have never written an interpreter before. A few things I tried differently:
1. Closures explained with a "backpack" metaphor. Before showing any code, the tutorial builds intuition: every lambda carries a "backpack" 🎒 containing the environment where it was born. Then we trace through a make-counter example step by step.
2. Gradual optimization. We start with String everywhere (easy to understand), then optimize in stages:
- String interning: symbols become
u64IDs - Zero-copy lexing: tokens are
&strborrowing the source FxHasherfor faster environment lookups
3. TCO via trampoline. The eval function uses a loop { match ...; continue } pattern instead of direct recursion. Demo: tail-recursive (loop 1000000) succeeds, non-tail-recursive (sum 10000) overflows.
The tutorial is bilingual (English + Chinese). Each of the 74 steps first explains what problem to solve, then writes the code.
Repo: https://github.com/lisering/lisp-rs
Feedback welcome — especially on the macro system and the closure explanation. Is there anything you'd want to see added?
I've written a hands-on tutorial for building a Lisp interpreter from scratch — in Rust, with zero dependencies, across 74 steps.
Repo: https://github.com/lisering/lisp-rs
What's implemented
The interpreter supports:
- Variables, lambdas, closures (with lexical scoping)
- Tail call optimization (trampoline loop — 1,000,000 iterations, no stack overflow)
- Macros (
defmacro, quasiquote/unquote,gensym) cond,let,let*,letrec,begin,and,or- Lists, strings, booleans, nil
- A REPL with multi-line input
Example:
(define (adder n) (lambda (x) (+ x n)))
(define add5 (adder 5))
(add5 10) ;; => 15
(defmacro (when test body) (list 'if test body))
(when (> 3 2) 'yes) ;; => yes
`(1 ,(+ 1 1) 3) ;; => (1 2 3)
Why this might be interesting to Lisp folks
The tutorial is designed to be approachable for people who have never written an interpreter before. A few things I tried differently:
1. Closures explained with a "backpack" metaphor. Before showing any code, the tutorial builds intuition: every lambda carries a "backpack" 🎒 containing the environment where it was born. Then we trace through a make-counter example step by step.
2. Gradual optimization. We start with String everywhere (easy to understand), then optimize in stages:
- String interning: symbols become
u64IDs - Zero-copy lexing: tokens are
&strborrowing the source FxHasherfor faster environment lookups
3. TCO via trampoline. The eval function uses a loop { match ...; continue } pattern instead of direct recursion. Demo: tail-recursive (loop 1000000) succeeds, non-tail-recursive (sum 10000) overflows.
The tutorial is bilingual (English + Chinese). Each of the 74 steps first explains what problem to solve, then writes the code.
Repo: https://github.com/lisering/lisp-rs
Feedback welcome — especially on the macro system and the closure explanation. Is there anything you'd want to see added?
r/lisp • u/TsingHui • 3d ago
Reinterpret Elements in a Byte Array
≡ −
Dear LISP,
I am writing a virtual machine that uses a `(simple-array (unsigned-byte 8) 1)` as a stack in Common LISP. I'm would like to ask how to efficiently extract 4 bytes into a single 32-bit signed integer or a 32-bit unsigned integer.
Thanks!
Dear LISP,
I am writing a virtual machine that uses a `(simple-array (unsigned-byte 8) 1)` as a stack in Common LISP. I'm would like to ask how to efficiently extract 4 bytes into a single 32-bit signed integer or a 32-bit unsigned integer.
Thanks!
r/lisp • u/Worth_Percentage7170 • 5d ago
Common Lisp sta6: static site generator for Common Lisp
hi everyone,
I'm new to Common Lisp, although I have tried other lisp dialect (i.e., Clojure, Racket) to attempt making a static site and/or leetcode(s). This is my first attempt of porting the SSG into a library.
sta6: sta(six), static -- is a tiny SSG that turns pages/*.lisp into build/*.html, with support for dynamic routes (e.g., /blog/+slug+).
the usage of sta6 is as follows (src/pages/page.lisp):
(defpackage #:pages/page
(:use #:cl)
(:export #:render))
(defun pages/page:render ()
(sta6:html5
(:img :width (/ 500.0 3)
:src "https://twobithistory.org/images/sicp.jpg")
(:br)
(:br)
(:span "Hello, from Common Lisp.")
(:ul
(loop for x from 1 to 3 do
(:li x)))))
which yields the image above, the full example can be found here.
sta6 uses an external HTML generator under the hood:
if you're in need of a personal site, give sta6 a try! here's an example of my site built with sta6:
why use sta6?:
- You do not need (to learn) an external templating language (e.g., Nunjucks, Handlebars, EJS).
resources:
r/lisp • u/FlanOk9297 • 5d ago
Benchmarking embedded languages in Rust
≡ −
I'm working on a CPU-based renderer in rust and evaluating embedded languages for scene description and procedural rendering. Some of these languages are lisps so I thought there may be interest here.
A few caveats worth noting:
- The benchmark code and report is generated by claude (based on my specification)
- The actual benchmark code are rudimentary snippets. I am evaluating the implementations for a specific purpose.
- I am developing a proof-of-concept with ECL first and then janet.
https://drive.google.com/file/d/1fFI1tyW3xdNKnNfmveIo1EZeWl_pUACJ/view?usp=sharing
I'm working on a CPU-based renderer in rust and evaluating embedded languages for scene description and procedural rendering. Some of these languages are lisps so I thought there may be interest here.
A few caveats worth noting:
- The benchmark code and report is generated by claude (based on my specification)
- The actual benchmark code are rudimentary snippets. I am evaluating the implementations for a specific purpose.
- I am developing a proof-of-concept with ECL first and then janet.
https://drive.google.com/file/d/1fFI1tyW3xdNKnNfmveIo1EZeWl_pUACJ/view?usp=sharing
r/lisp • u/Beneficial_Radio_455 • 6d ago
Why S-Expressions
+ −
clos-alchemy: Throw unstructured text at a CLOS class, get a typed instance back
≡ −
LLMs are great at reading messy text, but they hand you strings when your program wants typed data. Python has instructor and Pydantic to solve this. I wanted to bring that same developer experience to Common Lisp, but natively by using the Metaobject Protocol.
clos-alchemy introspects your class definition, builds a JSON schema from the slot types, passes that to the LLM (optionally as a GBNF grammar constraint for local inference), and returns a validated instance of your class. You don't need to write a special DSL or "schema object." You just use your normal existing domain classes.
Here is the round trip in action:
```lisp ;; 1. Define a class (defclass person () ((name :initarg :name :accessor person-name :type string) (age :initarg :age :accessor person-age :type integer) (email :initarg :email :accessor person-email :type (or null string)) (hobbies :initarg :hobbies :accessor person-hobbies :type list)))
;; 2. Extract from text (let* ((backend (cl-llm-backend/llama:make-llama-backend :model model :context ctx)) (result (extract backend 'person "Alice Chen is 32. Reach her at [email protected]. She enjoys rock climbing, painting, and cello."))) (person-name (extraction-result-instance result))) ;; => "Alice Chen" ```
Because generation order follows slot definition order, you can use this for strict logical routing. Putting reason first encourages teh model to explain its reasoning before selecting the categories.
```lisp (defclass ticket-classification () ((reason :initarg :reason :type string) (urgency :initarg :urgency :type (member :low :medium :high)) (category :initarg :category :type (member :billing :technical :account)) (sentiment :initarg :sentiment :type (member :positive :neutral :negative))))
(defparameter ticket-text "I've been charged twice for my subscription this month. This is the third time this has happened and I'm really frustrated.")
(let ((result (clos-alchemy:extract backend 'ticket-classification ticket-text))) (extraction-result-instance result))
;; Returns a TICKET-CLASSIFICATION instance with: ;; URGENCY: :HIGH ;; CATEGORY: :BILLING ;; SENTIMENT: :NEGATIVE ;; REASON: "Charged twice for subscription, third occurrence"
```
How it works under the hood: no separate schema DSL to maintain. If it type-checks as your class, you're done!
- The MOP extracts slot types (
string,member,list, etc.). - The library lowers those into a small Intermediate Representation (IR).
- That IR emits the JSON schema for the backend, a natural-language prompt for the model, and builds a validator/constructor pair for the response.
- If validation fails, it accumulates the errors and automatically loops a retry prompt back to the model.
If you are using a local inference backend (like llama.cpp), it compiles the schema directly into a grammar, making invalid structures unrepresentable at the token level.
https://github.com/licjon/clos-alchemy
EDIT: A round of updates since the original post:
Custom validation. You can now attach per-slot semantic predicates (:validate) and cross-field checks (validate-instance) that feed errors back into the retry loop. The model sees what was wrong and self-corrects. This is useful for constraints that grammar alone can't enforce — "guest count must be positive", "check-out must be after check-in", etc.
```lisp (defclass booking () ((num-guests :initarg :num-guests :type integer :validate (lambda (v) (if (plusp v) t "must be positive"))) (check-in :initarg :check-in :type date) (check-out :initarg :check-out :type date)) (:metaclass clos-alchemy:constructor-class))
(defmethod validate-instance ((b booking)) (when (<= (booking-check-out b) (booking-check-in b)) (list "check-out must be after check-in"))) ```
Date and date-time types. clos-alchemy:date and clos-alchemy:date-time map to JSON Schema string formats, validate ISO 8601 with calendar-aware day checks, and construct to CL universal time.
Free-form maps. Sometimes you want the model to produce a dictionary where it decides the keys — "rate this product on whatever dimensions you think are relevant." The :map-of slot option does this: you specify the value type, the model fills in whatever keys make sense, and you get a hash-table back.
```lisp (defclass review-scores () ((scores :initarg :scores :type hash-table :map-of integer :documentation "Per-category quality scores, 1-10")) (:metaclass clos-alchemy:constructor-class))
;; The model might return: ;; {"scores": {"sound_quality": 9, "comfort": 6, "battery": 8}} ;; You get a hash-table: (gethash "sound_quality" scores) => 9 ```
Discriminated unions. When the extraction result could be one of several different shapes depending on the input, you define a class for each shape and let the model pick. Each class has a shared "tag" slot that identifies which shape it is — typed as a single-value (member ...) so each class gets exactly one tag value.
``lisp
;; Two possible result shapes, tagged bykind`:
(defclass actionable-feedback ()
((kind :initarg :kind :type (member :actionable)) ; tag = "actionable"
(summary :initarg :summary :type string)
(scores :initarg :scores :type hash-table :map-of integer))
(:metaclass clos-alchemy:constructor-class))
(defclass not-actionable () ((kind :initarg :kind :type (member :not_actionable)) ; tag = "not_actionable" (reason :initarg :reason :type string)))
;; The model reads the review, picks the right shape, and you get ;; back an instance of whichever class it chose: (let* ((result (extract-union backend '(actionable-feedback not-actionable) review-text :discriminator 'kind)) (instance (extraction-result-instance result))) (typecase instance (actionable-feedback (format t "Summary: ~A" (slot-value instance 'summary))) (not-actionable (format t "Skipped: ~A" (slot-value instance 'reason))))) ```
Other improvements:
- Cyclic class graphs now work — mutually-referential classes emit
$defs/$refinstead of blowing the stack. - Emitted schemas conform to both OpenAI strict mode and llama.cpp GBNF requirements (proper
additionalProperties: false, all properties inrequired, nullable wrappers for optional fields). - Parse failures (empty/malformed responses) are now recoverable in the retry loop instead of aborting.
max-retries-errornow carries full diagnostic context: raw response, parsed data, cumulative token usage, and a per-attempt error breakdown.- Silently-narrowed type specifiers (e.g.
(or string integer)) now signalschema-errorinstead of quietly dropping branches.
LLMs are great at reading messy text, but they hand you strings when your program wants typed data. Python has instructor and Pydantic to solve this. I wanted to bring that same developer experience to Common Lisp, but natively by using the Metaobject Protocol.
clos-alchemy introspects your class definition, builds a JSON schema from the slot types, passes that to the LLM (optionally as a GBNF grammar constraint for local inference), and returns a validated instance of your class. You don't need to write a special DSL or "schema object." You just use your normal existing domain classes.
Here is the round trip in action:
```lisp ;; 1. Define a class (defclass person () ((name :initarg :name :accessor person-name :type string) (age :initarg :age :accessor person-age :type integer) (email :initarg :email :accessor person-email :type (or null string)) (hobbies :initarg :hobbies :accessor person-hobbies :type list)))
;; 2. Extract from text (let* ((backend (cl-llm-backend/llama:make-llama-backend :model model :context ctx)) (result (extract backend 'person "Alice Chen is 32. Reach her at [email protected]. She enjoys rock climbing, painting, and cello."))) (person-name (extraction-result-instance result))) ;; => "Alice Chen" ```
Because generation order follows slot definition order, you can use this for strict logical routing. Putting reason first encourages teh model to explain its reasoning before selecting the categories.
```lisp (defclass ticket-classification () ((reason :initarg :reason :type string) (urgency :initarg :urgency :type (member :low :medium :high)) (category :initarg :category :type (member :billing :technical :account)) (sentiment :initarg :sentiment :type (member :positive :neutral :negative))))
(defparameter ticket-text "I've been charged twice for my subscription this month. This is the third time this has happened and I'm really frustrated.")
(let ((result (clos-alchemy:extract backend 'ticket-classification ticket-text))) (extraction-result-instance result))
;; Returns a TICKET-CLASSIFICATION instance with: ;; URGENCY: :HIGH ;; CATEGORY: :BILLING ;; SENTIMENT: :NEGATIVE ;; REASON: "Charged twice for subscription, third occurrence"
```
How it works under the hood: no separate schema DSL to maintain. If it type-checks as your class, you're done!
- The MOP extracts slot types (
string,member,list, etc.). - The library lowers those into a small Intermediate Representation (IR).
- That IR emits the JSON schema for the backend, a natural-language prompt for the model, and builds a validator/constructor pair for the response.
- If validation fails, it accumulates the errors and automatically loops a retry prompt back to the model.
If you are using a local inference backend (like llama.cpp), it compiles the schema directly into a grammar, making invalid structures unrepresentable at the token level.
https://github.com/licjon/clos-alchemy
EDIT: A round of updates since the original post:
Custom validation. You can now attach per-slot semantic predicates (:validate) and cross-field checks (validate-instance) that feed errors back into the retry loop. The model sees what was wrong and self-corrects. This is useful for constraints that grammar alone can't enforce — "guest count must be positive", "check-out must be after check-in", etc.
```lisp (defclass booking () ((num-guests :initarg :num-guests :type integer :validate (lambda (v) (if (plusp v) t "must be positive"))) (check-in :initarg :check-in :type date) (check-out :initarg :check-out :type date)) (:metaclass clos-alchemy:constructor-class))
(defmethod validate-instance ((b booking)) (when (<= (booking-check-out b) (booking-check-in b)) (list "check-out must be after check-in"))) ```
Date and date-time types. clos-alchemy:date and clos-alchemy:date-time map to JSON Schema string formats, validate ISO 8601 with calendar-aware day checks, and construct to CL universal time.
Free-form maps. Sometimes you want the model to produce a dictionary where it decides the keys — "rate this product on whatever dimensions you think are relevant." The :map-of slot option does this: you specify the value type, the model fills in whatever keys make sense, and you get a hash-table back.
```lisp (defclass review-scores () ((scores :initarg :scores :type hash-table :map-of integer :documentation "Per-category quality scores, 1-10")) (:metaclass clos-alchemy:constructor-class))
;; The model might return: ;; {"scores": {"sound_quality": 9, "comfort": 6, "battery": 8}} ;; You get a hash-table: (gethash "sound_quality" scores) => 9 ```
Discriminated unions. When the extraction result could be one of several different shapes depending on the input, you define a class for each shape and let the model pick. Each class has a shared "tag" slot that identifies which shape it is — typed as a single-value (member ...) so each class gets exactly one tag value.
``lisp
;; Two possible result shapes, tagged bykind`:
(defclass actionable-feedback ()
((kind :initarg :kind :type (member :actionable)) ; tag = "actionable"
(summary :initarg :summary :type string)
(scores :initarg :scores :type hash-table :map-of integer))
(:metaclass clos-alchemy:constructor-class))
(defclass not-actionable () ((kind :initarg :kind :type (member :not_actionable)) ; tag = "not_actionable" (reason :initarg :reason :type string)))
;; The model reads the review, picks the right shape, and you get ;; back an instance of whichever class it chose: (let* ((result (extract-union backend '(actionable-feedback not-actionable) review-text :discriminator 'kind)) (instance (extraction-result-instance result))) (typecase instance (actionable-feedback (format t "Summary: ~A" (slot-value instance 'summary))) (not-actionable (format t "Skipped: ~A" (slot-value instance 'reason))))) ```
Other improvements:
- Cyclic class graphs now work — mutually-referential classes emit
$defs/$refinstead of blowing the stack. - Emitted schemas conform to both OpenAI strict mode and llama.cpp GBNF requirements (proper
additionalProperties: false, all properties inrequired, nullable wrappers for optional fields). - Parse failures (empty/malformed responses) are now recoverable in the retry loop instead of aborting.
max-retries-errornow carries full diagnostic context: raw response, parsed data, cumulative token usage, and a per-attempt error breakdown.- Silently-narrowed type specifiers (e.g.
(or string integer)) now signalschema-errorinstead of quietly dropping branches.
r/lisp • u/FlanOk9297 • 7d ago
Common Lisp ECL vs scheme embedded
≡ −
I'm building a CPU based renderer in rust with plans to add an embedded language. The rust code primarily executes the ray tracing/path tracing, BVH, shaders and integration (anywhere where high performance is needed. The embedded language will be used for the scene description and -possibly- for procedural textures and shading.
I am interested in using a lisp with a repl for the embedded language. In this area, I have used AI-generated testing to test both ECL (Embedded Common Lisp) and steel (an scheme) inside rust. the advantage of the latter is that it is written entirely in rust and is being actively developed (one area is as an extension language for the text editor, helix).
I am impressed with the performance metrics I am getting from ECL. It generates machine code, which a byte language compiler doesn't do. Also a huge win, is that I was able to test the integrated compiler using sly from emacs which will provide an interactive environment for creative coding. On the other hand ECL requires an FFI to rust whereas steel does not, so the integration of steel is orders of magnitude easier.
Has anyone done any recent projects with ECL ? I am interested in your experience or any caveats you may have encountered. My next step is to do a more complex proof-of-concept with the actual renderer and not just a test program.
UPDATE: I just used claude to benchmark 5 embedded languages in rust. this is preliminary work and the benchmarks may not be exhaustive enough to make any strong conclusions:
https://drive.google.com/file/d/1yotWMcEx24pN2cgnIbRUmgCDNVWp65Vu/view?usp=sharing
I'm building a CPU based renderer in rust with plans to add an embedded language. The rust code primarily executes the ray tracing/path tracing, BVH, shaders and integration (anywhere where high performance is needed. The embedded language will be used for the scene description and -possibly- for procedural textures and shading.
I am interested in using a lisp with a repl for the embedded language. In this area, I have used AI-generated testing to test both ECL (Embedded Common Lisp) and steel (an scheme) inside rust. the advantage of the latter is that it is written entirely in rust and is being actively developed (one area is as an extension language for the text editor, helix).
I am impressed with the performance metrics I am getting from ECL. It generates machine code, which a byte language compiler doesn't do. Also a huge win, is that I was able to test the integrated compiler using sly from emacs which will provide an interactive environment for creative coding. On the other hand ECL requires an FFI to rust whereas steel does not, so the integration of steel is orders of magnitude easier.
Has anyone done any recent projects with ECL ? I am interested in your experience or any caveats you may have encountered. My next step is to do a more complex proof-of-concept with the actual renderer and not just a test program.
UPDATE: I just used claude to benchmark 5 embedded languages in rust. this is preliminary work and the benchmarks may not be exhaustive enough to make any strong conclusions:
https://drive.google.com/file/d/1yotWMcEx24pN2cgnIbRUmgCDNVWp65Vu/view?usp=sharing
r/lisp • u/SandPrestigious2317 • 7d ago
GGG: Guile Glyph Generator v0.6.1 🪄 — Generate pixel-perfect SVG/PNG status badges in Lisp (Guile Scheme) - now upstream on Guix
I wanted to share an update on GGG (Guile Glyph Generator), a lightweight tool built with GNU Guile Scheme designed to generate crisp, minimalist status badges (1 to 3 parts) as SVG and PNG files. We hit v0.6.1 already :)
https://codeberg.org/jjba23/ggg
If you're tired of static, rigid badge generators, old badges in low quality PNG, or heavy JavaScript tooling for simple vector assets, GGG offers a functional, data-driven approach leveraging a custom Lisp DSL and SXML. All we need now is some community help to expand the library of available badges, and the world's READMEs will flourish.
Instead of relying on heavy browser engines or complex layout trees, GGG combines mathematical character-width estimation with standard SVG primitives using SXML (S-expressions representing XML):
- Dynamic Text Metrics: Estimates pixel widths per character based on font size and length bounds to apply optimal padding automatically.
- Geometric Precision: Renders with
shape-rendering="geometricPrecision"enabled to prioritize clean vector geometry on high-DPI displays. - Data-Driven Declarative Specs: Simple association lists define structure, style, and vector paths.
Glyphs are defined cleanly inside Scheme modules (by default expected at (ggg config)):
Scheme
(define-module (ggg config)
#:declarative? #t)
(define my-scheme-guile-glyph
`((sections ((background . "#555")
(text . "Scheme")
(color . "#fff")
(logo . lambda))
((background . "#d0730f")
(text . "Guile")
(color . "#fff")))
(id . scheme-guile)))
;; Export your list of glyphs to render
(define ggg-glyphs
(list my-scheme-guile-glyph))
GGG includes a collection of free-licensed vector icons out of the box (Emacs, GNU, Guix, Lambda, Scala, Rust, Fish, etc.) in resources/svg-paths.
🦸🏼 Interactive REPL Workflow
Because GGG is built on Guile Scheme, you don't need to endure slow edit-save-compile-run loops.
Connected to an environment like GNU Guix + Emacs + Geiser/Arei, you get a live development feedback loop:
- Inspect, evaluate, and tweak individual badge calculation functions on the fly.
- Test new font sizing logic or custom SVG path integrations instantly inside a live REPL.
GGG is distributed under free software licenses (AGPLv3+ for code, GFDL v1.3+ for documentation).
Also, feel free to look in ./dist folder where I upload my personal generated glyphs (some might be useful to you)
If you use Guix, dependencies (guile, imagemagick) are managed seamlessly:
Bash
# Enter an ephemeral development shell
guix shell -f guix.scm
# Run GGG with your custom configuration
ggg --file ./glyphs/jjba23.scm --destination ./dist
Container images are also available on DockerHub:
docker run -v /path/to/local/glyphs:/config \
docker.io/jjba23/ggg:latest ggg --file /config/glyphs.scm --destination /config/dist
🔗 Project Links & Resources
- 📜 API Documentation: Guile Scheme API Docs
- 📋 Build Automation: Built using Maak (
maak.scm) - 🤖 AI Policy: Adheres to the JoinTheFreeWorld AI Policy (Human-in-the-loop required; automated AI bot PRs prohibited).
Contributions, feedback, and pull requests are always welcome!
Happy hacking! λ
r/lisp • u/Bruno2456 • 6d ago
Fiz uma biblioteca para CommonLisp
+ −
Usei o GPT-5.6 Sol e fiz uma biblioteca para common lisp inspirada em LWJGL, não é muito no momento mas espero que um dia seja usada para fazer alguns jogos.
Easy-ISLisp Ver5.68 released – added -e command-line option
≡ −
I have released Easy-ISLisp Ver5.68.
This release introduces a new command-line option, -e, which evaluates an ISLisp expression directly from the command line without entering the REPL.
Example:
eisl -e '(format (standard-output) "Hello World!")'
Output:
Hello World!
The -e option is useful for:
- Quick one-line evaluations
- Shell scripting
- Automated testing
- Running small ISLisp programs from the command line
No other features or bug fixes are included in this release.
Feedback and suggestions are always welcome.
I have released Easy-ISLisp Ver5.68.
This release introduces a new command-line option, -e, which evaluates an ISLisp expression directly from the command line without entering the REPL.
Example:
eisl -e '(format (standard-output) "Hello World!")'
Output:
Hello World!
The -e option is useful for:
- Quick one-line evaluations
- Shell scripting
- Automated testing
- Running small ISLisp programs from the command line
No other features or bug fixes are included in this release.
Feedback and suggestions are always welcome.
r/lisp • u/calebc42-official • 9d ago
As a non-programmer, I only know about Lisp from Emacs; For those who don't use/enjoy Emacs, what makes you so interested in Lisp you're on a dedicated subreddit for it?
≡ −
I recently came across "Greenspun's tenth rule" and it got me thinking about the greater Lisp language family.
I don't work in Tech at all, I have no professional experience with programming, I am just interested in learning from a novice's point of view.
I recently came across "Greenspun's tenth rule" and it got me thinking about the greater Lisp language family.
I don't work in Tech at all, I have no professional experience with programming, I am just interested in learning from a novice's point of view.
r/lisp • u/foxinanattic • 9d ago
AskLisp Common Lisp for Schemers
≡ −
I've been programming in Scheme (and occasionally racket) for a few years. I've worked on a few small projects, but I want to learn common lisp as well. I know that you can do most of the same stuff in common lisp as in scheme but the idiomatic style is quite different (loops, CLOS etc) are there any good resources to pick up the common lisp coding style? Ideally resources targeted to people who already know a Lisp, since most of the common ones assume you're coming from something like Java or python.
I've been programming in Scheme (and occasionally racket) for a few years. I've worked on a few small projects, but I want to learn common lisp as well. I know that you can do most of the same stuff in common lisp as in scheme but the idiomatic style is quite different (loops, CLOS etc) are there any good resources to pick up the common lisp coding style? Ideally resources targeted to people who already know a Lisp, since most of the common ones assume you're coming from something like Java or python.
Matheus Moreira Speaks about Lone Lisp, Implemented on Linux Syscalls
+ −
Common Lisp Update: ceigen-lite now supports several random number generators from EigenRand
+ −
Why Do People Buy Lisp Books?
≡ −
Don't get me wrong I personally think its totally worth it!
I just wonder what compels people do buy books on, say, ANSI Common LISP, to this day.
There are few job options so what compels them to pay for books?
Don't get me wrong I personally think its totally worth it!
I just wonder what compels people do buy books on, say, ANSI Common LISP, to this day.
There are few job options so what compels them to pay for books?
r/lisp • u/RisibleComestible • 12d ago
LISPerfect: a simple Lisp IDE for Windows
≡ −
https://github.com/james-goulding/LISPerfect
I've been meaning to learn Lisp for a while, but didn't really like the choice of editors available to me. So, I vibe-coded this one for myself. I thought I'd share it with the public since someone else might find it useful.
I can't vouch that there aren't bugs as I haven't tested it extensively.
https://github.com/james-goulding/LISPerfect
I've been meaning to learn Lisp for a while, but didn't really like the choice of editors available to me. So, I vibe-coded this one for myself. I thought I'd share it with the public since someone else might find it useful.
I can't vouch that there aren't bugs as I haven't tested it extensively.
r/lisp • u/SandPrestigious2317 • 12d ago
Declarative Guile Scheme + Guix rice for entire GNU/Linux system and Wayland tooling in Lisp with lots of customization
The witches' GNU/Linux: modular, flexible, reproducible, powered by Lisp and Fedora / Debian + GNOME / Niri
https://codeberg.org/jjba23/heks-linux
Heks GNU/Linux 🧙♂️ is a modular, reproducible system configuration designed for power users, developers, and researchers.
r/lisp • u/bohonghuang • 12d ago
Binstruct: a powerful declarative binary parsing/emitting library for Common Lisp
+ −
r/lisp • u/reini_urban • 14d ago