A Schemer in Common Lisp Land

Basic functionality of Common Lisp (SBCL) seen from a Schemer’s view. I list commands in lowercase, fields are UPPERCASE, {} are optional, ... are repeat.

Everything after ;; comments is translation into a sane language.

General style notes: -p = -?. CL does not mark mutating commands with !, beware that everything mutates everything, you can’t really do FP in this. There is no tail-call elimination, recursion fills the stack and crashes eventually (soon; CL’s a fat piggy for memory).



I would like to use hashbang #!/usr/bin/env sbcl --script

And that works from shell, but SBCL doesn’t let you (load) such a script from REPL. Because Common LISP pretends UNIX never happened, even tho UNIX is 14 years older than CL. Scheme is 9 years older than CL and almost all impls (not MIT-Scheme) allow hashbangs in included scripts.

% rlwrap sbcl ;; no builtin line/term editor. For fuck’s sake, amateurs.

Long-ass startup banner:

This is SBCL 2.2.2, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.

Have later learned --noinform disables this, so now I have a script lol.zsh:

rlwrap sbcl --noinform "$@"

There is a startup file, ~/.sbclrc which can be given some library startup, like:

(load "~/Code/CodeLisp/marklib.lisp")
(prln "READY")

prompt is *, debugger is 0] etc. with this charming interaction:

debugger invoked on a SB-INT:SIMPLE-READER-ERROR in thread
#<THREAD "main thread" RUNNING {1001818003}>:
  no dispatch function defined for #\T
    Stream: #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000023B83}>
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.
0] 0

And can’t just q out of the debugger, have to find the last item in the menu and type that.

  • (load FILENAME) ;; include file

Function References

