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, followed by ;; comments, and 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, no obvious way to disable it:

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.

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


  • 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, #\space, #\tab ;; chars
  • *debug-io* ;; interactive debugging
  • *error-output* ;; stderr, warnings and non-interactive error messages
  • *query-io* ;; bidi stream, prompt/response
  • *standard-input* ;; stdin
  • *standard-output* ;; stdout
  • *trace-output* ;; traced functions & time


  • (defparameter NAME VALUE) ;; global dynamic *var*
  • (defvar NAME VALUE) ;; global dynamic *var*, won't replace value if already exists
  • (pop X 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.

  • (char S X)
  • (char-downcase S), (char-upcase S)
  • (length S)
  • (reverse S)
  • (string-downcase S), (string-upcase S), (string-capitalize S)
  • (string-trim S), (string-right-trim S), (string-left-trim S)
  • (stringp S)
  • (subseq S X {Y}) ;; substring


  • (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
  • (member X LS)
  • (null X) ;; null?
  • (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 UPDATEFUNC)... ) (TESTFUNC RETVAL) BODY) ;; uuuugh.
  • (dolist (NAME LS) BODY) ;; (for-each (λ (NAME) BODY) LS)
  • (dotimes (NAME X) BODY) ;; repeat
  • (flet ( DEFUN... ) BODY) ;; letrec
  • (labels ( DEFUN... ) 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