mark-watson/loving-common-lisp/common-lisp-skill
Claude Code and agent skill by mark-watson/loving-common-lisp
- community
Checking that the SKILL.md file is still in the repository.
Write Common Lisp in Mark Watson's style for the loving-common-lisp project. Quicklisp, ASDF, UIOP / Dexador / QURI / cl-json, file IO, and a deliberately plain no-LOOP style.
Install
npx vskill@latest install mark-watson/loving-common-lisp/common-lisp-skillSKILL.md
Skill: Common Lisp in Mark Watson's Style
Writing Common Lisp for Mark Watson's loving-common-lisp project. The audience is a model strong in Python and TypeScript and weak in Lisp, so treat the rules below as absolute.
0. How you work here — you do not run code
The developer runs everything. You never execute Lisp, and you never claim you did.
- Forms to evaluate — the smallest set that proves the point, in one
lisp
block, in evaluation order, starting with any ql:quickload. Say what you expect back.
- A file to load — write it, then ask the developer to run
sbcl --script path/to/file.lisp and paste what it prints.
Ask for the specific thing you need ("paste the value of the last form"), not "the output". Verify the risky parts only; read the real error before guessing. Until output comes back, say the code is unverified. sbcl --script skips ~/.sbclrc, so it works whatever the developer's Quicklisp setup looks like; if the file uses uiop: or asdf:, put (require :asdf) on its first line.
1. The ten rules
- Never write `loop` (see 2.5). Leave old
loopforms insrc/alone unless asked. - Every `(` must be closed — an unbalanced paren is the most common failure.
- Locals must be declared by
let/let*/dolist/dotimes/defunargs; there is no "assign and it exists". - Use `format`, not `print`, for anything a human reads.
- A function returns its last form; use
return/return-fromto exit early. - Only `nil` is false.
0,"", and#()are all true; there is no separateFalse. - Strings compare with `string=` or `equal`; numbers with
=. - Hash tables with string keys need `:test #'equal`.
- `if` takes both branches; with one branch use
when/unless. - Match the surrounding file — its package, indentation, and naming.
2. Mark's style
2.1 Layout and naming
Two spaces per indent; closing parens stack on the last line. ;;; headers, ;; inside functions, ; at end of line. A docstring is the first form of defun/defclass/defstruct/defvar/defparameter. Names: fetch-url; predicates valid-uri-p; globals *earmuffs*; constants +plus-signs+; accessors type-slot.
2.2 Packages and globals
Use #:name (uninterned symbol) for the package name, and export everything the outside world calls — otherwise callers get The symbol X is not external.
Inside the package call your own functions bare; outside use my-system:run. cl-user is the wrong place for real code.
2.3 Functions
Use either &optional or &key, never both.
Never write `(defun f (x &optional y &key z) ...)`. The optional parameterswallows the first keyword, so(f 1 :z 2)binds:Ztoyand then signalsodd number of &KEY arguments.
Order is &optional, then &rest, then &key. Never name a function after a CL symbol — report, count, remove, find, sort, length are locked, and redefining one signals Lock on package COMMON-LISP violated. Add a suffix.
2.4 Conditionals
2.5 No loop — the translation table
Mark, in manuscript/loop_macros.md: "a complex looping macro that I almost never use in my own code because it does not look Lisp like."
| Do not write | Write instead | |
|---|---|---|
(loop for x in xs do (f x)) | (dolist (x xs) (f x)) | |
(loop for i from 0 below n do ...) / (loop repeat n do ...) | (dotimes (i n) ...) | |
(loop while test do ...) | (do () ((not test)) ...) | |
(loop for x in xs collect (f x)) | (mapcar #'f xs) | |
(loop for x in xs when (p x) collect x) | (remove-if-not #'p xs) | |
(loop for x in xs unless (p x) collect x) | (remove-if #'p xs) | |
(loop for x in xs sum x) | (reduce #'+ xs) or an accumulator | |
(loop for x in xs count (p x)) | (count-if #'p xs) | |
(loop for x in xs always (p x)) | (every #'p xs) | |
(loop for k being the hash-keys of h ...) | maphash + collect (see 2.6) |
The accumulator idiom you will use constantly — push builds the list backwards, nreverse at the end, and the third argument of dolist/dotimes is the return value:
2.6 Iteration, hash tables, structs
The (declare (ignore v)) is needed or SBCL warns about the unused variable. defstruct defines make-<name>, <name>-<slot>, and <name>-p; use it instead of a class when there is no behaviour.
3. Language core
3.1 Syntax
The reader upcases symbols, so Foo and FOO are the same symbol. 1.0 is a single-float; write 1.0d0 for a double.
3.2 Truth and equality — read this twice
Only nil is false. 0, "", #(), and :false are all true.
| Test | Use it for | Example | |
|---|---|---|---|
= | numbers only | (= 1 1.0) → T | |
char= / char-equal | characters, case-sensitive / not | (char-equal #\a #\A) → T | |
string= / string-equal | strings | (string= "a" "a") → T | |
eq | identical object, same symbol | (eq 'a 'a) → T | |
eql | numbers and characters of the same type | (eql 1 1.0) → NIL | |
equal | lists, strings, structures — the default choice | (equal '(1 2) '(1 2)) → T | |
equalp | like equal, case/type-insensitive, compares vectors | (equalp #(1 2) #(1 2)) → T | |
null | is it nil | (null nil) → T | |
member | list membership | (member 2 '(1 2 3)) → (2 3) |
Two traps: (equal #(1 2) #(1 2)) is NIL — equal does not descend into vectors, so use equalp. And boolean functions return a generalised truth value, not necessarily t: (member 3 '(1 2 3)) → (3), (search "zz" "abc") → NIL.
3.3 Locals, recursion, multiple values
let for independent bindings, let* when one depends on another. setf assigns to anything: variables, (aref v i), (gethash k h), (point-x p). labels defines local recursive functions, flet non-recursive ones, (lambda (x) ...) anonymous ones. gethash, read-from-string, dex:get, and uiop:run-program return extra values — capture them with multiple-value-bind, not let.
3.4 Lists, vectors, sequences
mapcar, remove-if, remove-if-not, reduce, find-if, position, count-if, some, and every are the sequence functions you reach for instead of loop — see the table in 2.5. sort and nreverse destroy their argument; copy first when the list is shared: (sort (copy-list xs) #'<).
The default hash-table :test is #'eql, which will not find string keys built at runtime — pass :test #'equal for strings, #'eq for keywords.
3.5 CLOS, and alists
Slots need :initarg to be settable from make-instance and :accessor (or :reader) to be readable; :initform gives a default. defmethod dispatches on the class of its arguments, and a subclass method overrides the parent.
4. Strings and FORMAT
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingcommon-lisp-skill/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/mark-watson/loving-common-lisp/common-lisp-skill)