This is the big pain in the ass to a Schemer.

  • #'FUNCNAME ;; FUNCNAME for function references
  • (lambda (ARG…) BODY) ;; same! Hooray!
  • (complement #’FUNCNAME) ;; (λ (x) (not (FUNCNAME x)))


  • NIL, () ;; #f false value, but also self-eval empty list ‘() = ()
  • (QUOTE x) ;; quote value of x, also \`',x or (list 'quote x)
  • T ;; #t true value
  • #\newline (0x0A), #\space (0x20), #\tab (0x09), #\backspace (0x08), #\linefeed (0x0A), #\page (0x0C), #\return (0x0D), #\rubout (0x7F) ;; special chars
  • #\A … #\Z ;; normal chars
  • *debug-io* ;; port for interactive debugging
  • *error-output* ;; port for stderr, warnings and non-interactive error messages
  • *query-io* ;; port for bidi stream, prompt/response
  • *standard-input* ;; port for stdin
  • *standard-output* ;; port for stdout
  • *trace-output* ;; port for traced functions & time


  • (defparameter NAME VALUE) ;; global dynamic *var*
  • (defvar NAME VALUE) ;; global dynamic *var*, won’t replace value if already exists
  • (pop PLACE) ;; (let [(y (car PLACE))] (set! PLACE (cdr PLACE)) y) – I use a list-pop! macro for this.
  • (push X PLACE) ;; (let [(ls (cons X PLACE))] (set! PLACE ls) ls) – I use a list-push! macro for this.
  • (setf PLACE VALUE) ;; set! – use this, allows accessors as PLACE
  • (setq NAME VALUE) ;; set!, NAME is auto-quoted
  • (set NAME VALUE) ;; set!, NAME is not auto-quoted


  • (1+ X), (1- X) ;; inc/add1, dec/sub1
  • (and X…), (or X…)
  • (ash X Y) ;; arithmetic-shift X << Y, positive = left, negative = right
  • (eq X Y), (eql X Y), (equal X Y), (equalp X Y) ;; eq?, eqv?, equal?, equal-ignore-case?
  • (evenp X), (oddp X), (zerop X), (plusp X), (minusp X) ;; last 2 are positive?, negative?


Strings have no escape codes except \\ and \". Can you believe that shit? AutoCAD & elisp do, and there’s some quicklisp package I can’t figure out how to install.

  • (alphanumericp C) ;; (or (char-alphabetic? c) (char-numeric? c))
  • (alpha-char-p C), (digit-char-p C), (graphic-char-p C) ;; char-alpha?, etc., “graphic” is all non control chars.
  • (char S X) ;; string-ref
  • (char-downcase S), (char-upcase S)
  • (char-code N), (code-char C) ;; integer->char, char->integer. there’s also char-int, but they removed int-char!
  • (string-downcase S), (string-upcase S), (string-capitalize S)
  • (string-trim S), (string-right-trim S), (string-left-trim S)
  • (stringp S)
  • (write-to-string N), (parse-integer S) ;; number->string, string->number


Strings or lists.

  • (length S)
  • (reverse S)
  • (subseq S X {Y}) ;; substring
  • (substitute-if C #’FUNCNAME S) ;; SRFI-13 string-map, string-filter, string-delete, etc. Actually this took me a minute to realize how fundamental it is. It’s just map!


  • (apply #’FUNCNAME LS)
  • (assoc KEY LS)
  • (concatenate TYPE X…) ;; TYPE is ‘string, ‘list, ‘vector.
  • (cons X Y), (list X…)
  • (find X LS { :key #’FUNCNAME }), (find-if #’FUNCNAME LS), (find-if-not #’FUNCNAME LS) ;; find, exists, inverse exists
  • (mapcar #’FUNCNAME LS) ;; map
  • (mapc #’FUNCNAME LS) ;; for-each
  • (member X LS)
  • (null X) ;; null? but not nullp!
  • (remove X LS), (remove-if #’FUNCNAME LS), (remove-if-not #’FUNCNAME LS) ;; remove, remp, inverse remp


  • (case ( ((X…) THEN)… { (otherwise ELSE) } )) ;; otherwise = else
  • (cond ( (TEST THEN)… { (T ELSE) } )) ;; no else keyword
  • (defun NAME (ARGS) BODY) ;; define
  • (do ( (NAME X0 UPDATEEXPR)… ) (TESTEXPR RETVAL) BODY) ;; uuuugh.
  • (dolist (NAME LS) BODY) ;; (for-each (λ (NAME) BODY) LS)
  • (dotimes (NAME X) BODY) ;; repeat
  • (flet ( (FUNCNAME (X…) FUNCBODY)… ) BODY) ;; letrec
  • (labels ( (FUNCNAME (X…) FUNCBODY)… ) BODY) ;; letrec*
  • (let ( (NAME VALUE)… ) BODY) ;; let
  • (let* ( (NAME VALUE)… ) BODY) ;; let*
  • (loop BODY) ;; loops until (return X), like a loop inside call/cc
  • (loop for NAME in LS do BODY) ;; for-each
  • (loop for NAME from X to Y do BODY) ;; Schemers would use named let (preferred), do (ugh), or one of dozens of for macros, or for-each over iota.
  • (progn BODY) ;; begin
  • (when TEST BODY), (unless TEST BODY)


(defun test-number (x)
    ((not (numberp x))  "not a number")
    ((zerop x)  "0")
    ((plusp x)  "+")
    ((minusp x)  "-")
    (T  "?")


  • (finish-output {PORT}) ;; (flush-output-port {PORT})
  • (format OUTPUT PATTERN ARG…)
  • (terpri), (fresh-line) ;; (newline), newline only if not at column 0
  • (prin1 X), (prin1-to-string X) ;; (write X)
  • (princ X), (princ-to-string X) ;; (display X) (display #\space)
  • (print X) ;; (newline) (display X) (display #\space)
  • (read), (read-line), (read-from-string X)
  • (write X)

All of the print commands suck, they add extra noise, there is no pure display, they don’t flush their buffers. print puts newline first! WTF. So:

(defun fprint (port ls end)
    (assert (or (eq port T) (streamp port)))
    ;; ~:A prints () for nil
    (format port "~{~:A~}~A" (if (listp ls) ls (list ls)) end)
    (finish-output port)
(defun fpr (port ls) (fprint port ls ""))
(defun fprln (port ls) (fprint port ls #\newline))
(defun pr (ls) (fprint T ls ""))
(defun prln (ls) (fprint T ls #\newline))
(prln (list "a=" a ", b=" 3.14))
a=69, b=3.14
  • (with-open-file (STREAM FILESPEC {:direction DIR} {:if-exists IFEX} {:if-does-not-exist IFNEX}) BODY) ;; overengineered open wrapper

    STREAM varname is dynamically assigned, so you can do *standard-output* and it’ll capture all printing until BODY ends

    DIR is :input (default), :output, :io, :probe

    IFEX is :error, :new-version, :rename, :rename-and-delete, :overwrite, :append, :supersede, nil (default)

    IFNEX is :error, :create, nil (default varies by DIR! UGH!)
  • (sb-ext:run-program PROGRAM ARGS …) ;; system or process in Scheme, tho not standard, most every impl has it. LOL calls this ext:shell, SBCL docs good grief.