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).
Documentation:
- Land of Lisp – best comic book/music video on paren-based-programming ever.
- SBCL Manual
- Common Lisp Hyperspec
- format specifiers
- usable format specifiers cheatsheet!
- CL Cookbook
- Tutorialspoint Lisp Tutorial
REPL
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.
(SB-IMPL::READ-MAYBE-NOTHING #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {1000023B83}> #\#)
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)))
Constants
- 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
Vars
- (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
Math/Logic
- (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?
Char/Strings
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
Sequences
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!
Lists
- (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
, inverseexists
- (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
, inverseremp
Control
- (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
- (if TEST THEN ELSE)
- (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 offor
macros, or for-each over iota. - (progn BODY) ;;
begin
- (when TEST BODY), (unless TEST BODY)
(defun test-number (x)
(cond
((not (numberp x)) "not a number")
((zerop x) "0")
((plusp x) "+")
((minusp x) "-")
(T "?")
))
Input/Output
- (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
orprocess
in Scheme, tho not standard, most every impl has it. LOL calls thisext:shell
, SBCL docs good grief.