Common Lisp Cookbook
Common Lisp Cookbook
The CL Cookbook aims to tackle all sort of topics, for the beginner as for
the more advanced developer.
Content
Getting started
License
Getting started
How to install a Common Lisp implementation
How to start a Lisp REPL
How to install third-party libraries with Quicklisp
How to work with projects
Editor support
Using Emacs as an IDE
The LispWorks IDE
Using VSCode with Alive
Language basics
Functions
Data Structures
Strings
Regular Expressions
Numbers
Loops, iteration, mapping
Multidimensional Arrays
Dates and Times
Pattern Matching
Input/Output
Files and Directories
CLOS (the Common Lisp Object System)
Advanced topics
Packages
Defining Systems
Error and condition handling
Debugging
Macros and Backquote
Type System
Concurrency and Parallelism
Performance Tuning
Testing and Continuous Integration
Scripting. Building executables
Outside world
Download in EPUB
The Cookbook is also available in EPUB (and PDF) format.
You can download it directly in EPUB and PDF, and you can pay what you
want to further support its development:
Thank you!
Translations
The Cookbook has been translated to:
📹
Cliki, Common Lisp’s wiki
Common Lisp programming: from novice to effective developer, a
video course on the Udemy platform (paywall), by one of the main
Cookbook contributor. “Thanks for supporting my work on Udemy.
You can ask me for a free coupon if you are a student.” vindarel
Books
Specifications
The pages here on Github are kept up to date. You can also download a up
to date zip file for offline browsing. More info can be found at the Github
project page.
License
Redistribution and use of the “Common Lisp Cookbook” in its original
form (HTML) or in ‘derived’ forms (PDF, Postscript, RTF and so forth)
with or without modification, are permitted provided that the following
condition is met:
We hope that these EPUB and PDF versions make the learning experience
even more practical and enjoyable.
Install an implementation
With your package manager
The following implementations are packaged for Debian and most other
popular Linux distributions:
The asdf-vm tool can be used to manage a large ecosystem of runtimes and
tools.
Steel Bank Common Lisp (SBCL) is available via this plugin for asdf-
vm
With Roswell
Roswell is:
If you already know Docker, you can get started with Common Lisp pretty
quickly. The clfoundation/cl-devel image comes with recent versions of
SBCL, CCL, ECL and ABCL, plus Quicklisp installed in the home
(/home/cl), so than we can ql:quickload libraries straight away.
The following command will download the required image (around 1.0GB
compressed), put your local sources inside the Docker image where
indicated, and drop you into an SBCL REPL:
docker run --rm -it -v /path/to/local/code:/home/cl/common-
lisp/source clfoundation/cl-devel:latest sbcl
On Windows
Start a REPL
Just launch the implementation executable on the command line to enter the
REPL (Read Eval Print Loop), i.e. the interactive interpreter.
3
* (quit)
user@debian:~$
You can slightly enhance the REPL (the arrow keys do not work, it has no
history,…) with rlwrap:
apt-get install rlwrap
and:
rlwrap sbcl
But we’ll setup our editor to offer a better experience instead of working in
this REPL. See editor-support.
Libraries
Common Lisp has thousands of libraries available under a free software
license. See:
Some terminology
Install Quicklisp
It provides its own dist but it is also possible to build our own.
or
This will create the ~/quicklisp/ directory, where Quicklisp will maintain
its state and downloaded projects.
If you wish, you can install Quicklisp to a different location. For instance,
to install it to a hidden folder on Unix systems:
(quicklisp-quickstart:install :path "~/.quicklisp")
Install libraries
In the REPL:
(ql:quickload "system-name")
Anytime you want to use a third-party library in your Lisp REPL, you can
run this ql:quickload command. It will not hit the network a second time
if it finds that the library is already installed on your file system. Libraries
are by default installed in ~/quicklisp/dist/quicklisp/.
Note also that dozens of Common Lisp libraries are packaged in Debian.
The package names usually begin with the cl- prefix (use apt-cache
search --names-only "^cl-.*" to list them all).
For example, in order to use the cl-ppcre library, one should first install
the cl-ppcre package.
See Quicklisp’s documentation for more commands. For instance, see how
to upgrade or rollback your Quicklisp’s distribution.
You can drop Common Lisp projects into any of those folders:
~/quicklisp/local-projects
~/common-lisp,
~/.local/share/common-lisp/source,
A library installed here is automatically available for every project.
and
asdf:*central-registry*
Given the property above, we can clone any library into the
~/quicklisp/local-projects/ directory and it will be found by ASDF
(and Quicklisp) and available right-away:
(asdf:load-system "system")
or
(ql:quickload "system")
The practical different between the two is that ql:quickload first tries to
fetch the system from the Internet if it is not already installed.
But what if we want to work with an existing project or create a new one,
how do we proceed, what’s the right sequence of defpackage, what to put
in the .asd file, how to load the project into the REPL ?
Some project builders help to scaffold the project structure. We like cl-
project that also sets up a tests skeleton.
In short:
(ql:quickload "cl-project")
(cl-project:make-project #P"./path-to-project/root/")
You have created a new project, or you have an existing one, and you want
to work with it on the REPL, but Quicklisp doesn’t know it. How can you
do ?
Otherwise you’ll need to compile and load its system definition (.asd) first.
In SLIME with the slime-asdf contrib loaded, type C-c C-k (slime-
compile-and-load-file) in the .asd, then you can (ql:quickload …) it.
Usually you want to “enter” the system in the REPL at this stage:
(in-package :my-project)
Lastly, you can compile or eval the sources (my-project.lisp) with C-c
C-k or C-c C-c (slime-compile-defun) in a form, and see its result in the
REPL.
Another solution is to use ASDF’s list of known projects:
;; startup file like ~/.sbclrc
(pushnew "~/path-to/project/" asdf:*central-registry* :test #'eq
and since ASDF is integrated into Quicklisp, we can quickload our project
right away.
Happy hacking !
More settings
You might want to set SBCL’s default encoding format to utf-8:
(setf sb-impl::*default-external-format* :utf-8)
If you dislike the REPL to print all symbols upcase, add this:
(setf *print-case* :downcase)
See also
cl-cookieproject - a project skeleton for a ready-to-use project with an
entry point and unit tests. With a src/ subdirectory, some more
metadata, a 5AM test suite, a way to build a binary, an example CLI
args parsing, Roswell integration.
Source code organization, libraries and packages:
https://lispmethods.com/libraries.html
Credits
https://wiki.debian.org/CommonLisp
http://articulate-lisp.com/project/new-project.html
Editor support
The editor of choice is still Emacs, but it is not the only one.
Emacs
SLIME is the Superior Lisp Interaction Mode for Emacs. It has support for
interacting with a running Common Lisp process for compilation,
debugging, documentation lookup, cross-references, and so on. It works with
many implementations.
Installing SLIME
SLIME is in the official GNU ELPA repository of Emacs Lisp packages (in
Emacs24 and forward). Install with:
M-x package-install RET slime RET
Since SLIME is heavily modular and the defaults only do the bare minimum
(not even the SLIME REPL), you might want to enable more features with
(slime-setup '(slime-fancy slime-quicklisp slime-asdf))
For more details, consult the documentation (also available as an Info page).
Now you can run SLIME with M-x slime and/or M-x slime-connect.
See also:
Vlime is a Common Lisp dev environment for Vim (and Neovim), similar to
SLIME for Emacs and SLIMV for Vim.
cl-neovim makes it possible to write Neovim plugins in Common Lisp.
Slimv_box brings Vim, SBCL, ABCL, and tmux in a Docker container for a
quick installation.
See also:
Lisp in Vim demonstrates usage and compares both Slimv and Vlime
REPL
integrated debugger
(not a stepping debugger yet)
jump to definition
autocomplete suggestions based on your code
compile this function, compile this file
function arguments order
integrated profiler
interactive object inspection.
VSCode
Alive makes VSCode a powerful Common Lisp development. It hooks
directly into the Swank server that Emacs Slime uses and is fully compatible
with VSCode’s ability to develop remotely in containers, WSL, Remote
machines, etc. It has no dependencies beyond a version of Common Lisp on
which to run the Swank server. It can be configured to run with Quicklisp,
CLPM, and Roswell. It currently supports:
Syntax highlighting
Code completion
Code formatter
Jump to definition
Snippets
REPL integration
Interactive Debugger
REPL history
Inline evaluation
Macro expand
Disassemble
Inspector
Hover Text
Rename function args and let bindings
Code folding
commonlisp-vscode extension works via the cl-lsp language server and it’s
possible to write LSP client that works in other editors. It depends heavily on
Roswell. It currently supports:
running a REPL
evaluate code
auto indent,
code completion
go to definition
documentation on hover
Using VSCode with Alive
REPL
symbol completion
send expressions to the REPL
interactive debugging, breakpoints
documentation display
cross-references
find symbol by name, global class/symbol search
inspector (read-only)
graphical threads list
SDK support, automatic download for Windows users
multiple implementations support: SBCL, CCL, ABCL and AllegroCL.
Eclipse
Dandelion is a plugin for the Eclipse IDE.
Available for Windows, Mac and Linux, built-in SBCL and CLISP support
and possibility to connect other environments, interactive debugger with
restarts, macro-expansion, parenthesis matching,…
Lem
Lem is an editor tailored for Common Lisp development. Once you install it,
you can start developing. Its interface resembles Emacs and SLIME (same
shortcuts). It comes with an ncurses and an SDL2 frontend, and other
programming modes thanks to its built-in LSP client: Python, Go, Rust, JS,
Nim, Scheme, HTML, CSS, plus a directory mode, a vim layer, and more.
It can be started as a REPL right away in the terminal. Run it with:
lem --eval "(lem-lisp-mode:start-lisp-repl t)"
First install the “SublimeREPL” package and then see the options in
Tools/SublimeREPL to choose your CL implementation.
Then Slyblime ships IDE-like features to interact with the running Lisp
image. It is an implementation of SLY and it uses the same backend
(SLYNK). It provides advanced features including a debugger with stack
frame inspection.
LispWorks (proprietary)
LispWorks is a Common Lisp implementation that comes with its own
Integrated Development Environment (IDE) and its share of unique features,
such as the CAPI GUI toolkit. It is proprietary and provides a free limited
version.
REPLs
cl-repl is an ipython-like REPL. It supports symbol completion, magic and
shell commands, editing command in a file and a simple debugger.
You might also like sbcli, an even simpler REPL with readline capabilities.
It handles errors gracefully instead of showing a debugger.
Others
There are some more editors out there, more or less discontinued, and free
versions of other Lisp vendors, such as Allegro CL.
Emacs
Using Emacs as an IDE
This page is meant to provide an introduction to using Emacs as a Lisp IDE.
Learning Emacs Lisp is useful and similar (but different from CL):
Dynamic scope is everywhere
There are no reader (or reader-related) functions
Does not support all the types that are supported in CL
Incomplete implementation of CLOS (with the add-on EIEIO
package)
Not all of CL is supported
No numerical tower support
Some good Emacs Lisp learning resources:
An Introduction to Programming in Emacs Lisp
Writing GNU Emacs Extensions
Wikemacs
Pros:
Provides REPL which is hooked to implementation directly in
Emacs
Has integrated Common Lisp debugger with Emacs interface
Interactive object-inspector in Emacs buffer
Has its own minor mode which enhances lisp-mode in many ways
Supports every common Common Lisp implementation
Readily available from MELPA
Actively maintained
Symbol completion
Cross-referencing
Can perform macroexpansions
Cons:
Installing SLIME without MELPA can be tricky
Setup:
Installing it from MELPA is straightforward. Search package-list-
packages for ‘slime’ and click to install. If MELPA is configured
correctly, it will install itself and all dependencies.
Enable the desired contribs (SLIME does very little by defaults),
e.g. (slime-setup '(slime-fancy slime-quicklisp slime-
asdf)).
Run SLIME with M-x slime.
Check out this video tutorial ! (and the author’s channel, full of great stuff)
slime-autodoc
slime-c-p-c
slime-editing-commands
slime-fancy-inspector
slime-fancy-trace
slime-fontifying-fu
slime-fuzzy
slime-mdot-fu
slime-macrostep
slime-presentations
slime-references
slime-repl
slime-scratch
slime-package-fu
slime-trace-dialog
SLIME also has some nice extensions like Helm-SLIME which features,
among others:
Fuzzy completion,
REPL and connection listing,
Fuzzy-search of the REPL history,
Fuzzy-search of the apropos documentation.
REPL interactions
,load-system
,reload-system
,in-package
,restart-inferior-lisp
With the slime-quicklisp contrib, you can also ,ql to list all systems
available for installation.
The help keybindings start with either C-h or F1. Important ones are:
Sometimes, you start typing a key sequence but you can’t remember it
completely. Or, you wonder what other keybindings are related. Comes
which-key-mode. This packages will display all possible keybindings
starting with the key(s) you just typed.
For example, I know there are useful keybindings under C-x but I don’t
remember which ones… I just type C-x, I wait for half a second, and which-
key shows all the ones available.
Just try it with C-h too!
See also Helpful, an alternative to the built-in Emacs help that provides
much more contextual information.
Learn Emacs with the built-in tutorial
Emacs ships its own tutorial. You should give it a look to learn the most
important keybindings and concepts.
Editing
Use C-M-t to swap the first addition sexp and the second one. Put the cursor
on the open parens of “(+ x” in defun c and press
Use C-M-@ to highlight an entire sexp. Then press C-M-u to expand the
selection “upwards” and C-M-d to move forward down one level of
parentheses.
Deleting s-expressions
Use C-M-k (kill-sexp) and C-M-backspace (backward-kill-sexp) (but
caution: this keybinding may restart the system on GNU/Linux).
For example, if point is before (progn (I’ll use [] as an indication where the
cursor is):
(defun d ()
(if t
(+ 3 3)
[](progn
(+ 1 1)
(if t
(+ 2 2)
(+ 3 3)))
(+ 4 4)))
Indenting s-expressions
Pressing TAB will indent incorrectly indented code. For example, put the
point at the beginning of the (+ 3 3) form and press TAB:
(progn
(+ 3 3))
(defun e ()
"A badly indented function (now correctly indented)."
(let ((x 20))
(loop for i from 0 to x
do (loop for j from 0 below 10
do (print j))
(if (< i 10)
(let ((z nil) )
(setq z (format t "x=~d" i))
(print z))))))
Use M-( to insert a pair of parenthesis (()) and the same keybinding with a
prefix argument, C-u M-(, to enclose the expression in front of the cursor
with a pair of parens.
For example, we start with the cursor before the first paren:
CL-USER> |(- 2 2)
With a numbered prefix argument (C-u 2 M-(), wrap around this number of
s-expressions.
Additionnaly, use M-x check-parens to spot malformed s-exps and C-c C-]
(slime-close-all-parens-in-sexp) to insert the required number of
closing parenthesis.
Code completion
Use the built-in C-c TAB to complete symbols in SLIME. You can get
tooltips with company-mode.
Hiding/showing code
Comments
Insert a comment, comment a region with M-;, adjust text with M-q.
Compile a defun by putting the cursor inside it and pressing C-c C-c
(slime-compile-defun).
evaluate the sexp before the point by putting the cursor after its closing
paren and pressing C-x C-e (slime-eval-last-expression). The
result is printed in the minibuffer.
similarly, use C-c C-p (slime-pprint-eval-last-expression) to eval
and pretty-print the expression before point. It shows the result in a new
“slime-description” window.
evaluate a region with C-c C-r,
evaluate a defun with C-M-x,
type C-c C-e (slime-interactive-eval) to get a prompt that asks for
code to eval in the current context. It prints the result in the minibuffer.
With a prefix argument, insert the result into the current buffer.
type C-c C-j (slime-eval-last-expression-in-repl), when the
cursor is after the closing parenthesis of an expression, to send this
expression to the REPL and evaluate it.
EVALUATION VS COMPILATION
eval is still useful to observe results from individual non top-level forms.
For example, say you have this function:
(defun foo ()
(let ((f (open "/home/mariano/test.lisp")))
...))
Go to the end of the OPEN expression and evaluate it (C-x C-e), to observe
the result:
=> #<SB-SYS:FD-STREAM for "file /mnt/e6b00b8f-9dad-4bf4-bd40-
34b1e6d31f0a/home/marian/test.lisp" {1003AAAB53}>
Or on this example, with the cursor on the last parentheses, press C-x C-e to
evaluate the let:
(let ((n 20))
(loop for i from 0 below n
do (print i)))
C-s does an incremental search forward (e.g. - as each key is the search
string is entered, the source file is searched for the first match. This can
make finding specific text much quicker as you only need to type in the
unique characters. Repeat searches (using the same search characters) can be
done by repeatedly pressing C-s
C-s RET and C-r RET both do conventional string searches (forward and
backward respectively)
C-M-s and C-M-r both do regular expression searches (forward and backward
respectively)
Go to definition
Slime has nice cross-referencing facilities. For example, you can ask what
calls a particular function, what expands a macro, or where a global variable
is being used.
Results are presented in a new buffer, listing the places which reference a
particular entity. From there, we can press Enter to go to the corresponding
source line, or more interestingly we can recompile the place at point by
pressing C-c C-c on that line. Likewise, C-c C-k will recompile all the
references. This is useful when modifying macros, inline functions, or
constants.
The bindings are the following (they are also shown in Slime’s menu):
Argument lists
When you put the cursor on a function, SLIME will show its signature in the
minibuffer.
Documentation lookup
You can enhance the help buffer with the Slime extension slime-doc-
contribs. It will show more information in a nice looking buffer.
Inspect
You can call (inspect 'symbol) from the REPL or call it with C-c I from a
source file.
Macroexpand
The Common Lisp Hyper Spec is the official online version of the ANSI
Common Lisp standard. We can start browsing it from starting points: a
shortened table of contents of highlights, a symbols index, a glossary, a
master index.
If you want other tools to do a quick look-up of symbols on the CLHS, since
the official website doesn’t have a search bar, you can use: * Xach’s website
search utility: https://www.xach.com/clhs?q=with-open-file * the l1sp.org
website: http://l1sp.org/search?q=with-open-file, * and we can use
Duckduckgo’s or Brave Search’s !clhs “bang”.
Now, you can use C-c C-d h to look-up the symbol at point in the
HyperSpec. This will open your browser, but look at its URL starting with
“file://home/”: it opens a local file.
Miscellaneous
Synchronizing packages
Calling code
C-c C-y (slime-call-defun): When the point is inside a defun and C-c C-y
is pressed,
then (foo []) will be inserted into the REPL, so that you can write
additional arguments and run it.
This feature is very useful for testing a function you just wrote.
That works not only for defun, but also for defgeneric, defmethod, defmacro,
and define-compiler-macro in the same fashion as for defun.
C-y in SLDB on a frame will insert a call to that frame into the REPL, e.g.,
(/ 0) =>
…
1: (CCL::INTEGER-/-INTEGER 1 0)
…
Exporting symbols
Customization
There are different styles of how symbols are presented in defpackage, the
default is to use uninterned symbols (#:foo). This can be changed:
to use keywords:
(setq slime-export-symbol-representation-function
(lambda (n) (format ":%s" n)))
or strings:
(setq slime-export-symbol-representation-function
(lambda (n) (format "\"%s\"" (upcase n))))
Project Management
ASDF
ASDF best practices
From the REPL, we can use ,ql to install a package known by name already.
Questions/Answers
utf-8 encoding
(setf sly-lisp-implementations
'((sbcl ("/usr/local/bin/sbcl") :coding-system utf-8-unix)
))
This will avoid getting ascii stream decoding errors when you have
non-ascii characters in files you evaluate with SLIME.
Luckily, you have a solution! Install cua-mode and you can continue to use
these shortcuts.
;; C-z=Undo, C-c=Copy, C-x=Cut, C-v=Paste (needs cua.el)
(require 'cua) (CUA-mode t)
Appendix
Here is the reference of all Slime shortcuts that work in the REPL.
To see them, go in a REPL, type C-h m and go to the Slime REPL map
section.
REPL mode defined in ‘slime-repl.el’:
Major mode for interacting with a superior Lisp.
key binding
-
M-RET slime-repl-closing-return
M-n slime-repl-next-input
M-p slime-repl-previous-input
M-r slime-repl-previous-matching-input
M-s previous-line
C-M-x lisp-eval-defun
C-M-q indent-sexp
C-M-q prog-indent-sexp
(that binding is currently shadowed by another mode)
To see them, go in a .lisp file, type C-h m and go to the Slime section.
Commands to compile the current buffer’s source file and
visually
highlight any resulting compiler notes and warnings:
C-c C-k - Compile and load the current buffer’s file.
C-c M-k - Compile (but not load) the current buffer’s file.
C-c C-c - Compile the top-level form at point.
Finding definitions:
M-.
- Edit the definition of the function called at point.
M-,
- Pop the definition stack to go back from a definition.
Documentation commands:
C-c C-d C-d - Describe symbol.
C-c C-d C-a - Apropos search.
C-c M-d - Disassemble a function.
Evaluation commands:
C-M-x - Evaluate top-level from containing point.
C-x C-e - Evaluate sexp before point.
C-c C-p - Evaluate sexp before point, pretty-print result.
C-M-a slime-beginning-of-defun
C-M-e slime-end-of-defun
M-n slime-next-note
M-p slime-previous-note
C-M-, slime-previous-location
C-M-. slime-next-location
C-M-x slime-eval-defun
M-, slime-pop-find-definition-stack
M-. slime-edit-definition
M-? slime-edit-uses
M-_ slime-edit-uses
C-x 5 . slime-edit-definition-other-frame
C-x 4 . slime-edit-definition-other-window
C-c C-v M-o slime-clear-presentations
See also
Syntax highlighting
Code completion
Code formatter
Jump to definition
Snippets
REPL integration
Interactive Debugger
REPL history
Inline evaluation
Macro expand
Disassemble
Inspector
Hover Text
Rename function args and let bindings
Code folding
Prerequisites
To disconnect your REPL and shut down your SBCL instance, open the
Command Palette on the menu at the top where it says View/Command
Palette and choose: Alive: Detach from REPL
There are keybindings for every operation, feel free to explore and modify
those as needed.
Recipes
All recipes assume you have a file open in VSCode running with an attached
REPL unless otherwise stated.
3. You will see a small pop up that says => 4 (3 bits, #x4, #o4,
#b100), which is the result
Evaluate a statement
Compile a file
1. In your open file in your editor window, enter:
(+ 2 2)
; wrote /Users/jason/Desktop/hello.fasl
2. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Inline Eval to load your define function into
your image.
3. In your open file, add another new line and enter:
(divide 1 0)
4. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Inline Eval to run your divide function in your
image.
5. You will see the Interactive Debugger pop up. In the Restarts section,
choose option 2 to Abort.
6. You’re now back to your editor and still-running REPL and can
continue like it never happened.
2. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Inline Eval to load your define function into
your image.
3. In your open file, add another new line and enter:
(divide 1 0)
4. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Inline Eval to run your divide function in your
image.
5. You will see the Interactive Debugger pop up. In the Restarts section,
choose option 0 to “Retry assertion with new value for Y”.
6. In the popup menu, enter `y’
7. In the next popup menu, enter 1
8. You should now see a small pop up that says => 1 (1 bit, #x1, #o1,
#b1), which is the result of the new value. You’re now back to your
editor and still-running REPL after crashing out into the debugger,
having it let you change the value that caused the crash, and then
proceeding like you never typed that bad 0 value.
More ideas for what can be done with the debugger can be found on the
error handling page.
Expand a macro
1. In your open file in your editor window, enter:
(loop for x in '(a b c d e) do
(print x))
2. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Macro Expand to expand the for-loop macro.
3. You should see something like this:
(BLOCK NIL
(LET ((X NIL)
(#:LOOP-LIST-559
(SB-KERNEL:THE* (LIST :USE-ANNOTATIONS T
:SOURCE-FORM '(A B C D E))
'(A B C D E))))
(DECLARE (IGNORABLE #:LOOP-LIST-559)
(IGNORABLE X))
(TAGBODY
SB-LOOP::NEXT-LOOP
(SETQ X (CAR #:LOOP-LIST-559))
(SETQ #:LOOP-LIST-559 (CDR #:LOOP-LIST-559))
(PRINT X)
(IF (ENDP #:LOOP-LIST-559)
(GO SB-LOOP::END-LOOP))
(GO SB-LOOP::NEXT-LOOP)
SB-LOOP::END-LOOP)))
Disassemble a function
1. In your open file in your editor window, enter:
(defun hello (name)
(format t "Hello, ~A~%" name))
2. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Inline Eval to load the function into your image.
3. Put your cursor after the last parenthesis if it isn’t already there. Open
the Command Palette on the menu at the top View/Command Palette
and choose Alive: Disassemble print out the machine code of your
compiled function.
4. It will start something like this:
; disassembly for HELLO
; Size: 172 bytes. Origin: #x70052478B4 ;
HELLO
; 8B4: AC0A40F9 LDR R2, [THREAD, #16] ;
binding-stack-pointer
; 8B8: 4C0F00F9 STR R2, [CFP, #24]
; 8BC: AC4642F9 LDR R2, [THREAD, #1160] ;
tls: *STANDARD-OUTPUT*
; 8C0: 9F8501F1 CMP R2, #97
; 8C4: 61000054 BNE L0
; 8C8: 4AFDFF58 LDR R0, #x7005247870 ;
'*STANDARD-OUTPUT*
; 8CC: 4C1140F8 LDR R2, [R0, #1]
; 8D0: L0: 4C1700F9 STR R2, [CFP, #40]
; 8D4: E0031BAA MOV NL0, CSP
; 8D8: 7A0701F8 STR CFP, [CSP], #16
; 8DC: EAFCFF58 LDR R0, #x7005247878 ;
"Hello, "
; 8E0: 4B1740F9 LDR R1, [CFP, #40]
; 8E4: B6FBFF58 LDR LEXENV, #x7005247858 ;
#<SB-KERNEL:FDEFN WRITE-STRING>
; 8E8: 970080D2 MOVZ NARGS, #4
; 8EC: FA0300AA MOV CFP, NL0
; 8F0: DE9240F8 LDR LR, [LEXENV, #9]
; 8F4: C0033FD6 BLR LR
; 8F8: 3B039B9A CSEL CSP, OCFP, CSP, EQ
; 8FC: E0031BAA MOV NL0, CSP
; 900: 7A0701F8 STR CFP, [CSP], #16
; 904: 4A2F42A9 LDP R0, R1, [CFP, #32]
; 908: D6FAFF58 LDR LEXENV, #x7005247860 ;
#<SB-KERNEL:FDEFN PRINC>
; 90C: 970080D2 MOVZ NARGS, #4
; 910: FA0300AA MOV CFP, NL0
; 914: DE9240F8 LDR LR, [LEXENV, #9]
; 918: C0033FD6 BLR LR
; 91C: 3B039B9A CSEL CSP, OCFP, CSP, EQ
; 920: E0031BAA MOV NL0, CSP
; 924: 7A0701F8 STR CFP, [CSP], #16
; 928: 2A4981D2 MOVZ R0, #2633
; 92C: 4B1740F9 LDR R1, [CFP, #40]
; 930: D6F9FF58 LDR LEXENV, #x7005247868 ;
#<SB-KERNEL:FDEFN WRITE-CHAR>
; 934: 970080D2 MOVZ NARGS, #4
; 938: FA0300AA MOV CFP, NL0
; 93C: DE9240F8 LDR LR, [LEXENV, #9]
; 940: C0033FD6 BLR LR
; 944: 3B039B9A CSEL CSP, OCFP, CSP, EQ
; 948: EA031DAA MOV R0, NULL
; 94C: FB031AAA MOV CSP, CFP
; 950: 5A7B40A9 LDP CFP, LR, [CFP]
; 954: BF0300F1 CMP NULL, #0
; 958: C0035FD6 RET
; 95C: E00120D4 BRK #15 ;
Invalid argument count trap
This recipe creates a new Common Lisp System, so it does not need a
running REPL.
experiment.asd:
(in-package :asdf-user)
(defsystem "experiment"
:class :package-inferred-system
:depends-on ("experiment/src/app")
:description ""
:in-order-to ((test-op (load-op "experiment/test/all")))
:perform (test-op (o c) (symbol-call :test/all :test-suite)))
(defsystem "experiment/test"
:depends-on ("experiment/test/all"))
src/app.lisp:
(defpackage :app
(:use :cl))
(in-package :app)
test/all.lisp:
(defpackage :test/all
(:use :cl
:app)
(:export :test-suite))
(in-package :test/all)
(defun test-suite ()
(format T "Test Suite~%"))
Assuming that you have quicklisp installed and configured to load on init,
quicklisp just works.
Assuming that you have CLPM installed and configured, modify your
vscode settings to look like this:
Assuming that you have CLPM installed and configured and a bundle
configured in the root of your home directory that contains swank as a dev
dependency, modify your vscode settings to look like this:
Assuming that you have Roswell installed, modify your vscode settings to
look like this:
"alive.swank.startupCommand": [
"ros",
"run",
"--eval",
"(require :asdf)",
"--eval",
"(asdf:load-system :swank)",
"--eval",
"(swank:create-server)"
]
These instructions will work for remote connections, wsl connections, and
github Codespaces as well using the Remote - SSH and Remote - WSL, and
Github Codespaces extensions, respectively assuming you have the
extensions installed. For this example, make sure you have the Containers
extension installed and configured.
1. Pull a docker image that has sbcl installed, in this example, we’ll use
the latest clfoundations sbcl.
docker pull clfoundation/sbcl
Here, we will mainly explore its IDE, asking ourselves what it can offer to a
seasoned lisper used to Emacs and Slime. The short answer is: more graphical
tools, such as an easy to use graphical stepper, a tracer, a code coverage browser
or again a class browser. Setting and using breakpoints was easier than on Slime.
LispWorks also provides more integrated tools (the Process browser lists all
processes running in the Lisp image and we can stop, break or debug them) and
presents many information in the form of graphs (for example, a graph of
function calls or a graph of all the created windows).
LispWorks features
We can see a matrix of LispWorks features by edition and platform here:
http://www.lispworks.com/products/features.html.
We highlight:
LispWorks is used in diverse areas of the industry. They maintain a list of success
stories. As for software that we can use ourselves, we find ScoreCloud amazing
(a music notation software: you play an instrument, sing or whistle and it writes
the music) or OpenMusic (opensource composition environment).
The download instructions and the limitations are given on the download page.
There is a heap size limit which, if exceeded, causes the image to exit. A
warning is provided when the limit is approached.
What does it prevent us to do? As an illustration, we can not load this set of
libraries together in the same image:
(ql:quickload '("alexandria" "serapeum" "bordeaux-threads"
"lparallel" "dexador" "hunchentoot" "quri"
"cl-ppcre" "mito"))
There is a time limit of 5 hours for each session, after which LispWorks
Personal exits, possibly without saving your work or performing cleanups
such as removing temporary files. You are warned after 4 hours of use.
Initialization files are not loaded. If you are used to initializing Quicklisp
from your ~/.sbclrc on Emacs, you’ll have to load an init file manually
every time you start LispWorks ((load #p"~/.your-init-file)).
For the record, the snippet provided by Quicklisp to put in one’s startup file is the
following:
;; provided you installed quicklisp in ~/quicklisp/
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
You’ll have to paste it to the listener window (with the C-y key, y as “yank”).
The installation process requires you to fill an HTML form to receive a download
link, then to run a first script that makes you accept the terms and the licence,
then to run a second script that installs the software.
Licencing model
LispWorks actually comes in four paid editions. It’s all explained by themselves
here: http://www.lispworks.com/products/lispworks.html. In short, there is:
At the time of writing, the licence of the hobbyist edition costs 750 USD, the pro
version the double. They are bought for a LW version, per platform. They have
no limit of time.
NB: Please double check their upstream resources and don’t hesitate to contact
them.
LispWorks IDE
The LispWorks IDE is self-contained, but it is also possible to use LispWorks-
the-implementation from Emacs and Slime (see below). The IDE runs inside the
Common Lisp image, unlike Emacs which is an external program that
communicates with the Lisp image through Swank and Slime. User code runs in
the same process.
The editor
indention after a new line is not automatic, one has to press TAB again.
the auto-completion is not fuzzy.
there are no plugins similar to Paredit (there is a brand new (2021) Paredit
for LispWorks) or Lispy, nor a Vim layer.
We also had an issue, in that the go-to-source function bound to M-. did not work
out for built-in Lisp symbols. Apparently, LispWorks doesn’t provide much
source code, and mostly code of the editor. Some other commercial Lisps, like
Allegro CL, provide more source code
The editor provides an interesting tab: Changed Definitions. It lists the functions
and methods that were redefined since, at our choosing: the first edit of the
session, the last save, the last compile.
See also:
Keybindings
Most of the keybindings are similar to Emacs, but not all. Here are some
differences:
to compile a function, use C-S-c (control, shift and c), instead of C-c C-c.
to compile the current buffer, use C-S-b (instead of C-c C-k).
;; Insert pairs.
(editor:bind-key "Insert Parentheses For Selection" #\( :mode "Lisp")
(editor:bind-key "Insert Double Quotes For Selection"
#\"
:mode "Lisp")
Here’s how to define a new command. We make the ) key to go past the next
closing parenthesis.
(editor:defcommand "Move Over ()" (p)
"Move past the next close parenthesis.
Any indentation preceeding the parenthesis is deleted."
"Move past the next close parenthesis."
;; thanks to Thomas Hermann
;; https://github.com/ThomasHermann/LispWorks/blob/master/editor.li
(declare (ignore p))
(let ((point (editor:current-point)))
(editor:with-point ((m point))
(cond ((editor::forward-up-list m)
(editor:move-point point m)
(editor::point-before point)
(loop (editor:with-point ((back point))
(editor::back-to-indentation back)
(unless (editor:point= back point)
(return)))
(editor::delete-indentation point))
(editor::point-after point))
(t (editor:editor-error))))))
And here’s how you can change indentation for special forms:
(editor:setup-indent "if" 1 4 1)
See also:
The listener
The listener is the REPL we are expecting to find, but it has a slight difference
from Slime.
It doesn’t evaluate the input line by line or form by form, instead it parses the
input while typing. So we get some errors instantly. For example, we type (abc.
So far so good. Once we type a colon to get (abc:, an error message is printed
just above our input:
Error while reading: Reader cannot find package ABC.
Indeed, now abc: references a package, but such a package doesn’t exist.
Its interactive debugger is primarily textual but you can also interact with it with
graphical elements. For example, you can use the Abort button of the menu bar,
which brings you back to the top level. You can invoke the graphical debugger to
see the stacktraces and interact with them. See the Debugger button at the very
end of the toolbar.
If you see the name of your function in the stacktraces (you will if you wrote and
compiled your code in a file, and not directly wrote it in the REPL), you can
double-click on its name to go back to the editor and have it highlight the part of
your code that triggered the error.
The listener provides some helper commands, not unlike Slime’s ones starting
with a comma ,:
CL-USER 1 > :help
When your are writing code in the editor window, you can set breakpoints with
the big red “Breakpoint” button (or by calling M-x Stepper Breakpoint). This
puts a red mark in your code.
The next time your code is executed, you’ll get a comprehensive Stepper pop-up
window showing:
That’s not all. The non-visual, REPL-oriented stepper is also nice. It shows the
forms that are being evaluated and their results.
In this example, we use :s to “step” though the current form and its subforms. We
are using the usual listener, we can write any Lisp code after the prompt (the little
-> here), and we have access to the local variables (X).
The class browser allows us to examine a class’s slots, parent classes, available
methods, and some more.
the class hierarchy, showing the superclasses on the left and the subclasses
on the right, with their description to the bottom,
the superclasses viewer, in the form of a simple schema, and the same for
subclasses,
the slots pane (the default),
the available initargs,
the existing generic functions for that class
and the class precedence list.
The Functions pane lists all methods applicable to that class, so we can discover
public methods provided by the CLOS object system: initialize-instance,
print-object, shared-initialize, etc. We can double-click on them to go to
their source. We can choose not to include the inherited methods too (see the
“include inherited” checkbox).
You’ll find buttons on the toolbar (for example, Inspect a generic function) and
more actions on the Methods menu, such as a way to see the functions calls, a
menu to undefine or trace a function.
See more:
The function call browser allows us to see a graph of the callers and the callees of
a function. It provides several ways to filter the displayed information and to
further inspect the call stack.
After loading a couple packages, here’s a simple example showing who calls the
string-trim function.
The function call browser
It shows functions from all packages, but there is a select box to restrict it further,
for example to the “current and used” or only to the current packages.
The Text tab shows the same information, but textually, the callers and callees
side by side.
We can see cross references for compiled code, and we must ensure the feature is
on. When we compile code, LispWorks shows a compilation output likes this:
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is on
;;; Cross referencing is on
See more:
The Process Browser shows us a list of all threads running. The input area allows
to filter by name. It accepts regular expressions. Then we can stop, inspect, listen,
break into these processes.
“The process browser”
See more:
Saving images
So, effectively, we can save an image and have our development environment
back to the same state, effectively allowing to take snapshots of our current work
and to continue where we left of.
For example, we can start a game from the REPL, play a little bit in its window,
save an image, and when restored we will get the game and its state back.
Misc
We like the Search Files functionality. It is like a recursive grep, but we get a
typical LispWorks graphical window that displays the results, allows to double-
click on them and that offers some more actions.
Last but not least, have a look at the compilation conditions browser.
LispWorks puts all warnings and errors into a special browser when we compile a
system. From now on we can work on fixing them and see them disappear from
the browser. That helps keeping track of warnings and errors during development.
Start a server:
(swank:create-server :port 9876)
;; Swank started at port: 9876.
9876
From Emacs, run M-x slime-connect, choose localhost and 9876 for the port.
and run LispWorks like this to create the new image ~/lw-console:
lispworks-7-0-0-x86-linux -build /tmp/resave.lisp
See also
LispWorks IDE User Guide (check out the sections we didn’t cover)
LispWorks on Wikipedia
the Awesome LispWorks list
Real Image-based approach in Common Lisp - differences between SBCL
and LispWorks.
Functions
Named functions: defun
Creating named functions is done with the defun keyword. It follows this
model:
(defun <name> (list of arguments)
"docstring"
(function body))
The return value is the value returned by the last expression of the body
(see below for more). There is no “return xx” statement.
Call it:
(hello-world)
;; "hello world!" <-- output
;; "hello world!" <-- a string is returned.
Arguments
Base case: required arguments
If you don’t specify the right amount of arguments, you’ll be trapped into
the interactive debugger with an explicit error message:
(hello)
This function:
(defun hello (name &optional age gender) …)
Last but not least, you would quickly realize it, but we can choose the keys
programmatically (they can be variables):
(let ((key :happy)
(val t))
(hello "me" key val))
;; hello me :)
;; NIL
In the lambda list, use pairs to give a default value to an optional or a key
argument, like (happy t) below:
(defun hello (name &key (happy t))
You can skip this tip for now if you want, but come back later to it as it can
turn handy.
&key (:happy t)
So now, we will print a sad face if :happy was explicitely set to NIL. We
don’t print it by default.
(defun hello (name &key (happy nil happy-p))
(format t "Key supplied? ~a~&" happy-p)
(format t "hello ~a " name)
(when happy-p
(if happy
(format t ":)")
(format t ":("))))
(mean 1)
(mean 1 2) ;; => 3/2 (yes, it is printed as a ratio)
(mean 1 2 3 4 5) ;; => 3
Defining key arguments, and allowing more: &allow-other-
keys
Observe:
(defun hello (name &key happy)
(format t "hello ~a~&" name))
whereas
(defun hello (name &key happy &allow-other-keys)
(format t "hello ~a~&" name))
Here’s a real example. We define a function to open a file that always uses
:if-exists :supersede, but still passes any other keys to the open
function.
(defun open-supersede (f &rest other-keys &key &allow-other-keys
(apply #'open f :if-exists :supersede other-keys))
Return values
The return value of the function is the value returned by the last executed
form of the body.
There are ways for non-local exits (return-from <function name>
<value>), but they are usually not needed.
Multiple values are specially useful and powerful because a change in them
needs little to no refactoring.
(defun foo (a b c)
a)
All functions that use the return value of foo need not to change, they still
work. If we had returned a list or an array, this would be different.
multiple-value-bind
The variables var-n are not available outside the scope of multiple-
value-bind.
With nth-value:
(nth-value 0 (values :a :b :c)) ;; => :A
(nth-value 2 (values :a :b :c)) ;; => :C
(nth-value 9 (values :a :b :c)) ;; => NIL
multiple-value-list
In the example above, we used #', but a single quote also works, and we
can encounter it in the wild. Which one to use?
(funcall *foo-caller* 1)
;; 100 ;; and not 2
If we bind the caller with 'foo, a single quote, the function will be resolved
at runtime:
(defun foo (x) (* x 100)) ;; back to original behavior.
(defparameter *foo-caller-2* 'foo)
;; *FOO-CALLER-2*
(funcall *foo-caller-2* 1)
;; 100
;; We try again:
(funcall *foo-caller-2* 1)
;; 2
The behaviour you want depends on your use case. Generally, using
sharpsign-quote is less surprising. But if you are running a tight loop and
you want live-update mechanisms (think a game or live visualisations), you
might want to use a single quote so that your loop picks up the user’s new
function definition.
Here we have defined the function adder which returns an object of type
function.
Indeed, CL has different namespaces for functions and variables, i.e. the
same name can refer to different things depending on its position in a form
that’s evaluated.
;; The symbol foo is bound to nothing:
CL-USER> (boundp 'foo)
NIL
CL-USER> (fboundp 'foo)
NIL
;; We create a variable:
CL-USER> (defparameter foo 42)
FOO
* foo
42
;; Now foo is "bound":
CL-USER> (boundp 'foo)
T
;; but still not as a function:
CL-USER> (fboundp 'foo)
NIL
;; So let's define a function:
CL-USER> (defun foo (x) (* x x))
FOO
;; Now the symbol foo is bound as a function too:
CL-USER> (fboundp 'foo)
T
;; Get the function:
CL-USER> (function foo)
#<FUNCTION FOO>
;; and the shorthand notation:
* #'foo
#<FUNCTION FOO>
;; We call it:
(funcall (function adder) 5)
#<CLOSURE (LAMBDA (X) :IN ADDER) {100991761B}>
;; and call the lambda:
(funcall (funcall (function adder) 5) 3)
8
To simplify a bit, you can think of each symbol in CL having (at least) two
“cells” in which information is stored. One cell - sometimes referred to as
its value cell - can hold a value that is bound to this symbol, and you can
use boundp to test whether the symbol is bound to a value (in the global
environment). You can access the value cell of a symbol with symbol-
value.
The other cell - sometimes referred to as its function cell - can hold the
definition of the symbol’s (global) function binding. In this case, the symbol
is said to be fbound to this definition. You can use fboundp to test whether a
symbol is fbound. You can access the function cell of a symbol (in the
global environment) with symbol-function.
In Common Lisp, as opposed to Scheme, it is not possible that the car of the
compound form to be evaluated is an arbitrary form. If it is not a symbol, it
must be a lambda expression, which looks like (lambdalambda-list
_form*_).
Closures
Closures allow to capture lexical bindings:
(let ((limit 3)
(counter -1))
(defun my-counter ()
(if (< counter limit)
(incf counter)
(setf counter 0))))
(my-counter)
0
(my-counter)
1
(my-counter)
2
(my-counter)
3
(my-counter)
0
Or similarly:
(defun repeater (n)
(let ((counter -1))
(lambda ()
(if (< counter n)
(incf counter)
(setf counter 0)))))
setf functions
A function name can also be a list of two symbols with setf as the first
one, and where the first argument is the new value:
(defun (setf <name>) (new-value <other arguments>)
body)
A silly example:
(defparameter *current-name* ""
"A global name.")
Currying
Concept
Now that you know how to do it, you may appreciate using the
implementation of the Alexandria library (in Quicklisp).
(ql:quickload "alexandria")
Documentation
functions:
http://www.lispworks.com/documentation/HyperSpec/Body/t_fn.htm#f
unction
ordinary lambda lists:
http://www.lispworks.com/documentation/HyperSpec/Body/03_da.htm
multiple-value-bind: http://clhs.lisp.se/Body/m_multip.htm
Data structures
We hope to give here a clear reference of the common data structures. To
really learn the language, you should take the time to read other resources.
The following resources, which we relied upon, also have many more
details:
Don’t miss the appendix and when you need more data structures, have a
look at the awesome-cl list and Quickdocs.
Lists
Building lists. Cons cells, lists.
The list basic element is the cons cell. We build lists by assembling cons
cells.
(cons 1 2)
;; => (1 . 2) ;; representation with a point, a dotted pair.
See that the representation is not a dotted pair ? The Lisp printer
understands the convention.
or by calling quote:
'(1 2)
;; => (1 2)
Circular lists
A cons cell car or cdr can refer to other objects, including itself or other
cells in the same list. They can therefore be used to define self-referential
structures such as circular lists.
Before working with circular lists, tell the printer to recognise them and not
try to print the whole list by setting *print-circle* to T:
(setf *print-circle* t)
A function which modifies a list, so that the last cdr points to the start of
the list is:
(defun circular! (items)
"Modifies the last cdr of list ITEMS, returning a circular lis
(setf (cdr (last items)) items))
The reader can also create circular lists, using Sharpsign Equal-Sign
notation. An object (like a list) can be prefixed with #n= where n is an
unsigned decimal integer (one or more digits). The label #n# can be used to
refer to the object later in the expression:
'#42=(1 2 3 . #42#)
;; => #1=(1 2 3 . #1#)
Note that the label given to the reader (n=42) is discarded after reading, and
the printer defines a new label (n=1).
Further reading
return the last cons cell in a list (or the nth last cons cells).
(last '(1 2 3))
;; => (3)
(car (last '(1 2 3)) ) ;; or (first (last …))
;; => 3
(butlast '(1 2 3))
;; => (1 2)
reverse, nreverse
append
append takes any number of list arguments and returns a new list containing
the elements of all its arguments:
(append (list 1 2) (list 3 4))
;; => (1 2 3 4)
The new list shares some cons cells with the (3 4):
http://gigamonkeys.com/book/figures/after-append.png
push prepends item to the list that is stored in place, stores the resulting list
in place, and returns the list.
(defparameter mylist '(1 2 3))
(push 0 mylist)
;; => (0 1 2 3)
push is equivalent to (setf place (cons item place )) except that the
subforms of place are evaluated only once, and item is evaluated before
place.
pop
a destructive operation.
It binds the parameter values to the list elements. We can destructure trees,
plists and even provide defaults.
Simple matching:
(destructuring-bind (x y z) (list 1 2 3)
(list :x x :y y :z z))
;; => (:X 1 :Y 2 :Z 3)
The parameter list can use the usual &optional, &rest and &key
parameters.
(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
(list :x x :y1 y1 :y2 y2 :z z))
;; => (:X 1 :Y1 2 :Y2 NIL :Z 3)
If this gives you the will to do pattern matching, see pattern matching.
(make-list 3)
;; => (NIL NIL NIL)
(fill * "hello")
;; => ("hello" "hello" "hello")
sublis accepts the :test and :key arguments. :test is a function that
takes two arguments, the key and the subtree.
(sublis '((t . "foo"))
'("one" 2 ("three" (4 5)))
:key #'stringp)
;; ("foo" 2 ("foo" (4 5)))
Sequences
lists and vectors (and thus strings) are sequences.
Pay attention to the :test argument. It defaults to eql (for strings, use
:equal).
is similar to (assoc* x y): It searches for an element of the list whose car
equals x, rather than for an element which equals x itself. If :key is omitted
or nil, the filter is effectively the identity function.
some, notany (test, sequence): return either the value of the test, or nil.
Functions
length (sequence)
subseq is “setf”able, but only works if the new sequence has the same
length of the one to replace.
These sort functions are destructive, so one may prefer to copy the sequence
with copy-seq before sorting:
(sort (copy-seq seq) :test #'string<)
could be either ((1 :A) (1 :B)), either ((1 :B) (1 :A)). On my tests,
the order is preserved, but the standard does not guarantee it.
mismatch returns the position where the two sequences start to differ:
(mismatch '(10 20 99) '(10 20 30))
;; 2
(mismatch "hellolisper" "helloworld")
;; 5
(mismatch "same" "same")
;; NIL
(mismatch "foo" "bar")
;; 0
substitute, nsubstitute[if,if-not]
Return a sequence of the same kind as sequence with the same elements,
except that all elements equal to old are replaced with new.
(substitute #\o #\x "hellx") ;; => "hello"
(substitute :a :x '(:a :x :x)) ;; => (:A :A :A)
(substitute "a" "x" '("a" "x" "x") :test #'string=)
;; => ("a" "a" "a")
sort, stable-sort, merge
(see above)
If you’re used to map and filter in other languages, you probably want
mapcar. But it only works on lists, so to iterate on vectors (and produce
either a vector or a list, use (map 'list function vector).
mapcar also accepts multiple lists with &rest more-seqs. The mapping
stops as soon as the shortest sequence runs out.
The backquote first warns we’ll do interpolation, the comma introduces the
value of the variable.
E. Weitz warns that “objects generated this way will very likely share
structure (see Recipe 2-7)”.
Comparing lists
Set
We show below how to use set operations on lists.
For more, see functions in Alexandria: setp, set-equal,… and the FSet
library, shown in the next section.
intersection of lists
(adjoin 5 list-a)
;; => (5 0 1 2 3) ;; <-- element added in front.
list-a
;; => (0 1 2 3) ;; <-- original list unmodified.
A simple vector is a simple array that is also not specialized (it doesn’t use
:element-type to set the types of the elements).
Sizes
Vectors
Create with vector or the reader macro #(). It returns a simple vector.
(vector 1 2 3)
;; => #(1 2 3)
#(1 2 3)
;; => #(1 2 3)
vector-push (foo vector): replace the vector element pointed to by the fill
pointer by foo. Can be destructive.
vector-pop (vector): return the element of vector its fill pointer points to.
If you’re mapping over it, see the map function whose first parameter is the
result type.
Hash Table
Hash Tables are a powerful data structure, associating keys with values in a
very efficient way. Hash Tables are often preferred over association lists
whenever performance is an issue, but they introduce a little overhead that
makes assoc lists better if there are only a few key-value pairs to maintain.
The function gethash takes two required arguments: a key and a hash table.
It returns two values: the value corresponding to the key in the hash table
(or nil if not found), and a boolean indicating whether the key was found in
the table. That second value is necessary since nil is a valid value in a key-
value pair, so getting nil as first value from gethash does not necessarily
mean that the key was not found in the table.
The first value returned by gethash is the object in the hash table that’s
associated with the key you provided as an argument to gethash or nil if
no value exists for this key. This value can act as a generalized boolean if
you want to test for the presence of keys.
CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'one-entry *my-hash*) "one")
"one"
CL-USER> (if (gethash 'one-entry *my-hash*)
"Key exists"
"Key does not exist")
"Key exists"
CL-USER> (if (gethash 'another-entry *my-hash*)
"Key exists"
"Key does not exist")
"Key does not exist"
But note that this does not work if nil is amongst the values that you want
to store in the hash.
CL-USER> (setf (gethash 'another-entry *my-hash*) nil)
NIL
CL-USER> (if (gethash 'another-entry *my-hash*)
"Key exists"
"Key does not exist")
"Key does not exist"
In this case you’ll have to check the second return value of gethash which
will always return nil if no value is found and T otherwise.
CL-USER> (if (nth-value 1 (gethash 'another-entry *my-hash*))
"Key exists"
"Key does not exist")
"Key exists"
CL-USER> (if (nth-value 1 (gethash 'no-entry *my-hash*))
"Key exists"
"Key does not exist")
"Key does not exist"
Use remhash to delete a hash entry. Both the key and its associated value
will be removed from the hash table. remhash returns T if there was such an
entry, nil otherwise.
CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
ONE
CL-USER> (gethash 'first-key *my-hash*)
ONE
T
CL-USER> (remhash 'first-key *my-hash*)
T
CL-USER> (gethash 'first-key *my-hash*)
NIL
NIL
CL-USER> (gethash 'no-entry *my-hash*)
NIL
NIL
CL-USER> (remhash 'no-entry *my-hash*)
NIL
CL-USER> (gethash 'no-entry *my-hash*)
NIL
NIL
Use clrhash to delete a hash table. This will remove all of the data from the
hash table and return the deleted table.
CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
ONE
CL-USER> (setf (gethash 'second-key *my-hash*) 'two)
TWO
CL-USER> *my-hash*
#<hash-table :TEST eql :COUNT 2 {10097BF4E3}>
CL-USER> (clrhash *my-hash*)
#<hash-table :TEST eql :COUNT 0 {10097BF4E3}>
CL-USER> (gethash 'first-key *my-hash*)
NIL
NIL
CL-USER> (gethash 'second-key *my-hash*)
NIL
NIL
If you want to perform an action on each entry (i.e., each key-value pair) in
a hash table, you have several options:
You can use maphash which iterates over all entries in the hash table. Its
first argument must be a function which accepts two arguments, the key and
the value of each entry. Note that due to the nature of hash tables you can’t
control the order in which the entries are provided by maphash (or other
traversing constructs). maphash always returns nil.
CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
ONE
CL-USER> (setf (gethash 'second-key *my-hash*) 'two)
TWO
CL-USER> (setf (gethash 'third-key *my-hash*) nil)
NIL
CL-USER> (setf (gethash nil *my-hash*) 'nil-value)
NIL-VALUE
CL-USER> (defun print-hash-entry (key value)
(format t "The value associated with the key ~S is ~S~%"
key value))
PRINT-HASH-ENTRY
CL-USER> (maphash #'print-hash-entry *my-hash*)
The value associated with the key FIRST-KEY is ONE
The value associated with the key SECOND-KEY is TWO
The value associated with the key THIRD-KEY is NIL
The value associated with the key NIL is NIL-VALUE
Note the following caveat from the HyperSpec: “It is unspecified what
happens if any of the implicit interior state of an iteration is returned outside
the dynamic extent of the with-hash-table-iterator form such as by
returning some closure over the invocation form.”
And there’s always loop:
;;; same hash-table as above
CL-USER> (loop for key being the hash-keys of *my-hash*
do (print key))
FIRST-KEY
SECOND-KEY
THIRD-KEY
NIL
NIL
CL-USER> (loop for key being the hash-keys of *my-hash*
using (hash-value value)
do (format t "The value associated with the key ~S is
key value))
The value associated with the key FIRST-KEY is ONE
The value associated with the key SECOND-KEY is TWO
The value associated with the key THIRD-KEY is NIL
The value associated with the key NIL is NIL-VALUE
NIL
CL-USER> (loop for value being the hash-values of *my-hash*
do (print value))
ONE
TWO
NIL
NIL-VALUE
NIL
CL-USER> (loop for value being the hash-values of *my-hash*
using (hash-key key)
do (format t "~&~A -> ~A" key value))
FIRST-KEY -> ONE
SECOND-KEY -> TWO
THIRD-KEY -> NIL
NIL -> NIL-VALUE
NIL
To map over keys or values we can again rely on Alexandria with maphash-
keys and maphash-values.
Counting the Entries in a Hash Table
gives:
;; WARNING:
;; redefining PRINT-OBJECT (#<STRUCTURE-CLASS COMMON-
LISP:HASH-TABLE>
;; #<SB-PCL:SYSTEM-CLASS COMMON-
LISP:T>) in DEFMETHOD
;; #<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (HASH-TABLE T)
{1006A0D063}>
This snippets prints the keys, values and the test function of a hash-table,
and uses alexandria:alist-hash-table to read it back in:
;; https://github.com/phoe/phoe-toolbox/blob/master/phoe-toolbox
(defun print-hash-table-readably (hash-table
&optional
(stream *standard-output*))
"Prints a hash table readably using ALEXANDRIA:ALIST-HASH-TABL
(let ((test (hash-table-test hash-table))
(*print-circle* t)
(*print-readably* t))
(format stream "#.(ALEXANDRIA:ALIST-HASH-TABLE '(~%")
(maphash (lambda (k v) (format stream " (~S . ~S)~%" k v))
(format stream " ) :TEST '~A)" test)
hash-table))
Example output:
#.(ALEXANDRIA:ALIST-HASH-TABLE
'((ONE . 1))
:TEST 'EQL)
#<HASH-TABLE :TEST EQL :COUNT 1 {10046D4863}>
This output can be read back in to create a hash-table:
(read-from-string
(with-output-to-string (s)
(print-hash-table-readably
(alexandria:alist-hash-table
'((a . 1) (b . 2) (c . 3))) s)))
;; #<HASH-TABLE :TEST EQL :COUNT 3 {1009592E23}>
;; 83
The Serapeum library has the dict constructor, the function pretty-print-
hash-table and the toggle-pretty-print-hash-table switch, all which
do not use print-object under the hood.
CL-USER> (serapeum:toggle-pretty-print-hash-table)
T
CL-USER> (serapeum:dict :a 1 :b 2 :c 3)
(dict
:A 1
:B 2
:C 3
)
With SBCL, we can create thread-safe hash tables with the :synchronized
keyword to make-hash-table: http://www.sbcl.org/manual/#Hash-Table-
Extensions.
If nil (the default), the hash-table may have multiple concurrent
readers, but results are undefined if a thread writes to the hash-table
concurrently with another reader or writer. If t, all concurrent accesses
are safe, but note that clhs 3.6 (Traversal Rules and Side Effects)
remains in force. See also: sb-ext:with-locked-hash-table.
(defparameter *my-hash* (make-hash-table :synchronized t))
But, operations that expand to two accesses, like the modify macros (incf)
or this:
(setf (gethash :a *my-hash*) :new-value)
Ultimately, you might like what the cl-gserver library proposes. It offers
helper functions around hash-tables and its actors/agent system to allow
thread-safety. They also maintain the order of updates and reads.
Evaluation took:
0.27 seconds of real time
0.25 seconds of user run time
0.02 seconds of system run time
0 page faults and
8754768 bytes consed.
NIL
CL-USER> (time (dotimes (n 100000)
(setf (gethash n *my-hash*) n)))
Compiling LAMBDA NIL:
Compiling Top-Level Form:
Evaluation took:
0.05 seconds of real time
0.05 seconds of user run time
0.0 seconds of system run time
0 page faults and
0 bytes consed.
NIL
The hash has to be re-sized 19 times until it’s big enough to hold 100,000
entries. That explains why we saw a lot of consing and why it took rather
long to fill the hash table. It also explains why the second run was much
faster - the hash table already had the correct size.
Here’s a faster way to do it: If we know in advance how big our hash will
be, we can start with the right size:
CL-USER> (defparameter *my-hash* (make-hash-table :size 100000))
*MY-HASH*
CL-USER> (hash-table-size *my-hash*)
100000
CL-USER> (time (dotimes (n 100000)
(setf (gethash n *my-hash*) n)))
( (g y ) )))
Compiling LAMBDA NIL:
Compiling Top-Level Form:
Evaluation took:
0.04 seconds of real time
0.04 seconds of user run time
0.0 seconds of system run time
0 page faults and
0 bytes consed.
NIL
That’s obviously much faster. And there was no consing involved because
we didn’t have to re-size at all. If we don’t know the final size in advance
but can guess the growth behaviour of our hash table we can also provide
this value to make-hash-table. We can provide an integer to specify
absolute growth or a float to specify relative growth.
CL-USER> (defparameter *my-hash* (make-hash-table :rehash-size 1
*MY-HASH*
CL-USER> (hash-table-size *my-hash*)
65
CL-USER> (hash-table-rehash-size *my-hash*)
100000
CL-USER> (time (dotimes (n 100000)
(setf (gethash n *my-hash*) n)))
Compiling LAMBDA NIL:
Compiling Top-Level Form:
Evaluation took:
0.07 seconds of real time
0.05 seconds of user run time
0.01 seconds of system run time
0 page faults and
2001360 bytes consed.
NIL
Also rather fast (we only needed one re-size) but much more consing
because almost the whole hash table (minus 65 initial elements) had to be
built during the loop.
Note that you can also specify the rehash-threshold while creating a new
hash table. One final remark: Your implementation is allowed to completely
ignore the values provided for rehash-size and rehash-threshold…
Alist
Definition
Construction
Access
To get a key, we have assoc (use :test 'equal when your keys are strings,
as usual). It returns the whole cons cell, so you may want to use cdr or
second to get the value, or even assoc-value list key from Alexandria.
If the alist has repeating (duplicate) keys, you can use remove-if-not, for
example, to retrieve all of them.
(remove-if-not
(lambda (entry)
(eq :a entry))
*alist-with-duplicate-keys*
:key #'car)
We can use pop and other functions that operate on lists, like remove:
(remove :team *my-alist*)
;; ((:TEAM . "team") (FOO . "foo") (BAR . "bar"))
;; => didn't remove anything
(remove :team *my-alist* :key 'car)
;; ((FOO . "foo") (BAR . "bar"))
;; => returns a copy
;; because otherwise:
(remove 'bar *my-alist* :key 'car)
;; ((TEAM . "team") (FOO . "foo"))
;; => no more 'bar
Update entries
Replace a value:
*my-alist*
;; => '((:FOO . "foo") (:BAR . "bar"))
(assoc :foo *my-alist*)
;; => (:FOO . "foo")
(setf (cdr (assoc :foo *my-alist*)) "new-value")
;; => "new-value"
*my-alist*
;; => '((:foo . "new-value") (:BAR . "bar"))
Replace a key:
*my-alist*
;; => '((:FOO . "foo") (:BAR . "bar")))
(setf (car (assoc :bar *my-alist*)) :new-key)
;; => :NEW-KEY
*my-alist*
;; => '((:FOO . "foo") (:NEW-KEY . "bar")))
Plist
A property list is simply a list that alternates a key, a value, and so on,
where its keys are symbols (we can not set its :test). More precisely, it
first has a cons cell whose car is the key, whose cdr points to the following
cons cell whose car is the value.
We access an element with getf (list elt) (it returns the value) (the list
comes as first element),
Structures
Structures offer a way to store data in named slots. They support single
inheritance.
Classes provided by the Common Lisp Object System (CLOS) are more
flexible however structures may offer better performance (see for example
the SBCL manual).
Creation
Use defstruct:
(defstruct person
id name age)
Slot access
Setting
Predicate
Single inheritance
Limitations
If we try to add a slot (email below), we have the choice to lose all
instances, or to continue using the new definition of person. But the effects
of redefining a structure are undefined by the standard, so it is best to re-
compile and re-run the changed code.
(defstruct person
id
(name "john doe" :type string)
age
email)
Restarts:
0: [CONTINUE] Use the new definition of PERSON, invalidating
already-loaded code and instances.
1: [RECKLESSLY-CONTINUE] Use the new definition of PERSON as
if it were compatible, allowing old accessors to use new
instances and allowing new accessors to use old instances.
2: [CLOBBER-IT] (deprecated synonym for RECKLESSLY-CONTINUE)
3: [RETRY] Retry SLIME REPL evaluation request.
4: [*ABORT] Return to SLIME's top level.
5: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING
{1002A0FFA3}>)
There is also very little introspection. Portable Common Lisp does not
define ways of finding out defined super/sub-structures nor what slots a
structure has.
The Common Lisp Object System (which came after into the language)
doesn’t have such limitations. See the CLOS section.
Tree
tree-equal, copy-tree. They descend recursively into the car and the cdr
of the cons cells they visit.
https://github.com/ndantam/sycamore
Features:
If you have a very big list, printing it on the REPL or in a stacktrace can
take a long time and bring your editor or even your server down. Use
*print-length* to choose the maximum of elements of the list to print,
and to show there is a rest with a ... placeholder:
(setf *print-length* 2)
(list :A :B :C :D :E)
;; (:A :B ...)
And if you have a very nested data structure, set *print-level* to choose
the depth to print:
(let ((*print-level* 2))
(print '(:a (:b (:c (:d :e))))))
;; (:A (:B #)) <= *print-level* in action
;; (:A (:B (:C (:D :E))))
;; => the list is returned,
;; the let binding is not in effect anymore.
the access library (battle tested, used by the Djula templating system)
has a generic (access my-var :elt) (blog post). It also has accesses
(plural) to access and set nested values.
rutils as a generic generic-elt or ?,
The access library given above provides this, with (accesses var key1
key2…).
Strings
The most important thing to know about strings in Common Lisp is
probably that they are arrays and thus also sequences. This implies that all
concepts that are applicable to arrays and sequences also apply to strings. If
you can’t find a particular string function, make sure you’ve also searched
for the more general array or sequence functions. We’ll only cover a
fraction of what can be done with and to strings here.
Last but not least, when you’ll need to tackle the format construct, don’t
miss the following resources:
Creating strings
A string is created with double quotes, all right, but we can recall these
other ways:
using format nil doesn’t print but returns a new string (see more
examples of format below):
(defparameter *person* "you")
(format nil "hello ~a" *person*) ;; => "hello you"
Accessing Substrings
As a string is a sequence, you can access substrings with the SUBSEQ
function. The index into the string is, as always, zero-based. The third,
optional, argument is the index of the first character which is not a part of
the substring, it is not the length of the substring.
(defparameter *my-string* (string "Groucho Marx"))
*MY-STRING*
(subseq *my-string* 8)
"Marx"
(subseq *my-string* 0 7)
"Groucho"
(subseq *my-string* 1 5)
"rouc"
You can also manipulate the substring if you use SUBSEQ together with
SETF.
* (defparameter *my-string* (string "Harpo Marx"))
*MY-STRING*
* (subseq *my-string* 0 5)
"Harpo"
* (setf (subseq *my-string* 0 5) "Chico")
"Chico"
* *my-string*
"Chico Marx"
But note that the string isn’t “stretchable”. To cite from the HyperSpec: “If
the subsequence and the new sequence are not of equal length, the shorter
length determines the number of elements that are replaced.” For example:
* (defparameter *my-string* (string "Karl Marx"))
*MY-STRING*
* (subseq *my-string* 0 4)
"Karl"
* (setf (subseq *my-string* 0 4) "Harpo")
"Harpo"
* *my-string*
"Harp Marx"
* (subseq *my-string* 4)
" Marx"
* (setf (subseq *my-string* 4) "o Marx")
"o Marx"
* *my-string*
"Harpo Mar"
Because strings are arrays and thus sequences, you can also use the more
generic functions AREF and ELT (which are more general while CHAR
might be implemented more efficiently).
* (defparameter *my-string* (string "Groucho Marx"))
*MY-STRING*
* (aref *my-string* 3)
#\u
* (elt *my-string* 8)
#\M
:UTF-8
* (code-char 200)
#\LATIN_CAPITAL_LETTER_E_WITH_GRAVE
* (char-code #\LATIN_CAPITAL_LETTER_E_WITH_GRAVE)
200
* (code-char 2048)
#\SAMARITAN_LETTER_ALAF
* (char-code #\SAMARITAN_LETTER_ALAF)
2048
Check out the UTF-8 Wikipedia article for the range of supported
characters and their encodings.
If you have to construct a string out of many parts, all of these calls to
CONCATENATE seem wasteful, though. There are at least three other
good ways to construct a string piecemeal, depending on what exactly your
data is. If you build your string one character at a time, make it an
adjustable VECTOR (a one-dimensional ARRAY) of type character with a
fill-pointer of zero, then use VECTOR-PUSH-EXTEND on it. That way,
you can also give hints to the system if you can estimate how long the string
will be. (See the optional third argument to VECTOR-PUSH-EXTEND.)
* (defparameter *my-string* (make-array 0
:element-type 'character
:fill-pointer 0
:adjustable t))
*MY-STRING*
* *my-string*
""
* (dolist (char '(#\Z #\a #\p #\p #\a))
(vector-push-extend char *my-string*))
NIL
* *my-string*
"Zappa"
If the string will be constructed out of (the printed representations of)
arbitrary objects, (symbols, numbers, characters, strings, …), you can use
FORMAT with an output stream argument of NIL. This directs FORMAT to
return the indicated output as a string.
* (format nil "This is a string with a list ~A in it"
'(1 2 3))
"This is a string with a list (1 2 3) in it"
FORMAT can do a lot more processing but it has a relatively arcane syntax.
After this last example, you can find the details in the CLHS section about
formatted output.
* (format nil "The Marx brothers are:~{ ~A~^,~}."
'("Groucho" "Harpo" "Chico" "Zeppo" "Karl"))
"The Marx brothers are: Groucho, Harpo, Chico, Zeppo, Karl."
Or do it with LOOP.
* (loop for char across "Zeppo"
collect char)
(#\Z #\e #\p #\p #\o)
Use sb-unicode:lines to break a string into lines that are no wider than
the :margin keyword argument. Combining marks will always be kept
together with their base characters, and spaces (but not other types of
whitespace) will be removed from the end of lines. If :margin is
unspecified, it defaults to 80 characters
(sb-unicode:lines "A first sentence. A second somewhat long one.
;; => ("A first"
"sentence."
"A second"
"somewhat"
"long one.")
Tip: you can ensure these functions are run only in SBCL with a feature
flag:
#+sbcl
(runs on sbcl)
#-sbcl
(runs on other implementations)
Controlling Case
Common Lisp has a couple of functions to control the case of a string.
* (string-upcase "cool")
"COOL"
* (string-upcase "Cool")
"COOL"
* (string-downcase "COOL")
"cool"
* (string-downcase "Cool")
"cool"
* (string-capitalize "cool")
"Cool"
* (string-capitalize "cool example")
"Cool Example"
These functions take the :start and :end keyword arguments so you can
optionally only manipulate a part of the string. They also have destructive
counterparts whose names starts with “N”.
* (string-capitalize "cool example" :start 5)
"cool Example"
* (string-capitalize "cool example" :end 5)
"Cool example"
* (defparameter *my-string* (string "BIG"))
*MY-STRING*
* (defparameter *my-downcase-string* (nstring-downcase *my-strin
*MY-DOWNCASE-STRING*
* *my-downcase-string*
"big"
* *my-string*
"big"
This implies that the last result in the following example is implementation-
dependent - it may either be “BIG” or “BUG”. If you want to be sure, use
COPY-SEQ.
* (defparameter *my-string* (string "BIG"))
*MY-STRING*
* (defparameter *my-upcase-string* (string-upcase *my-string*))
*MY-UPCASE-STRING*
* (setf (char *my-string* 1) #\U)
#\U
* *my-string*
"BUG"
* *my-upcase-string*
"BIG"
To lower case: ~( ~)
We show this in the Regular Expressions chapter but while we are on this
topic, you can find it super useful:
* (ppcre:all-matches-as-strings "-?\\d+" "42 is 41 plus 1")
;; ("42" "41" "1")
* (mapcar #'parse-integer *)
;; (42 41 1)
Be aware that the full reader is in effect if you’re using this function. This
can lead to vulnerability issues. You should use a library like parse-number
or parse-float instead.
(read-from-string "#X23")
35
4
(read-from-string "4.5")
4.5
3
(read-from-string "6/8")
3/4
3
(read-from-string "#C(6/8 1)")
#C(3/4 1)
9
(read-from-string "1.2e2")
120.00001
5
(read-from-string "symbol")
SYMBOL
6
(defparameter *foo* 42)
*FOO*
(read-from-string "#.(setq *foo* \"gotcha\")")
"gotcha"
23
*foo*
"gotcha"
Comparing Strings
The general functions EQUAL and EQUALP can be used to test whether
two strings are equal. The strings are compared element-by-element, either
in a case-sensitive manner (EQUAL) or not (EQUALP). There’s also a
bunch of string-specific comparison functions. You’ll want to use these if
you’re deploying implementation-defined attributes of characters. Check
your vendor’s documentation in this case.
Here are a few examples. Note that all functions that test for inequality
return the position of the first mismatch as a generalized boolean. You can
also use the generic sequence function MISMATCH if you need more
versatility.
(string= "Marx" "Marx")
T
(string= "Marx" "marx")
NIL
(string-equal "Marx" "marx")
T
(string< "Groucho" "Zeppo")
0
(string< "groucho" "Zeppo")
NIL
(string-lessp "groucho" "Zeppo")
0
(mismatch "Harpo Marx" "Zeppo Marx" :from-end t :test #'char=)
3
String formatting
The format function has a lot of directives to print strings, numbers, lists,
going recursively, even calling Lisp functions, etc. We’ll focus here on a
few things to print and format strings.
The need of our examples arise when we want to print many strings and
justify them. Let’s work with this list of movies:
(defparameter movies '(
(1 "Matrix" 5)
(10 "Matrix Trilogy swe sub" 3.3)
))
We’ll use mapcar to iterate over our movies and experiment with the format
constructs.
(mapcar (lambda (it)
(format t "~a ~a ~a~%" (first it) (second it) (third i
movies)
which prints:
1 Matrix 5
10 Matrix Trilogy swe sub 3.3
Structure of format
Format directives start with ~. A final character like A or a (they are case
insensitive) defines the directive. In between, it can accept coma-separated
options and parameters.
~& does not print a newline if the output stream is already at one.
Tabs
So, expanding:
(mapcar (lambda (it)
(format t "~2a ~25a ~2a~%" (first it) (second it) (thi
movies)
1 Matrix 5
10 Matrix Trilogy swe sub 3.3
Use a @ as in ~2@A:
(format nil "~20@a" "yo")
;; " yo"
1 Matrix 5
10 Matrix Trilogy swe sub 3.3
Justifying decimals
With ~2,2f:
(mapcar (lambda (it)
(format t "~2@a ~25a ~2,2f~%" (first it) (second it) (
movies)
1 Matrix 5.00
10 Matrix Trilogy swe sub 3.30
Iteration
using ~^ to avoid printing the comma and space after the last element:
(format nil "~{~A~^, ~}" '(a b c))
;; "A, B, C"
Sometimes you want to justify a string, but the length is a variable itself.
You can’t hardcode its value as in (format nil "~30a" "foo"). Enters the
v directive. We can use it in place of the comma-separated prefix
parameters:
(let ((padding 30))
(format nil "~va" padding "foo"))
;; "foo "
Other times, you would like to insert a complete format directive at run
time. Enters the ? directive.
(format nil "~?" "~30a" '("foo"))
;; ^ a list
Conditional Formatting
If the number is out of range, the default option (after ~:;) is returned:
(format nil "~[dog~;cat~;bird~:;default~]" 9)
;; "default"
Cleaning up strings
The following examples use the cl-slug library which, internally, iterates
over the characters of the string and uses ppcre:regex-replace-all.
(ql:quickload "cl-slug")
Removing punctuation
They strip the punctuation with one ppcre unicode regexp ((ppcre:regex-
replace-all "[^\\p{L}\\p{N}]+" where p{L} is the “letter” category and
p{N} any kind of numeric character).
Appendix
All format directives
See also
Pretty printing table data, in ASCII art, a tutorial as a Jupyter
notebook.
Numbers
Common Lisp has a rich set of numerical types, including integer, rational,
floating point, and complex.
Some sources:
Introduction
Integer types
Common Lisp provides a true integer type, called bignum, limited only by
the total memory available (not the machine word size). For example this
would overflow a 64 bit integer by some way:
* (expt 2 200)
1606938044258990275541962092341162602522202993782792835301376
* most-positive-fixnum
4611686018427387903
* most-negative-fixnum
-4611686018427387904
isqrt, which returns the greatest integer less than or equal to the exact
positive square root of natural.
* (isqrt 10)
3
* (isqrt 4)
2
Rational types
Rational numbers of type ratio consist of two bignums, the numerator and
denominator. Both can therefore be arbitrarily large:
* (/ (1+ (expt 2 100)) (expt 2 100))
1267650600228229401496703205377/1267650600228229401496703205376
Floating point types attempt to represent the continuous real numbers using
a finite number of bits. This means that many real numbers cannot be
represented, but are approximated. This can lead to some nasty surprises,
particularly when converting between base-10 and the base-2 internal
representation. If you are working with floating point numbers then reading
What Every Computer Scientist Should Know About Floating-Point
Arithmetic is highly recommended.
The Common Lisp standard allows for several floating point types. In order
of increasing precision these are: short-float, single-float, double-
float, and long-float. Their precisions are implementation dependent,
and it is possible for an implementation to have only one floating point
precision for all types.
When reading floating point numbers, the default type is set by the special
variable *read-default-float-format*. By default this is SINGLE-FLOAT,
so if you want to ensure that a number is read as double precision then put a
d0 suffix at the end
* (type-of 1.24)
SINGLE-FLOAT
* (type-of 1.24d0)
DOUBLE-FLOAT
Other suffixes are s (short), f (single float), d (double float), l (long float)
and e (default; usually single float).
The default type can be changed, but note that this may break packages
which assume single-float type.
* (setq *read-default-float-format* 'double-float)
* (type-of 1.24)
DOUBLE-FLOAT
Note that unlike in some languages, appending a single decimal point to the
end of a number does not make it a float:
* (type-of 10.)
(INTEGER 0 4611686018427387903)
* (type-of 10.0)
SINGLE-FLOAT
If the result of a floating point calculation is too large then a floating point
overflow occurs. By default in SBCL (and other implementations) this
results in an error condition:
* (exp 1000)
; Evaluation aborted on #<FLOATING-POINT-OVERFLOW {10041720B3}>.
The error can be caught and handled, or this behaviour can be changed, to
return +infinity. In SBCL this is:
* (sb-int:set-floating-point-modes :traps '(:INVALID :DIVIDE-BY-
* (exp 1000)
#.SB-EXT:SINGLE-FLOAT-POSITIVE-INFINITY
* (/ 1 (exp 1000))
0.0
Arbitrary precision
* (sqrt-r 2)
+1.41421356237309504880...
Complex types
There are 5 types of complex number: The real and imaginary parts must be
of the same type, and can be rational, or one of the floating point types
(short, single, double or long).
Complex values can be created using the #C reader macro or the function
complex. The reader macro does not allow the use of expressions as real
and imaginary parts:
* #C(1 1)
#C(1 1)
* #C((+ 1 2) 5)
; Evaluation aborted on #<TYPE-ERROR expected-type: REAL datum:
* (complex (+ 1 2) 5)
#C(3 5)
If constructed with mixed types then the higher precision type will be used
for both parts.
* (type-of #C(1 1))
(COMPLEX (INTEGER 1 1))
The real and imaginary parts of a complex number can be extracted using
realpart and imagpart:
Complex arithmetic
Converting numbers
Most numerical functions automatically convert types as needed. The
coerce function converts objects from one type to another, including
numeric types.
* (floor 1.42)
1
0.41999996
* (round 1.42)
1
0.41999996
* (truncate 1.42)
1
0.41999996
* (floor -1.42)
-2
0.58000004
* (ceiling -1.42)
-1
-0.41999996
Similar functions fceiling, ffloor, fround and ftruncate return the
result as floating point, of the same type as their argument:
* (ftruncate 1.3)
1.0
0.29999995
Comparing numbers
See Common Lisp the Language, 2nd Edition, Section 12.3.
The = predicate returns T if all arguments are numerically equal. Note that
comparison of floating point numbers includes some margin for error, due
to the fact that they cannot represent all real numbers and accumulate
errors.
Note that this does not mean that a single-float is always precise to
within 6e-8:
* (= (+ 10s0 4e-7) 10s0)
T
* (= (+ 10s0 5e-7) 10s0)
NIL
Instead this means that single-float is precise to approximately seven
digits. If a sequence of calculations are performed, then error can
accumulate and a larger error margin may be needed. In this case the
absolute difference can be compared:
* (< (abs (- (+ 10s0 5e-7)
10s0))
1s-6)
T
When comparing numbers with = mixed types are allowed. To test both
numerical value and type use eql:
* (= 3 3.0)
T
* (eql 3 3.0)
NIL
* (dotimes (i 3)
(let ((*random-state* (make-random-state nil)))
(format t "~a~%"
(loop for i from 0 below 10 collecting (random 10)
(8 3 9 2 1 8 0 0 4 1)
(8 3 9 2 1 8 0 0 4 1)
(8 3 9 2 1 8 0 0 4 1)
This generates 10 random numbers in a loop, but each time the sequence is
the same because the *random-state* special variable is dynamically
bound to a copy of its state before the let form.
Other resources:
Bit-wise Operation
Common Lisp also provides many functions to perform bit-wise arithmetic
operations. Some commonly used ones are listed below, together with their
C/C++ equivalence.
Common
C/C++ Description
Lisp
a & b &
(logand a b c) Bit-wise AND of multiple operands
c
(logior a b c) a|b|c Bit-wise OR of multiple operands
(lognot a) ~a Bit-wise NOT of single operands
Bit-wise exclusive or (XOR) of multiple
(logxor a b c) a ^ b ^ c
operands
(ash a 3) a << 3 Bit-wise left shift
(ash a -3) a >> 3 Bit-wise right shift
For example:
* (logior 1 2 4 8)
15
;; Explanation:
;; 0001
;; 0010
;; 0100
;; | 1000
;; -------
;; 1111
* (logand 2 -3 4)
0
;; Explanation:
;; 0010 (2)
;; 1101 (two's complement of -3)
;; & 0100 (4)
;; -------
;; 0000
* (logxor 1 3 7 15)
10
;; Explanation:
;; 0001
;; 0011
;; 0111
;; ^ 1111
;; -------
;; 1010
* (lognot -1)
0
;; Explanation:
;; 11 -> 00
* (lognot -3)
2
;; 101 -> 010
* (ash 3 2)
12
;; Explanation:
;; 11 -> 1100
* (ash -5 -2)
-2
;; Explanation
;; 11011 -> 110
Please see the CLHS page for a more detailed explanation or other bit-wise
functions.
Loop, iteration, mapping
Introduction: loop, iterate, for, mapcar, series
loop is the built-in macro for iteration.
Its simplest form is (loop (print "hello")): this will print forever.
iterate is a popular iteration macro that aims at being simpler, “lispier” and
more predictable than loop, besides being extensible. However it isn’t built-
in, so you have to import it:
(ql:quickload “iterate”) (use-package :iterate)
(if you use loop and iterate in the same package, you might run into name
conflicts)
Much of the examples on this page that are valid for loop are also valid for
iterate, with minor modifications.
for is an extensible iteration macro that is often shorter than loop, that
“unlike loop is extensible and sensible, and unlike iterate does not require
code-walking and is easier to extend”.
It has the other advantage of having one construct that works for all data
structures (lists, vectors, hash-tables…): in doubt, just use for… over:
(for:for ((x over <your data structure>))
(print …))
(ql:quickload “for”)
We’ll also give examples with mapcar and map, and eventually with their
friends mapcon, mapcan, maplist, mapc and mapl which E. Weitz
categorizes very well in his “Common Lisp Recipes”, chap. 7. The one you
are certainly accustomed to from other languages is mapcar: it takes a
function, one or more lists as arguments, applies the function on each
element of the lists one by one and returns a list of result.
(mapcar (lambda (it) (+ it 10)) '(1 2 3))
(11 12 13)
map is generic, it accepts list and vectors as arguments, and expects the type
for its result as first argument:
(map 'vector (lambda (it) (+ it 10)) '(1 2 3))
;; #(11 12 13)
(map 'list (lambda (it) (+ it 10)) #(1 2 3))
;; (11 12 13)
(map 'string (lambda (it) (code-char it)) '#(97 98 99))
;; "abc"
The other constructs have their advantages in some situations ;) They either
process the tails of lists, or concatenate the return values, or don’t return
anything. We’ll see some of them.
If you like mapcar, use it a lot, and would like a quicker and shorter way to
write lambdas, then you might like one of those lambda shorthand libraries.
and voilà :) We won’t use this more in this recipe, but feel free to do.
Last but not least, you might like series, a library that describes itself as
combining aspects of sequences, streams, and loops. Series expressions
look like operations on sequences (= functional programming), but can
achieve the same high level of efficiency as a loop. Series first appeared in
“Common Lisp the Language”, in the appendix A (it nearly became part of
the language). Series looks like this:
(collect
(mapping ((x (scan-range :from 1 :upto 5)))
(* x x)))
;; (1 4 9 16 25)
series is good, but its function names are different from what we find in
functional languages today. You might like the “Generators The Way I Want
Them Generated” library. It is a lazy sequences library, similar to series
although younger and not as complete, with a “modern” API with words
like take, filter, for or fold, and that is easy to use.
(range :from 20)
;; #<GTWIWTG::GENERATOR! {1001A90CA3}>
Recipes
Looping forever, return
(loop
(print "hello"))
dotimes
(dotimes (n 3)
(print n))
;; =>
;; 0
;; 1
;; 2
;; NIL
Here dotimes returns nil. There are two ways to return a value. First, you
can set a result form in the lambda list:
(dotimes (n 3 :done)
;; ^^^^^ result form. It can be a s-expression.
(print n))
;; =>
;; 0
;; 1
;; 2
;; :DONE
loop… repeat
(loop repeat 10
do (format t "Hello!~%"))
Series
(iterate ((n (scan-range :below 10)))
(print n))
First, as shown above, we can simply use (loop ...) to loop infinitely.
Here we show how to loop on a list forever.
We can build an infinite list by setting its last element to the list itself:
(loop with list-a = '(1 2 3)
with infinite-list = (setf (cdr (last list-a)) list-a)
for item in infinite-list
repeat 8
collect item)
;; (1 2 3 1 2 3 1 2)
Illustration: (last '(1 2 3)) is (3), a list, or rather a cons cell, whose car
is 3 and cdr is NIL. See the data-structures chapter for a reminder. This is
the representation of (list 3):
[o|/]
|
3
By setting the cdr of the last element to the list itself, we make it recur on
itself.
If you need to alternate only between two values, use for … then:
(loop repeat 4
for up = t then (not up)
do (print up))
T
NIL
T
NIL
or, a generalized iteration clause for lists and vectors, use in-sequence
(you’ll pay a speed penalty).
symbols in-package
forms - or lines, or whatever-you-wish - in-file, or in-stream
elements in-sequence - sequences can be vectors or lists
loop
mapcar
loop: across
Series
We create a hash-table:
(defparameter h (make-hash-table))
(setf (gethash 'a h) 1)
(setf (gethash 'b h) 2)
iterate
Use in-hashtable:
(iter (for (key value) in-hashtable h)
(collect (list key value)))
for
maphash
The lambda function of maphash takes two arguments: the key and the
value:
(maphash (lambda (key val)
(format t "key: ~a val:~a~&" key val))
h)
;; key: A val:1
;; key: B val:2
;; NIL
dohash
Series
loop
If a list is smaller than the other one, loop stops at the end of the small one:
(loop for x in '(a b c)
for y in '(1 2 3 4 5)
collect (list x y))
;; ((A 1) (B 2) (C 3))
We could loop over the biggest list and manually access the elements of the
smaller one by index, but it would quickly be inefficient. Instead, we can
tell loop to extend the short list.
(loop for y in '(1 2 3 4 5)
for x-list = '(a b c) then (cdr x-list)
for x = (or (car x-list) 'z)
collect (list x y))
;; ((A 1) (B 2) (C 3) (Z 4) (Z 5))
The trick is that the notation for … = … then (cdr …) (note the = and the
role of then) shortens our intermediate list at each iteration (thanks to cdr).
It will first be '(a b c), the initial value, then we will get the cdr: (2 3),
then (3), then NIL. And both (car NIL) and (cdr NIL) return NIL, so we
are good.
mapcar
(mapcar (lambda (x y)
(list x y))
'(a b c)
'(1 2 3))
;; ((A 1) (B 2) (C 3))
or simply:
(mapcar #'list
'(a b c)
'(1 2 3))
;; ((A 1) (B 2) (C 3))
Series
(collect
(#Mlist (scan '(a b c))
(scan '(1 2 3))))
A more efficient way, when the lists are known to be of equal length:
(collect
(mapping (((x y) (scan-multiple 'list
'(a b c)
'(1 2 3))))
(list x y)))
Nested loops
loop
iterate
(iter outer
(for i below 2)
(iter (for j below 3)
(in outer (collect (list i j)))))
;; ((0 0) (0 1) (0 2) (1 0) (1 1) (1 2))
Series
(collect
(mapping ((x (scan-range :from 1 :upto 3)))
(collect (scan-range :from 1 :upto x))))
Use =.
With for:
(loop for x from 1 to 3
for y = (* x 10)
collect y)
;; (10 20 30)
With with, the difference being that the value is computed only once:
(loop for x from 1 to 3
for y = (* x 10)
with z = x
collect (list x y z))
;; ((1 10 1) (2 20 1) (3 30 1))
so it turns out we can specify the type before the = and chain the with with
and:
(loop for x from 1 to 3
for y integer = (* x 10)
with z integer = x
collect (list x y z))
We can also give for a then clause that will be called at each iteration:
(loop repeat 3
for intermediate = 10 then (incf intermediate)
do (print intermediate))
10
11
12
T
NIL
T
NIL
loop
Iterate through a list, and have a counter iterate in parallel. The length of the
list determines when the iteration ends. Two sets of actions are defined, one
of which is executed conditionally.
* (loop for x in '(a b c d e)
for y from 1
when (> y 1)
do (format t ", ")
do (format t "~A" x)
)
A, B, C, D, E
NIL
if (> y 1)
do (format t ", ~A" x)
else do (format t "~A" x)
)
A, B, C, D, E
NIL
Series
loop
from… to…:
(loop for i from 0 to 10
do (print i))
;; 0 1 2 3 4 5 6 7 8 9 10
Series
Steps
loop
with by:
(loop for i from 1 to 10 by 2
do (print i))
if you use by (1+ (random 3)), the random is evaluated only once, as if it
was in a closure:
(let ((step (random 3)))
(loop for i from 1 to 10 by (+ 1 step)
do (print i)))
The step must always be a positive number. If you want to count down, see
above.
Series
with :by:
(iterate ((i (scan-range :from 1 :upto 10 :by 2)))
(print i))
loop
(42 82 24 92 92)
(55 89 59 13 49)
(loop repeat 10
for x = (random 100)
if (evenp x)
collect x into evens
and do (format t "~a is even!~%" x)
else
collect x into odds
and count t into n-odds
finally (return (values evens odds n-odds)))
46 is even!
8 is even!
76 is even!
58 is even!
0 is even!
(46 8 76 58 0)
(7 45 43 15 69)
5
iterate
Translating (or even writing!) the above example using iterate is straight-
forward:
(iter (repeat 10)
(for x = (random 100))
(if (evenp x)
(progn
(collect x into evens)
(format t "~a is even!~%" x))
(progn
(collect x into odds)
(count t into n-odds)))
(finally (return (values evens odds n-odds))))
Series
The preceding loop would be done a bit differently in Series. split sorts
one series into multiple according to provided boolean series.
(let* ((number (#M(lambda (n) (random 100))
(scan-range :below 10)))
(parity (#Mevenp number)))
(iterate ((n number) (p parity))
(when p (format t "~a is even!~%" n)))
(multiple-value-bind (evens odds) (split number parity)
(values (collect evens)
(collect odds)
(collect-length odds))))
Note that although iterate and the three collect expressions are written
sequentially, only one iteration is performed, the same as the example with
loop.
loop
Series
We truncate the series with until-if, then collect from its result.
(collect
(until-if (lambda (i) (> i 3))
(scan '(1 2 3 4 5))))
Series
loop
The special loop named foo syntax allows you to create a loop that you can
exit early from. The exit is performed using return-from, and can be used
from within nested loops.
;; useless example
(loop named loop-1
for x from 0 to 10 by 2
do (loop for y from 0 to 100 by (1+ (random 3))
when (< x y)
do (return-from loop-1 (values x y))))
0
2
Sometimes, you want to return early but execute the finally clause
anyways. Use loop-finish.
(loop for x from 0 to 100
do (print x)
when (>= x 3)
return x
finally (print :done)) ;; <-- not printed
;; 0
;; 1
;; 2
;; 3
;; 3
It is most needed when some computation must take place in the finally
clause.
Series
Count
loop
Series
Summation
loop
Series
max, min
loop
and minimize.
Series
and collect-min.
loop
(loop for (a b) in '((x 1) (y 2) (z 3))
collect (list b a) )
;; ((1 X) (2 Y) (3 Z))
Then we add destructuring to bind only the first two items at each iteration:
(loop for (key value) on '(a 2 b 2 c 3) by #'cddr
collect (list key (* 2 value)))
;; ((A 2) (B 4) (C 6))
Series
If you are a newcomer in Lisp, it’s perfectly OK to keep this section for
later. You could very well spend your career in Lisp without resorting to
those features… although they might turn out useful one day.
loop requires that all for clauses appear before the loop body, for example
before a while. It’s ok for iter to not follow this order:
(iter (for x in '(1 2 99)
(while (< x 10))
(for y = (print x))
(collect (list x y)))
Skips the remainder of the loop body and begins the next iteration of
the loop.
Generators
Use generate and next. A generator is lazy, it goes to the next value when
said explicitly.
(iter (for i in '(1 2 3 4 5))
(generate c in-string "black")
(if (oddp i) (next c))
(format t "~a " c))
;; b b l l a
;; NIL
In this case however we can do it with loop’s parallel binding and, which is
unsupported in iterate:
(loop for el in '(a b c d e)
and prev-el = nil then el
collect (list el prev-el))
More clauses
in-string can be used explicitly to iterate character by character over
a string. With loop, use across.
(iter (for c in-string "hello")
(collect c))
;; => (#\h #\e #\l #\l #\o)
Iterate is extensible
(defmacro dividing-by (num &keys (initial-value 0))
`(reducing ,num by #'/ initial-value ,initial-value))
We saw libraries extending loop, for example CLSQL, but they are full of
feature flag checks (#+(or allegro clisp-aloop cmu openmcl sbcl
scl)) and they call internal modules (ansi-loop::add-loop-path, sb-
loop::add-loop-path etc).
Custom series scanners
If we often scan the same type of object, we can write our own scanner for
it: the iteration itself can be factored out. Taking the example above, of
scanning a list of two-element lists, we’ll write a scanner that returns a
series of the first elements and a series of the second.
(defun scan-listlist (listlist)
(declare (optimizable-series-function 2))
(map-fn '(values t t)
(lambda (l)
(destructuring-bind (a b) l
(values a b)))
(scan listlist)))
(collect
(mapping (((a b) (scan-listlist '((x 1) (y 2) (z 3)))))
(list b a)))
It’s a bit longer than it needs to be, the mapping form’s only purpose is to
bind the variable i, and i is used in only one place. Series has a “hidden
feature” that allows us to simplify this expression to the following:
(collect-sum (* 2 (scan-range :length 5)))
(series::install :implicit-map t)
Iterate gotchas
It breaks on the function count:
(iter (for i from 1 to 10)
(sum (count i '(1 3 5))))
It works in loop:
(loop for i from 1 to 10
sum (count i '(1 3 5 99)))
;; 3
Variable Clauses
initially finally for as with
Main Clauses
do collect collecting append
appending nconc nconcing into count
counting sum summing maximize return loop-finish
maximizing minimize minimizing doing
thereis always never if when
unless repeat while until
These don’t introduce clauses:
= and it else end from upfrom
above below to upto downto downfrom
in on then across being each the hash-key
hash-keys of using hash-value hash-values
symbol symbols present-symbol
present-symbols external-symbol
external-symbols fixnum float t nil of-type
But note that it’s the parsing that determines what is a keyword. For
example in:
(loop for key in hash-values)
Iterate
Series
Others
This page covers what can be done with the built-in multidimensional
arrays, but there are limitations. In particular:
$ cd ~/quicklisp/local-projects
$ git clone https://github.com/rigetticomputing/magicl.git
Taking this further, domain specific languages have been built on Common
Lisp, which can be used for numerical calculations with arrays. At the time
of writing the most widely used and supported of these are:
Maxima
Axiom
Creating
The function CLHS: make-array can create arrays filled with a single value
* (defparameter *my array* (make array '(3 2) :initial element 1
* (defparameter *my-array* (make-array '(3 2) :initial-element 1
*MY-ARRAY*
* *my-array*
#2A((1.0 1.0) (1.0 1.0) (1.0 1.0))
(:ARRAY-OPERATIONS)
Random numbers
(:ALEXANDRIA)
* (aops:generate #'alexandria:gaussian-random 4)
#(0.5522547885338768d0 -1.2564808468164517d0 0.9488161476129733d
-0.10372852118266523d0)
Note that this is not particularly efficient: It requires a function call for each
element, and although gaussian-random returns two random numbers, only
one of them is used.
Accessing elements
To access the individual elements of an array there are the aref and row-
major-aref functions.
The aref function takes the same number of index arguments as the array
has dimensions. Indexing is from 0 and row-major as in C, but not Fortran.
* (defparameter *a* #(1 2 3 4))
*A*
* (aref *a* 0)
1
* (aref *a* 3)
4
* (defparameter *b* #2A((1 2 3) (4 5 6)))
*B*
* (aref *b* 1 0)
4
* (aref *b* 0 2)
3
or the rank of the array can be found, and then the size of each dimension
queried:
* (array-rank *a*)
1
* (array-dimension *a* 0)
4
* (array-rank *b*)
2
* (array-dimension *b* 0)
2
* (array-dimension *b* 1)
3
a[0 0] = 1
a[0 1] = 2
a[0 2] = 3
a[1 0] = 4
a[1 1] = 5
a[1 2] = 6
NIL
A utility macro which does this for multiple dimensions is nested-loop:
(defmacro nested-loop (syms dimensions &body body)
"Iterates over a multidimensional range of indices.
Example:
(nested-loop (i j) '(10 20) (format t '~a ~a~%' i j))
"
(unless syms (return-from nested-loop `(progn ,@body))) ; No s
a[0 0] = 1
a[0 1] = 2
a[0 2] = 3
a[1 0] = 4
a[1 1] = 5
a[1 2] = 6
NIL
The cmu-infix library provides some different syntax which can make
mathematical expressions easier to read:
* (ql:quickload :cmu-infix)
To load "cmu-infix":
Load 1 ASDF system:
cmu-infix
; Loading "cmu-infix"
(:CMU-INFIX)
* (named-readtables:in-readtable cmu-infix:syntax)
(("COMMON-LISP-USER" . #<NAMED-READTABLE CMU-INFIX:SYNTAX {10030
...)
* #i(arr[0 1] = 2.0)
2.0
* arr
#2A((1.0 2.0) (1.0 1.0) (1.0 1.0))
Element-wise operations
To multiply two arrays of numbers of the same size, pass a function to each
in the array-operations library:
* (aops:each #'* #(1 2 3) #(2 3 4))
#(2 6 12)
Note that each is not destructive, but makes a new array. All arguments to
each must be arrays of the same size, so (aops:each #'+ 42 *a*) is not
valid.
Vectorising expressions
;; Get the size of the first variable, and create a new arra
;; of the same type for the result
;; of the same type for the result
`(let ((size (array-total-size ,(first variables))) ; Total
(result (make-array (array-dimensions ,(first variabl
:element-type (array-element-type
;; Check that all variables have the same sizeo
,@(mapcar (lambda (var) `(if (not (equal (array-dimension
(array-dimension
(error "~S and ~S have diffe
(rest variables))
[Note: Expanded versions of this macro are available in this fork of array-
operations, but not Quicklisp]
Calling BLAS
Scale an array
AXPY
If the y array is complex, then this operation calls the complex number
versions of these operators:
* (defparameter x #(1 2 3))
* (defparameter y (make-array 3 :element-type '(complex double-f
( p y ( y yp ( p
:initial-element #C(1d0 1d0)))
* y
#(#C(1.0d0 1.0d0) #C(1.0d0 1.0d0) #C(1.0d0 1.0d0))
Dot product
Reductions
More complex reductions are sometimes needed, for example finding the
maximum absolute difference between two arrays. Using the above
methods we could do:
* (defparameter a #2A((1 2) (3 4)))
A
* (defparameter b #2A((1 3) (5 4)))
B
* (reduce #'max (aops:flatten
(aops:each
(lambda (a b) (abs (- a b))) a b)))
2
This involves allocating an array to hold the intermediate result, which for
large arrays could be inefficient. Similarly to vectorize defined above, a
macro which does not allocate can be defined as:
(defmacro vectorize-reduce (fn variables &body body)
"Performs a reduction using FN over all elements in a vectoriz
on array VARIABLES.
(let ((size (gensym)) ; Total array size (same for all variabl
(result (gensym)) ; Returned value
(indx (gensym))) ; Index inside loop from 0 to size
Using this macro, the maximum value in an array A (of any shape) is:
* (vectorize-reduce #'max (a) a)
Linear algebra
Several packages provide bindings to BLAS and LAPACK libraries,
including:
lla
MAGICL
To load "lla":
Load 1 ASDF system:
lla
; Loading "lla"
.
(:LLA)
Matrix multiplication
Note that one vector is treated as a row vector, and the other as column:
* (lla:mm #(1 2 3) #(2 3 4))
20
Matrix-vector product
Matrix-matrix multiply
Note that the type of the returned arrays are simple arrays, specialised to
element type double-float
* (type-of (lla:mm #2A((1 0 0) (0 1 0) (0 0 1)) #(1 2 3)))
(SIMPLE-ARRAY DOUBLE-FLOAT (3))
Outer product
(:ARRAY-OPERATIONS)
* (aops:outer #'* #(1 2 3) #(2 3 4))
#2A((2 3 4) (4 6 8) (6 9 12))
which has created a new 2D array A[i j] = B[i] * C[j]. This outer
function can take an arbitrary number of inputs, and inputs with multiple
dimensions.
Matrix inverse
e.g
* (defparameter a #2A((1 2 3) (0 2 1) (1 3 2)))
A
* (defparameter b (lla:invert a))
B
* (lla:mm a b)
#2A((1.0d0 2.220446049250313d-16 0.0d0)
(0.0d0 1.0d0 0.0d0)
(0.0d0 1.1102230246251565d-16 0.9999999999999998d0))
Calculating the direct inverse is generally not advisable, particularly for
large matrices. Instead the LU decomposition can be calculated and used for
multiple inversions.
* (defparameter a #2A((1 2 3) (0 2 1) (1 3 2)))
A
* (defparameter b (lla:mm a #(1 2 3)))
B
* (lla:solve (lla:lu a) b)
#(1.0d0 2.0d0 3.0d0)
The diagonal matrix (singular values) and vectors can be accessed with
functions:
(lla:svd-u a-svd)
#2A((-0.6494608633564334d0 0.7205486773948702d0 0.24292013188045
(-0.3744175632000917d0 -0.5810891192666799d0 0.7225973455785
(-0.6618248071322363d0 -0.3783451320875919d0 -0.647180721043
* (lla:svd-d a-svd)
(lla:svd d a svd)
#S(CL-NUM-UTILS.MATRIX:DIAGONAL-MATRIX
:ELEMENTS #(5.593122609997059d0 1.2364443401235103d0 0.433802
* (lla:svd-vt a-svd)
#2A((-0.2344460799312531d0 -0.7211054639318696d0 -0.651952410450
(0.2767642134809678d0 -0.6924017945853318d0 0.66631923654602
(-0.9318994611765425d0 -0.02422116311440764d0 0.361907073039
Matlisp
The Matlisp scientific computation library provides high performance
operations on arrays, including wrappers around BLAS and LAPACK
functions. It can be loaded using quicklisp:
* (ql:quickload :matlisp)
* (in-package :my-new-code)
and to use the #i infix reader (note the same name as for cmu-infix), run:
* (named-readtables:in-readtable :infix-dispatch-table)
Creating tensors
* (matlisp:zeros '(2 2))
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 2)
0.000 0.000
0.000 0.000
>
Note that by default matrix storage types are double-float. To create a
complex array using zeros, ones and eye, specify the type:
* (matlisp:zeros '(2 2) '((complex double-float)))
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: (COMPLEX DOUBLE-FLOAT)>| #(2
0.000 0.000
0.000 0.000
>
As well as zeros and ones there is eye which creates an identity matrix:
* (matlisp:eye '(3 3) '((complex double-float)))
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: (COMPLEX DOUBLE-FLOAT)>| #(3
1.000 0.000 0.000
0.000 1.000 0.000
0.000 0.000 1.000
>
Ranges
* (matlisp:linspace 0 (* 2 pi) 5)
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(5)
0.000 1.571 3.142 4.712 6.283
>
Currently linspace requires real inputs, and doesn’t work with complex
numbers.
Random numbers
Reader macros
* #d[[1,2,3],[4,5,6]]
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 3)
1.000 2.000 3.000
4.000 5.000 6.000
>
or a list:
* (m:copy (m:ones '(2 3)) 'cons)
((1.0d0 1.0d0 1.0d0) (1.0d0 1.0d0 1.0d0))
Element access
The ref function is the equivalent of aref for standard CL arrays, and is
also setf-able:
* (defparameter a (matlisp:ones '(2 3)))
Element-wise operations
The matlisp-user package, loaded when matlisp is loaded, contains
functions for operating element-wise on tensors.
* (matlisp-user:* 2 (ones '(2 3)))
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 3)
2.000 2.000 2.000
2.000 2.000 2.000
>
This includes arithmetic operators ‘+’, ‘-’, ’*‘,’/’ and ‘expt’, but also
sqrt,sin,cos,tan, hyperbolic functions, and their inverses. The #i reader
macro recognises many of these, and uses the matlisp-user functions:
* (let ((a (ones '(2 2)))
(b (random-normal '(2 2))))
#i( 2 * a + b ))
#<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 2)
0.9684 3.250
1.593 1.508
>
CL-USER> (get-universal-time)
3220993326
Of course this value is not very readable, so you can use the function
decode-universal-time to turn it into a “calendar time” representation:
NB: in the next section we’ll use the local-time library to get more user-
friendy functions, such as (local-time:universal-to-timestamp (get-
universal-time)) which returns @2021-06-25T09:16:29.000000+02:00.
This call to decode-universal-time returns nine values: seconds,
minutes, hours, day, month, year, day of the week, daylight
savings time flag and time zone. Note that the day of the week is
represented as an integer in the range 0..6 with 0 being Monday and 6 being
Sunday. Also, the time zone is represented as the number of hours you need
to add to the current time in order to get GMT time.
is equivalent to
CL-USER> (decode-universal-time (get-universal-time))
CL-USER> (multiple-value-bind
(second minute hour day month year day-of-week dst-p
(get-decoded-time)
(format t "It is now ~2,'0d:~2,'0d:~2,'0d of ~a, ~d/~
hour
minute
second
(nth day-of-week *day-names*)
month
day
year
(- tz)))
(- tz)))
It is now 17:07:17 of Saturday, 1/26/2002 (GMT-5)
Note that the result is automatically adjusted for daylight savings time if the
time zone is not supplied. If it is supplied, than Lisp assumes that the
specified time zone already accounts for daylight savings time, and no
adjustment is performed.
Since universal times are simply numbers, they are easier and safer to
manipulate than calendar times. Dates and times should always be stored as
universal times if possible, and only converted to string representations for
output purposes. For example, it is straightforward to know which of two
dates came before the other, by simply comparing the two corresponding
universal times with <.
Internal Time
Internal time is the time as measured by your Lisp environment, using your
computer’s clock. It differs from universal time in three important respects.
First, internal time is not measured starting from a specified point in time: it
could be measured from the instant you started your Lisp, from the instant
you booted your machine, or from any other arbitrary time point in the past.
As we will see shortly, the absolute value of an internal time is almost
always meaningless; only differences between internal times are useful. The
second difference is that internal time is not measured in seconds, but in a
(usually smaller) unit whose value can be deduced from internal-time-
units-per-second:
CL-USER> internal-time-units-per-second
1000
This means that in the Lisp environment used in this example, internal time
is measured in milliseconds.
Finally, what is being measured by the “internal time” clock? There are
actually two different internal time clocks in your Lisp:
one of them measures the passage of “real” time (the same time that
universal time measures, but in different units), and
the other one measures the passage of CPU time, that is, the time your
CPU spends doing actual computation for the current Lisp process.
On most modern computers these two times will be different, since your
CPU will never be entirely dedicated to your program (even on single-user
machines, the CPU has to devote part of its time to processing interrupts,
performing I/O, etc). The two functions used to retrieve internal times are
called get-internal-real-time and get-internal-run-time respectively.
Using them, we can solve the above problem about measuring a function’s
run time, which is what the time built-in macro does.
CL-USER> (time (sleep 1))
Evaluation took:
1.000 seconds of real time
0.000049 seconds of total run time (0.000044 user, 0.000005 sy
0.00% CPU
2,594,553,447 processor cycles
0 bytes consed
In particular, it can
print timestamps in various standard or custom formats (e.g. RFC1123
or RFC3339)
parse timestrings,
perform time arithmetic,
convert Unix times, timestamps, and universal times to and from.
We present below what we find the most useful functions. See its manual
for the full details.
It is available in Quicklisp:
CL-USER> (ql:quickload "local-time")
encode-timestamp nsec sec minute hour day month year &key timezone
offset into
The offset is the number of seconds offset from UTC of the locale. If offset
is not specified, the offset will be guessed from the timezone. If a
timestamp is passed as the into argument, its value will be set and that
timestamp will be returned. Otherwise, a new timestamp is created.
(local-time:today)
@2019-11-13T01:00:00.000000+01:00
(defun tomorrow ()
"Returns a timestamp representing the day after today."
(timestamp+ (today) 1 :day))
@1984-02-29T23:59:59.999999+01:00
Querying timestamp objects (get the day, the day of week, the
days in month…)
Use:
timestamp-[year month day hour minute second millisecond
timestamp [year, month, day, hour, minute, second, millisecond,
day-of-week (starts at 0 for sunday),
millenium, century, decade]
8
8
You can of course bind each time unit (:sec :minute :day) to its variable,
in any order.
Its default value is +iso-8601-format+, with the output shown above. The
+rfc3339-format+ format defaults to it.
With +rfc-1123-format+:
With +asctime-format+:
(local-time:format-timestring nil (local-time:now) :format local
"Wed Nov 13 18:13:15 2019"
With +iso-week-date-format+:
(local-time:format-timestring nil (local-time:now) :format local
"2019-W46-3"
Putting all this together, here is a function that returns Unix times as a
human readable string:
(defun unix-time-to-human-string (unix-time)
(local-time:format-timestring
nil
(local-time:unix-to-timestamp unix-time)
:format local-time:+asctime-format+))
(unix-time-to-human-string (get-universal-time))
"Mon Jun 25 06:46:49 2091"
The syntax consists of a list made of symbols with special meanings (:year,
:day…), strings and characters:
There are :year :month :day :weekday :hour :hour12 :min :sec
:msec, long and short notations (:long-weekday for “Monday”, :short-
weekday for “Mon.”, :minimal-weekday for “Mo.” as well as :long-month
for “January” and :short-month for “Jan.”), gmt offset, timezone markers,
:ampm, :ordinal-day (1st, 23rd), iso numbers and more.
We see the form (:day 2): the 2 is for padding, to ensure that the day is
printed with two digits (not only 1, but 01). There could be an optional third
argument, the character with which to fill the padding (by default, #\0).
To parse more formats such as “Thu Jul 23 19:42:23 2013” (asctime), we’ll
use the cl-date-time-parser library.
The parse-timestring docstring is:
Examples:
(local-time:parse-timestring "2019-11-13T18:09:06.313650+01:00")
;; @2019-11-13T18:09:06.313650+01:00
(local-time:parse-timestring "2019-11-13")
;; @2019-11-13T01:00:00.000000+01:00
This custom format fails by default: “2019/11/13”, but we can set the
:date-separator to “/”:
Now a format like "“Wed Nov 13 18:13:15 2019” will fail. We’ll use the
cl-date-time-parser library:
(cl-date-time-parser:parse-date-time "Wed Nov 13 18:13:15 2019")
;; 3782657595
;; 0
It returns the universal time which, in turn, we can ingest with the local-
time library:
(local-time:universal-to-timestamp *)
;; @2019-11-13T19:13:15.000000+01:00
Misc
(match '(1 2 3)
((cons x y)
; ^^ pattern
(print x)
(print y)))
;; |-> 1
;; |-> (2 3)
list, list*
list is a strict pattern, it expects the length of the matched object to be the
same length as its subpatterns.
(match '(something 2 3)
((list a b _)
(values a b)))
SOMETHING
2
(match '(1 2 . 3)
((list* _ _ x)
x))
3
However pay attention that if list* receives only one object, that object is
returned, regardless of whether or not it is a list:
(match #(0 1 2)
((list* a)
a))
#(0 1 2)
vector, vector*
vector checks if the object is a vector, if the lengths are the same, and if the
contents matches against each subpatterns.
vector* is similar, but called a soft-match variant that allows if the length
is larger-than-equal to the length of subpatterns.
(match #(1 2 3)
((vector _ x _)
x))
;; -> 2
(match #(1 2 3 4)
((vector _ x _)
x))
;; -> NIL : does not match
(match #(1 2 3 4)
((vector* _ x _)
x))
;; -> 2 : soft match.
(match *x*
;; make-instance style
((foo :bar a :baz b)
(values a b))
;; with-slots style
((foo (bar a) (baz b))
(values a b))
;; slot name style
((foo bar baz)
(values bar baz)))
type, satisfies
The type pattern matches if the object is of type. satisfies matches if the
predicate returns true for the object. A lambda form is acceptable.
All these patterns first check if the pattern is a list. If that is satisfied, then
they obtain the contents, and the value is matched against the subpattern.
See https://github.com/guicho271828/trivia/wiki/Type-Based-
Destructuring-Patterns#array-simple-array-row-major-array-pattern !
and, or
The following:
(match x
((or (list 1 a)
(cons a 3))
a))
not
It does not match when subpattern matches. The variables used in the
subpattern are not visible in the body.
Guards
Guards allow us to use patterns and to verify them against a predicate.
Nesting patterns
Patterns can be nested:
(match '(:a (3 4) 5)
((list :a (list _ c) _)
c))
returns 4.
See more
See special patterns: place, bind and access.
Regular Expressions
The ANSI Common Lisp standard does not include facilities for regular
expressions, but a couple of libraries exist for this task, for instance: cl-
ppcre.
See also the respective Cliki: regexp page for more links.
PPCRE
CL-PPCRE (abbreviation for Portable Perl-compatible regular expressions)
is a portable regular expression library for Common Lisp with a broad set of
features and good performance. It has been ported to a number of Common
Lisp implementations and can be easily installed (or added as a
dependency) via Quicklisp:
(ql:quickload "cl-ppcre")
Basic operations with the CL-PPCRE library functions are described below.
The scan function tries to match the given pattern and on success returns
four multiple-values values - the start of the match, the end of the match,
and two arrays denoting the beginnings and ends of register matches. On
failure returns NIL.
A regular expression pattern can be compiled with the create-scanner
function call. A “scanner” will be created that can be used by other
functions.
For example:
(let ((ptrn (ppcre:create-scanner "(a)*b")))
(ppcre:scan ptrn "xaaabd"))
but will require less time for repeated scan calls as parsing the expression
and compiling it is done only once.
Extracting information
all-matches, all-matches-as-strings
Look carefully: it actually return a list containing the start and end positions
of all matches: 9 and 10 are the start and end for the first number (1), and so
on.
If you wanted to extract integers from this example string, simply map
parse-integer to the result:
CL-USER> (ppcre:all-matches-as-strings "\\d+" "numbers: 1 10 42"
;; ("1" "10" "42")
CL-USER> (mapcar #'parse-integer *)
(1 10 42)
The two functions accept the usual :start and :end key arguments.
Additionnaly, all-matches-as-strings accepts a :sharedp argument:
scan-to-strings, register-groups-bind
Syntactic sugar
You might like to use CL-PPCRE with the cl-interpol library. cl-interpol is
a library for Common Lisp which modifies the reader in a way that
introduces interpolation within strings similar to Perl, Scala, or Unix Shell
scripts.
In this mode you can write regular expressions in-between #?/ and /.
See more
cl-ppcre on common-lisp-libraries.readthedocs.io and read on: do-
matches, do-matches-as-strings, do-register-groups, do-scans,
parse-string, regex-apropos, quote-meta-chars, split…
Input/Output
Redirecting the Standard Output of your
Program
You do it like this:
(let ((*standard-output* <some form generating a stream>))
...)
If the output of the program should go to a file, you can do the following:
(with-open-file (*standard-output* "somefile.dat"
:direction :output
:if-exists :supersede)
...)
CLISP
AllegroCL
LispWorks
Example
:external-format e)
(write-sequence
(loop with s = (make-string 256)
for i from 0 to 255
do (setf (char s i) (code-char i))
do (setf (char s i) (code char i))
finally (return s))
f)
(file-position f))))
#| CLISP
(check-256 *unicode-test-file*)
(progn (generate-256 :external-format :unix) (check-256))
; uses UTF-8 -> 385 bytes
Many functions will come from UIOP, so we suggest you have a look
directly at it:
UIOP/filesystem
UIOP/pathname
File extension
File basename
Parent directory
(uiop:pathname-parent-directory-pathname #P"/foo/bar/quux/")
;; => #P"/foo/bar/"
* (probe-file "/etc/passwd")
#p"/etc/passwd"
* (probe-file "foo")
#p"/etc/passwd"
* (probe-file "bar")
NIL
It also expand the tilde with files and directories that don’t exist:
(uiop:native-namestring "~/foo987.txt")
:: "/home/me/foo987.txt"
With files that exist, you can also use truename. But, at least on SBCL, it
returns an error if the path doesn’t exist.
Creating directories
This may create foo, bar and baz. Don’t forget the trailing slash.
Deleting directories
Use uiop:delete-directory-tree with a pathname (#p), a trailing slash
and the :validate key:
;; mkdir dirtest
(uiop:delete-directory-tree #p"dirtest/" :validate t)
Look at the difference: if you don’t include a trailing slash to either paths,
otherpath and projects are seen as files, so otherpath is appended to the
base directory containing projects:
(merge-pathnames "otherpath" "/home/vince/projects")
;; #P"/home/vince/otherpath"
;; ^^ no "projects", because it was seen as a file
Use uiop/os:getcwd:
(uiop/os:getcwd)
;; #P"/home/vince/projects/cl-cookbook/"
;; ^ with a trailing slash, u
Say you are working inside mysystem. It has an ASDF system declaration,
the system is loaded in your Lisp image. This ASDF file is somewhere on
your filesystem and you want the path to src/web/. Do this:
(asdf:system-relative-pathname "mysystem" "src/web/")
;; => #P"/home/vince/projects/mysystem/src/web/"
This will work on another user’s machine, where the system sources are
located in another location.
Or, to set for the current directory for the next operation only, use
uiop:with-current-directory:
(let ((dir "/path/to/another/directory/"))
(uiop:with-current-directory (dir)
(directory-files "./")))
Opening a file
Common Lisp has open and close functions which resemble the functions
of the same denominator from other programming languages you’re
probably familiar with. However, it is almost always recommendable to use
the macro with-open-file instead. Not only will this macro open the file
for you and close it when you’re done, it’ll also take care of it if your code
leaves the body abnormally (such as by a use of throw). A typical use of
with-open-file looks like this:
Reading files
It’s quite common to need to access the contents of a file in string form, or
to get a list of lines.
and
(uiop:read-file-lines "file.txt")
Sometimes you don’t control the internals of a library, so you’d better set
the default encoding to utf-8. Add this line to your ~/.sbclrc:
and optionally
read-line will read one line from a stream (which defaults to standard
input) the end of which is determined by either a newline character or the
end of the file. It will return this line as a string without the trailing newline
character. (Note that read-line has a second return value which is true if
there was no trailing newline, i.e. if the line was terminated by the end of
the file.) read-line will by default signal an error if the end of the file is
reached. You can inhibit this by supplying NIL as the second argument. If
you do this, read-line will return nil if it reaches the end of the file.
(with-open-file (stream "/etc/passwd")
(do ((line (read-line stream nil)
(read-line stream nil)))
((null line))
(print line)))
You can also supply a third argument which will be used instead of nil to
signal the end of the file:
(with-open-file (stream "/etc/passwd")
(loop for line = (read-line stream nil 'foo)
until (eq line 'foo)
do (print line)))
You can ‘look at’ the next character of a stream without actually removing
it from there - this is what the function peek-char is for. It can be used for
three different purposes depending on its first (optional) argument (the
second one being the stream it reads from): If the first argument is nil,
peek-char will just return the next character that’s waiting on the stream:
#\I
#\'
#\'
#\I
#\'
#\m
#\n
#\n
#\o
If the first argument to peek-char is a character, the function will skip all
characters until that particular character is found:
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char #\a stream))
(print (read-char stream))
(print (read-char stream))
(values))
#\I
#\a
#\a
#\m
Note that peek-char has further optional arguments to control its behaviour
on end-of-file similar to those for read-line and read-char (and it will
signal an error by default):
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (read-char stream))
(print (peek-char #\d stream))
(print (read-char stream))
(print (peek-char nil stream nil 'the-end))
(values))
#\I
#\d
#\d
THE-END
You can also put one character back onto the stream with the function
unread-char. You can use it as if, after you have read a character, you
decide that you’d better used peek-char instead of read-char:
CL-USER> (with-input-from-string (stream "I'm not amused")
(let ((c (read-char stream)))
(print c)
(unread-char c stream)
(print (read-char stream))
(values)))
#\I
#\I
Note that the front of a stream doesn’t behave like a stack: You can only put
back exactly one character onto the stream. Also, you must put back the
same character that has been read previously, and you can’t unread a
character if none has been read before.
Use the function file-position for random access to a file. If this function
is used with one argument (a stream), it will return the current position
within the stream. If it’s used with two arguments (see below), it will
actually change the file position in the stream.
CL-USER> (with-input-from-string (stream "I'm not amused")
(print (file-position stream))
(print (read-char stream))
(print (file-position stream))
(file-position stream 4)
(print (file-position stream))
(print (read-char stream))
(print (file-position stream))
(values))
0
#\I
1
4
#\n
5
If it doesn’t exist, you can :error out. See the standard for more details.
Using libraries
(ql:quickload “file-attributes”)
First get a stat object for a file, then get the stat you want:
CL-USER> (sb-posix:stat "test.txt")
#<SB-POSIX:STAT {10053FCBE3}>
CL-USER> (sb-posix:stat-mtime *)
1686671405
Some functions below return pathnames, so you might need the following:
(namestring #p"/foo/bar/baz.txt") ==> "/foo/bar/baz.tx
(directory-namestring #p"/foo/bar/baz.txt") ==> "/foo/bar/"
(file-namestring #p"/foo/bar/baz.txt") ==> "baz.txt"
(uiop:directory-files "./")
Listing sub-directories
(uiop:subdirectories "./")
(#P"/home/vince/projects/cl-cookbook/.git/"
#P"/home/vince/projects/cl-cookbook/.sass-cache/"
#P"/home/vince/projects/cl-cookbook/_includes/"
#P"/home/vince/projects/cl-cookbook/_layouts/"
#P"/home/vince/projects/cl-cookbook/_site/"
#P"/home/vince/projects/cl-cookbook/assets/")
Traversing (walking) directories recursively
a directory
a collectp function
a recursep function
a collector function
Given a directory, when collectp returns true with the directory, call the
collector function on the directory, and recurse each of its subdirectories
on which recursep returns true.
This function will thus let you traverse a filesystem hierarchy, superseding
the functionality of cl-fad:walk-directory.
Examples:
(uiop:collect-sub*directories "~/cl-cookbook"
(constantly t)
(constantly t)
(lambda (it) (push it *dirs*)))
and of course, we can use an external tool: the good ol’ unix find, or
the newer fd (fdfind on Debian) that has a simpler syntax and filters
out a set of common files and directories by default (node_modules,
.git…):
(str:lines (uiop:run-program (list "find" ".") :output :string))
;; or
(str:lines (uiop:run-program (list "fdfind") :output :string))
Below we simply list files of a directory and check that their name contains
a given string.
(remove-if-not (lambda (it)
(search "App" (namestring it)))
(uiop:directory-files "./"))
(#P"/home/vince/projects/cl-cookbook/AppendixA.jpg"
#P"/home/vince/projects/cl-cookbook/AppendixB.jpg"
#P"/home/vince/projects/cl-cookbook/AppendixC.jpg")
(directory #P"**/*.png")
The concept of . denoting the current directory does not exist in portable
Common Lisp. This may exist in specific filesystems and specific
implementations.
Also ~ to denote the home directory does not exist. They may be recognized
by some implementations as non-portable extensions.
What is a condition ?
z0ltan
Let’s dive into it step by step. More resources are given afterwards.
We get a welcome division-by-zero warning but the code runs well and it
returns two things: nil and the condition that was signaled. We could not
choose what to return.
Remember that we can inspect the condition with a right click in Slime.
handler-case VS handler-bind
handler-case is similar to the try/catch forms that we find in other
languages.
handler-bind (see the next examples), is what to use when we need absolute
control over what happens when a signal is raised. It allows us to use the
debugger and restarts, either interactively or programmatically.
If some library doesn’t catch all conditions and lets some bubble out to us,
we can see the restarts (established by restart-case) anywhere deep in the
stack, including restarts established by other libraries that this library called.
And we can see the stack trace, with every frame that was called and, in
some lisps, even see local variables and such. Once we handler-case, we
“forget” about this, everything is unwound. handler-bind does not rewind
the stack.
Before we properly see handler-bind, let’s study conditions and restarts.
(make-condition 'my-division-by-zero)
;; #<MY-DIVISION-BY-ZERO {1005A5FE43}>
Now when we’ll “signal” or “throw” the condition in our code we’ll be able
to populate it with information to be consumed later:
(make-condition 'my-division-by-zero :dividend 3)
;; #<MY-DIVISION-BY-ZERO {1005C18653}>
Note: here’s a quick reminder on classes, if you are not fully operational on
the Common Lisp Object System.
(make-condition 'my-division-by-zero :dividend 3)
;; ^^ this is the ":initarg"
and :reader dividend created a generic function that is a “getter” for the
dividend of a my-division-by-zero object:
(make-condition 'my-division-by-zero :dividend 3)
;; #<MY-DIVISION-BY-ZERO {1005C18653}>
(dividend *)
;; 3
So, the general form of define-condition looks and feels like a regular
class definition, but despite the similarities, conditions are not standard
objects.
Throwing these conditions will enter the interactive debugger, where the
user may select a restart.
warn will not enter the debugger (create warning conditions by subclassing
simple-warning).
Use signal if you do not want to enter the debugger, but you still want to
signal to the upper levels that something exceptional happened.
And that can be anything. For example, it can be used to track progress
during an operation. You would create a condition with a percent slot,
signal one when progress is made, and the higher level code would handle it
and display it to the user. See the resources below for more.
Conditions hierarchy
So far, when throwing our error, we saw this default text in the debugger:
Condition COMMON-LISP-USER::MY-DIVISION-BY-ZERO was signalled.
[Condition of type MY-DIVISION-BY-ZERO]
Now:
(error 'my-division-by-zero :dividend 3)
;; Debugger:
;;
;; You were going to divide 3 by zero.
;; [Condition of type MY-DIVISION-BY-ZERO]
Inspecting the stacktrace
That’s another quick reminder, not a Slime tutorial. In the debugger, you can
inspect the stacktrace, the arguments to the function calls, go to the
erroneous source line (with v in Slime), execute code in the context (e), etc.
Often, you can edit a buggy function, compile it (with the C-c C-c shortcut
in Slime), choose the “RETRY” restart and see your code pass.
By handling restarts we can start over the operation as if the error didn’t
occur (as seen in the stack).
(divide 3 0)
;; The assertion (NOT #1=(ZEROP Y)) failed with #1# = T.
;; [Condition of type SIMPLE-ERROR]
;;
;; Restarts:
;; 0: [CONTINUE] Retry assertion.
;; 1: [RETRY] Retry SLIME REPL evaluation request.
;; …
and when we choose it, we are prompted for a new value in the REPL:
The old value of Y is 0.
Do you want to supply a new value? (y or n) y
All this is good but we might want more custom choices. We can add
restarts on the top of the list by wrapping our function call inside restart-
case.
(defun divide-with-restarts (x y)
(restart-case (/ x y)
(return-zero () ;; <-- creates a new restart called "RETURN
0)
(divide-by-one ()
(/ x 1))))
(divide-with-restarts 3 0)
In case of any error (we’ll improve on that with handler-bind), we’ll get
those two new choices at the top of the debugger:
The two restarts we defined didn’t ask for a new value. To do this, we add
an :interactive lambda function to the restart, that asks for the user a new
value with the input method of its choice. Here, we’ll use the regular read.
(defun divide-with-restarts (x y)
(restart-case (/ x y)
(return-zero ()
:report "Return 0"
0)
(divide-by-one ()
:report "Divide by 1"
(/ x 1))
(set-new-divisor (value)
:report "Enter a new divisor"
;;
;; Ask the user for a new value:
:interactive (lambda () (prompt-new-value "Please enter a
;;
;; and call the divide function with the new value…
;; … possibly catching bad input again!
(divide-with-restarts x value))))
(divide-with-restarts 3 0)
When calling it, we are offered a new restart, we enter a new value, and we
get our result:
(divide-with-restarts 3 0)
;; Debugger:
;;
;; 2: [SET-NEW-DIVISOR] Enter a new divisor
;;
;; Please enter a new divisor: 10
;;
;; 3/10
Oh, you prefer a graphical user interface? We can use the zenity command
line interface on GNU/Linux.
(defun prompt-new-value (prompt)
(list
(let ((input
;; We capture the program's output to a string.
(with-output-to-string (s)
(let* ((*standard-output* s))
(uiop:run-program `("zenity"
"--forms"
,(format nil "--add-entry=~a"
:output s)))))
;; We get a string and we want a number.
;; We could also use parse-integer, the parse-number library
(read-from-string input))))
Now try again and you should get a little window asking for a new number:
That’s fun, but that’s not all. Choosing restarts manually is not always (or
often?) satisfactory. And by handling restarts we can start over the operation
as if the error didn’t occur, as seen in the stack.
(divide-and-handle-error 3 0)
;; Got error: arithmetic error DIVISION-BY-ZERO signalled
;; Operation was (/ 3 0).
;; and will divide by 1
;; 3
Use find-restart.
(restart-case
(return-zero ()
:test (lambda ()
(some-test))
...
Handling conditions (handler-bind)
We just saw a use for handler-bind.
If the handler returns normally (it declines to handle the condition), the
condition continues to bubble up, searching for another handler, and it will
find the interactive debugger (when it’s an error, not when it’s a simple
condition).
We can study a real example with the unix-opts library, that parses
command line arguments. It defined some conditions: unknown-option,
missing-arg and arg-parser-failed, and it is up to us to write what to do
in these cases.
(handler-bind ((opts:unknown-option #'unknown-option)
(opts:missing-arg #'missing-arg)
(opts:arg-parser-failed #'arg-parser-failed))
(opts:get-opts))
Conclusion
You’re now more than ready to write some code and to dive into other
resources!
Resources
Practical Common Lisp: “Beyond Exception Handling: Conditions and
Restarts” - the go-to tutorial, more explanations and primitives.
Common Lisp Recipes, chap. 12, by E. Weitz
language reference
Video tutorial: introduction on conditions and restarts, by Patrick Stein.
Condition Handling in the Lisp family of languages
z0ltan.wordpress.com (the article this recipe is heavily based upon)
See also
Algebraic effects - You can touch this ! - how to use conditions and
restarts to implement progress reporting and aborting of a long-running
calculation, possibly in an interactive or GUI context.
A tutorial on conditions and restarts, based around computing the roots
of a real function. It was presented by the author at a Bay Area Julia
meetup on may 2019 (talk slides here).
lisper.in - example with parsing a csv file and using restarts with
success, in a flight travel company.
https://github.com/svetlyak40wt/python-cl-conditions - implementation
of the CL conditions system in Python.
Packages
See: The Complete Idiot’s Guide to Common Lisp Packages
Creating a package
Here’s an example package definition. It takes a name, and you probably
want to :use the Common Lisp symbols and functions.
(defpackage :my-package
(:use :cl))
any new variable or function will be created in this package, aka in the
“namespace” of this package.
you can call all this package’s symbols directly, without using the
package prefix.
Just try!
We can also use in-package to try packages on the REPL. Note that on a
new Lisp REPL session, we are “inside” the CL-USER package. It is a
regular package.
Let’s show you an example. We open a new .lisp file and we create a new
package with a function inside our package:
;; in test-package.lisp
(defpackage :my-package
(:use :cl))
(in-package :my-package)
(defun hello ()
(print "Hello from my package."))
For example:
(str:concat …)
When the symbol is not exported (it is “private”), use a double colon:
(package::non-exported-symbol)
(my-package::hello)
But now, come back to the CL-USER package and try to call “hello”: we
get an error.
MY-PACKAGE> (in-package :cl-user)
#<PACKAGE "COMMON-LISP-USER">
CL-USER> (hello)
=> you get the interactive debugger that says:
(quit)
Exporting symbols
Augment our defpackage declaration to export our “hello” function like so:
(defpackage :my-package
(:use :cl)
(:export
#:hello))
Compile this (C-c C-c in Slime), and now you can call
CL-USER> (my-package:hello)
Observation:
You can import exactly the symbols you need with :import-from:
(defpackage :my-package
(:import-from :ppcre #:regex-replace)
(:use :cl))
Now you can call regex-replace from inside my-package, without the
ppcre package prefix. regex-replace is a new symbol inside your package.
It is not exported.
You can also use the import function from outside a package definition:
CL-USER> (import 'ppcre:regex-replace)
CL-USER> (regex-replace …)
Now you can access all variables, functions and macros of cl-ppcre from
your my-package package.
You can also use the use-package function:
CL-USER> (use-package 'cl-ppcre)
and now, all symbols that are exported by cl-ppcre (aka ppcre) are
available to use directly in your package. However, this should be
considered bad practice, unless you use another package of your project
that you control. Indeed, if the external package adds a symbol, it could
conflict with one of yours, or you could add one which will hide the
external symbol and you might not see a warning.
Package nickname
Package Local Nicknames (PLN)
To use a PLN you can simply do the following, for example, if you’d like to
try out a local nickname in an ad-hoc fashion:
(uiop:add-package-local-nickname :a :alexandria)
(a:iota 12) ; (0 1 2 3 4 5 6 7 8 9 10 11)
You can also set up a PLN in a defpackage form. The effect of PLN is
totally within mypackage i.e. the nickname won’t work in other packages
unless defined there too. So, you don’t have to worry about unintended
package name clash in other libraries.
(defpackage :mypackage
(:use :cl)
(:local-nicknames (:nickname :original-package-name)
(:alex :alexandria)
(:re :cl-ppcre)))
(in-package :mypackage)
Afterwards, a user may use a nickname instead of the package name to refer
to this package. For example:
(prove:run)
(cl-test-more:is)
(test-more:ok)
(defpackage :cl-ppcre
(:nicknames :ppcre)
Package locks
Restarts:
0: [CONTINUE] Ignore the package lock.
1: [IGNORE-ALL] Ignore all package locks in the context of
this operation.
2: [UNLOCK-PACKAGE] Unlock the package.
3: [RETRY] Retry SLIME REPL evaluation request.
4: [*ABORT] Return to SLIME's top level.
5: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING
{10047A8433}>)
...
See also
Package Local Nicknames in Common Lisp article.
Macros
The word macro is used generally in computer science to mean a syntactic
extension to a programming language. (Note: The name comes from the
word “macro-instruction,” which was a useful feature of many second-
generation assembly languages. A macro-instruction looked like a single
instruction, but expanded into a sequence of actual instructions. The basic
idea has since been used many times, notably in the C preprocessor. The
name “macro” is perhaps not ideal, since it connotes nothing relevant to
what it names, but we’re stuck with it.) Although many languages have a
macro facility, none of them are as powerful as Lisp’s. The basic
mechanism of Lisp macros is simple, but has subtle complexities, so
learning your way around it takes a bit of practice.
when z=8 then both x and y are set to 11. (I can’t think of any use for this,
but it’s just an example.)
Actually, this isn’t quite right, but it will do for now. A macro allows us to
do precisely this, by specifying a program for transforming the input pattern
(setq2 v1 v2 e) into the output pattern (progn ...).
Quote
We also use the quote, a special operator (not a function nor a macro, but
one of a few special operators forming the core of Lisp).
Macroexpand
More interestingly:
(macroexpand '(setq2 v1 v2 (+ z 3)))
;; (PROGN (SETQ V1 (+ z 3)) (SETQ V2 (+ z 3)))
;; T
We can confirm that our expression e, here (+ z 3), was not evaluated. We
will see how to control the evaluation of arguments with the comma: ,.
With Slime, you can call macroexpand by putting the cursor at the left of
the parenthesis of the s-expr to expand and call the functionM-x slime-
macroexpand-[1,all], or C-c M-m:
[|](setq2 v1 v2 3)
;^ cursor
; C-c M-m
; =>
; (PROGN (SETQ V1 3) (SETQ V2 3))
Another tip: on a macro name, type C-c C-w m (or M-x slime-who-
macroexpands) to get a new buffer with all the places where the macro was
expanded. Then type the usual C-c C-k (slime-compile-and-load-file)
to recompile all of them.
Macros VS functions
Evaluation context
This is all there is to it, except, of course, for the myriad subtle
consequences. The main consequence is that run time for the setq2 macro
is compile time for its context. That is, suppose the Lisp system is compiling
a function, and midway through it finds the expression (setq2 x y (+ z
3)). The job of the compiler is, of course, to translate source code into
something executable, such as machine language or perhaps byte code.
Hence it doesn’t execute the source code, but operates on it in various
mysterious ways. However, once the compiler sees the setq2 expression, it
must suddenly switch to executing the body of the setq2 macro. As I said,
this is an ordinary piece of Lisp code, which can in principle do anything
any other piece of Lisp code can do. That means that when the compiler is
running, the entire Lisp (run-time) system must be present.
We’ll stress this once more: at compile-time, you have the full language at
your disposal.
Novices often make the following sort of mistake. Suppose that the setq2
macro needs to do some complex transformation on its e argument before
plugging it into the result. Suppose this transformation can be written as a
Lisp procedure some-computation. The novice will often write:
(defmacro setq2 (v1 v2 e)
(let ((e1 (some-computation e)))
(list 'progn (list 'setq v1 e1) (list 'setq v2 e1))))
The mistake is to suppose that once a macro is called, the Lisp system
enters a “macro world,” so naturally everything in that world must be
defined using defmacro. This is the wrong picture. The right picture is that
defmacro enables a step into the ordinary Lisp world, but in which the
principal object of manipulation is Lisp code. Once that step is taken, one
uses ordinary Lisp function definitions:
(defmacro setq2 (v1 v2 e)
(let ((e1 (some-computation e)))
(list 'progn (list 'setq v1 e1) (list 'setq v2 e1))))
One possible explanation for this mistake may be that in other languages,
such as C, invoking a preprocessor macro does get you into a different
world; you can’t run an arbitrary C program. It might be worth pausing to
think about what it might mean to be able to.
Another subtle consequence is that we must spell out how the arguments to
the macro get distributed to the hypothetical behind-the-scenes function
(called setq2-function in my example). In most cases, it is easy to do so:
In defining a macro, we use all the usual lambda-list syntax, such as
&optional, &rest, &key, but what gets bound to the formal parameters are
pieces of the macro form, not their values (which are mostly unknown, this
being compile time for the macro form). So if we defined a macro thus:
(defmacro foo (x &optional y &key (cxt 'null)) ...)
then
if we call it with (foo a), the parameters’ values are: x=a, y=nil,
cxt=null.
calling (foo (+ a 1) (- y 1)) gives: x=(+ a 1), y=(- y 1),
cxt=null.
and (foo a b :cxt (zap zip)) gives: x=a, y=b, cxt=(zap zip).
Note that the values of the variables are the actual expressions (+ a 1) and
(zap zip). There is no requirement that these expressions’ values be
known, or even that they have values. The macro can do anything it likes
with them. For instance, here’s an even more useless variant of setq:
(setq-reversible e1 e2 d) behaves like (setq e1 e2) if d=:normal, and
behaves like (setq e2 e1) if d=:backward. It could be defined thus:
We’ll see the backquote and comma mechanism in the next section, but
here’s a fix:
(defmacro setq-reversible (v1 v2 direction)
(case direction
(:normal (list 'setq v1 v2))
(:backward (list 'setq v2 v1))
(t `(error "Unknown direction: ~a" ,direction))))
;; ^^ backquote ^^ comma: get the value i
The backquote (`) character signals that in the expression that follows,
every subexpression not preceded by a comma is to be quoted, and every
subexpression preceded by a comma is to be evaluated.
That’s mostly all there is to backquote. There are just two extra items to
point out.
Comma-splice ,@
First, if you write “,@e” instead of “,e” then the value of e is spliced (or
“joined”, “combined”, “interleaved”) into the result. So if v equals (oh
boy), then
evaluates to
(zap oh boy (oh boy))
;; ^^^^^ elements of v (two elements), spliced.
;; ^^ v itself (a list)
Quote-comma ’,
When we are inside a backquote context and we want to print an expression
literally, we have no choice but to use the combination of quote and
comma:
(defmacro explain-exp (exp)
`(format t "~S = ~S" ',exp ,exp))
;; ^^
(explain-exp (+ 2 3))
;; (+ 2 3) = 5
See by yourself:
;; Defmacro with no quote at all:
(defmacro explain-exp (exp)
(format t "~a = ~a" exp exp))
(explain-exp v1)
;; V1 = V1
Nested backquotes
One problem with backquote is that once you learn it you tend to use for
every list-building occasion. For instance, you might write
(mapcan (lambda (x)
(cond ((symbolp x) `((,x)))
((> x 10) `(,x ,x))
(t '())))
some-list)
then backquote may well treat (low) as if it were '(low); the list will be
allocated at load time, and every time the lambda is evaluated, that same
chunk of storage will be returned. So if we evaluate the expression with
some-list = (a 6 15), we will get ((a) low 15 15), but as a side effect
the constant (low) will get clobbered to become (low 15 15). If we then
evaluate the expression with, say, some-list = (8 oops), the result will be
(low 15 15 (oops)), and now the “constant” that started off as '(low)
will be (low 15 15 (oops)). (Note: The bug exemplified here takes other
forms, and has often bit newbies - as well as experienced programmers - in
the ass. The general form is that a constant list is produced as the value of
something that is later destructively altered. The first line of defense against
this bug is never to destructively alter any list. For newbies, this is also the
last line of defense. For those of us who imagine we’re more sophisticated,
the next line of defense is to think very carefully any time you use nconc or
mapcan).
To fix the bug, you can write (map 'list ...) instead of mapcan.
However, if you are determined to use mapcan, write the expression this
way:
(mapcan (lambda (x)
(cond ((symbolp x) (list `(,x)))
((> x 10) (list x x))
((>= x 0) (list 'low))
(t '())))
some-list)
If sk is being used as a stack, that is, it’s going to be popped in the normal
course of things, I would write (push x sk). If not, I would write (setq sk
(cons x sk)).
so that x will have value 10 and y will have value 12. Indeed, here’s its
macroexpansion:
(macroexpand '(setq2 x y (+ x 2)))
;;(PROGN (SETQ X (+ X 2)) (SETQ Y (+ X 2)))
Chances are that isn’t what the macro is expected to do (although you never
know). Another problematic case is (setq2 x y (pop l)), which causes l
to be popped twice; again, probably not right.
Gensym
Here gensym has returned the symbol #:g2003, which prints in this funny
way because it won’t be recognized by the reader. (Nor is there any need for
the reader to recognize it, since it exists only long enough for the code that
contains it to be compiled.)
Exercise: Verify that this new version works correctly for the case (setq2 x
y (pop l1)).
Exercise: Try writing the new version of the macro without using
backquote. If you can’t do it, you have done the exercise correctly, and
learned what backquote is for!
Before surveying what sorts of syntactic extensions I have found useful, let
me point out what sorts of syntactic extensions are generally not useful, or
best accomplished using means other than macros. Some novices think
macros are useful for open-coding functions. So, instead of defining
(defun sqone (x)
(let ((y (+ x 1))) (* y y)))
This is correct, but a waste of effort. For one thing, the amount of time
saved is almost certainly negligible. If it’s really important that sqone be
expanded inline, one can put (declaim (inline sqone)) before sqone is
defined (although the compiler is not obligated to honor this declaration).
For another, once sqone is defined as a macro, it becomes impossible to
write (mapcar #'sqone ll), or to do anything else with it except call it.
But macros have a thousand and one legitimate uses. Why write (lambda
(x) ...) when you can write (\\ (x) ...)? Just define \\ as a macro:
(defmacro \ (&rest list) `(lambda ,@list)).
Many people find mapcar and mapcan a bit too obscure, especially when
used with large lambda expressions. Rather than write something like
(mapcar (lambda (x)
(let ((y (hairy-fun1 x))
(z (hairy-fun2 x)))
(dolist (y1 y)
(dolist (z1 z)
_... and further meaningless_
_space-filling nonsense..._
))))
list)
It’s worth stopping for a second to discuss the role the keyword :in plays in
this macro. It serves as a sort of “local syntax marker,” in that it has no
meaning as far as Lisp is concerned, but does serve as a syntactic guidepost
for the macro itself. I will refer to these markers as guide symbols. (Here its
job may seem trivial, but if we generalized the for macro to allow multiple
list arguments and an implicit progn in the body the :ins would be crucial
in telling us where the arguments stopped and the body began.)
Second, notice that I wrote (eq (second listspec) ':in) in the macro
definition to check for the presence of the guide symbol. If I had used in
instead, I would have had to think about which package my in lives in and
which package the macro user’s in lives in. One way to avoid trouble
would be to write
(and (symbolp (second listspec))
(eq (intern (symbol-name (second listspec))
:keyword)
':in))
Another would be to write
(and (symbolp (second listspec))
(string= (symbol-name (second listspec)) (symbol-name 'in))
(defun make-a-zap (u v w)
(vector 3 'zap u v w))
(defun test-whether-zap (x) ...)
(defun zap-copy (x) ...)
(defun zap-deactivate (x) ...)
(defun make-a-zep ()
(vector 0 'zep))
(defun test-whether-zep (x) ...)
(defun zep-copy (x) ...)
(defun zep-deactivate (x) ...)
Where the omitted pieces are the same in all similarly named functions.
(That is, the “…” in zep-deactivate is the same code as the “…” in zip-
deactivate, and so forth.) Here, for the sake of concreteness, if not
plausibility, zip, zap, and zep are behaving like odd little data structures.
The functions could be rather large, and it would get tedious keeping them
all in sync as they are debugged. An alternative would be to use a macro:
(defmacro odd-define (name buildargs)
`(progn (defun ,(build-symbol make-a- (:< name))
,buildargs
(vector ,(length buildargs) ',name ,@buildargs))
(defun ,(build-symbol test-whether- (:< name)) (x)
(and (vectorp x) (eq (aref x 1) ',name))
(defun ,(build-symbol (:< name) -copy) (x)
...)
(defun ,(build-symbol (:< name) -deactivate) (x)
...))))
If all the uses of this macro are collected in this one place, it might be
clearer to make it a local macro using macrolet:
(macrolet ((odd-define (name buildargs)
`(progn
(defun ,(build-symbol make-a- (:< name))
,buildargs
(vector ,(length buildargs)
',name
,@buildargs))
(defun ,(build-symbol test-whether- (:< name))
(x)
(and (vectorp x) (eq (aref x 1) ',name))
(defun ,(build-symbol (:< name) -copy) (x)
...)
(defun ,(build-symbol (:< name) -deactivate) (
...)))))
(odd-define zip (y z))
(odd-define zap (u v w))
(odd-define zep ()))
(fi odd-define)
(ODD-DEFINE ZIP (U V W))
(ex)
(PROGN (DEFUN MAKE-A-ZIP (U V W) ...)
...)
See also
A gentle introduction to Compile-Time Computing — Part 1
The following video, from the series “Little bits of Lisp” by cbaggers,
is a two hours long talk on macros, showing simple to advanced
concepts such as compiler macros: https://www.youtube.com/watch?
v=ygKXeLKhiTI It also shows how to manipulate macros (and their
expansion) in Emacs.
the article “Reader macros in Common Lisp”: https://lisper.in/reader-
macros
Fundamentals of CLOS
CLOS is the “Common Lisp Object System”, arguably one of the most
powerful object systems available in any language.
The functionality belonging to this name was added to the Common Lisp
language between the publication of Steele’s first edition of “Common Lisp,
the Language” in 1984 and the formalization of the language as an ANSI
standard ten years later.
This page aims to give a good understanding of how to use CLOS, but only
a brief introduction to the MOP.
(name p1)
;;^^^ accessor
;; => "me"
(lisper p1)
;; => nil
;; ^^ initform (slot unbound by default)
The macro used for defining new data types in CLOS is defclass.
This gives us a CLOS type (or class) called person and two slots, named
name and lisper.
(class-of p1)
#<STANDARD-CLASS PERSON>
(type-of p1)
PERSON
So, our person class doesn’t explicitly inherit from another class (it gets the
empty parentheses ()). However it still inherits by default from the class t
and from standard-object. See below under “inheritance”.
We could write a minimal class definition without slot options like this:
(defclass point ()
(x y z))
This has the direct advantage that you can control the required arguments.
You should now export the constructor from your package and not the class
itself.
Slots
Given our point class above, which didn’t define any slot accessors:
(defvar pt (make-instance 'point))
(inspect pt)
The object is a STANDARD-OBJECT of type POINT.
0. X: "unbound"
1. Y: "unbound"
2. Z: "unbound"
We got an object of type POINT, but slots are unbound by default: trying
to access them will raise an UNBOUND-SLOT condition:
(slot-value pt 'x) ;; => condition: the slot is unbound
slot-value is setf-able:
(setf (slot-value pt 'x) 1)
(slot-value pt 'x) ;; => 1
(type-of #'name)
STANDARD-GENERIC-FUNCTION
:reader and :writer do what you expect. Only the :writer is setf-
able.
If you don’t specify any of these, you can still use slot-value.
You can give a slot more than one :accessor, :reader or :initarg.
or
(with-slots ((n name)
(l lisper))
c1
(format t "got ~a, ~a~&" n l))
a shared slot will always be equal for all instances of the class. We set
it with :allocation :class.
In the following example, note how changing the value of the class slot
species of p2 affects all instances of the class (whether or not those
instances exist yet).
(defclass person ()
((name :initarg :name :accessor name)
(species
:initform 'homo-sapiens
:accessor species
:allocation :class)))
(species p1)
(species p2)
;; HOMO-SAPIENS
(setf (species p2) 'homo-numericus)
;; HOMO-NUMERICUS
(species p1)
;; HOMO-NUMERICUS
Slot documentation
Slot type
The :type slot option may not do the job you expect it does. If you are new
to the CLOS, we suggest you skip this section and use your own
constructors to manually check slot types.
Indeed, whether slot types are being checked or not is undefined. See the
Hyperspec.
Few implementations will do it. Clozure CL does it, SBCL does it since its
version 1.5.9 (November, 2019) or when safety is high ((declaim
(optimise safety))).
(class-of my-point)
;; #<STANDARD-CLASS POINT 275B78DC>
CLOS classes are also instances of a CLOS class, and we can find out what
that class is, as in the example below:
(class-of (class-of my-point))
;; #<STANDARD-CLASS STANDARD-CLASS 20306534>
Note: this is your first introduction to the MOP. You don’t need that to get
started !
The object my-point is an instance of the class named point, and the class
named point is itself an instance of the class named standard-class. We
say that the class named standard-class is the metaclass (i.e. the class of
the class) of my-point. We can make good uses of metaclasses, as we’ll see
later.
Subclasses and inheritance
(ql:quickload "closer-mop")
;; ...
A subclass inherits all of its parents’ slots, and it can override any of their
slot options. Common Lisp makes this process dynamic, great for REPL
session, and we can even control parts of it (like, do something when a
given slot is removed/updated/added, etc).
:initform: we get the most specific default initial value form, i.e. the
first :initform for that slot in the precedence list.
Last but not least, be warned that inheritance is fairly easy to misuse, and
multiple inheritance is multiply so, so please take a little care. Ask yourself
whether foo really wants to inherit from bar, or whether instances of foo
want a slot containing a bar. A good general guide is that if foo and bar are
“same sort of thing” then it’s correct to mix them together by inheritance,
but if they’re really separate concepts then you should use slots to keep
them apart.
Multiple inheritance
The first class on the list of parent classes is the most specific one, child’s
slots will take precedence over the person’s. Note that both child and
person have to be defined prior to defining baby in this example.
We’ll gloss over the details. Suffice it to say that everything’s configurable
by implementing methods exposed by the MOP.
To redefine a class, simply evaluate a new defclass form. This then takes
the place of the old definition, the existing class object is updated, and all
instances of the class (and, recursively, its subclasses) are lazily updated
to reflect the new definition. You don’t have to recompile anything other
than the new defclass, nor to invalidate any of your objects. Think about it
for a second: this is awesome !
(defclass person ()
(defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform t ;; <-- from nil to t
:accessor lisper)))
(lisper p1)
;; NIL (of course!)
(defclass person ()
((name
:initarg :name
:accessor name)
(lisper
:initform nil
:accessor lisper)
(age ;; <-- new slot
:initarg :arg
:initform 18 ;; <-- default value
:accessor age)))
(age p1)
;; => 18. Correct. This is the default initform for this new slo
(slot-value p1 'bwarf)
;; => "the slot bwarf is missing from the object #<person…>"
(defclass person ()
((name
:initarg :name
:accessor name)))
(class-of p1)
;; #<STANDARD-CLASS CHILD>
(can-walk-p p1)
;; T
Pretty printing
#<PERSON {1006234593}>
It gives:
p1
;; #<PERSON me, lisper: T>
We used the with-accessors macro, but of course for simple cases this is
enough:
(defmethod print-object ((obj person) stream)
(print-unreadable-object (obj stream :type t)
(format stream "~a, lisper: ~a" (name obj) (lisper obj))))
Caution: trying to access a slot that is not bound by default will lead to an
error. Use slot-boundp.
Generously, the functions introduced in the last section also work on lisp
objects which are not CLOS instances:
(find-class 'symbol)
;; #<BUILT-IN-CLASS SYMBOL>
(class-name *)
;; SYMBOL
(eq ** (class-of 'symbol))
;; T
(class-of ***)
;; #<STANDARD-CLASS BUILT-IN-CLASS>
We see here that symbols are instances of the system class symbol. This is
one of 75 cases in which the language requires a class to exist with the same
name as the corresponding lisp type. Many of these cases are concerned
with CLOS itself (for example, the correspondence between the type
standard-class and the CLOS class of that name) or with the condition
system (which might or might not be built using CLOS classes in any given
implementation). However, 33 correspondences remain relating to
“traditional” lisp types:
Note that not all “traditional” lisp types are included in this list. (Consider:
atom, fixnum, short-float, and any type not denoted by a symbol.)
(class-of (make-foo))
;; #<STRUCTURE-CLASS FOO 21DE8714>
Introspection
Your best option is to discover the closer-mop library and to keep the CLOS
& MOP specifications at hand.
More functions:
closer-mop:class-default-initargs
closer-mop:class-direct-default-initargs
closer-mop:class-direct-slots
closer-mop:class-direct-subclasses
closer-mop:class-direct-superclasses
closer-mop:class-precedence-list
closer-mop:class-slots
closer-mop:classp
closer-mop:extract-lambda-list
closer-mop:extract-specializer-names
closer-mop:generic-function-argument-precedence-order
closer-mop:generic-function-declarations
closer-mop:generic-function-lambda-list
closer-mop:generic-function-method-class
closer-mop:generic-function-method-combination
closer-mop:generic-function-methods
closer-mop:generic-function-name
closer-mop:method-combination
closer-mop:method-function
closer-mop:method-generic-function
closer-mop:method-lambda-list
closer-mop:method-specializers
closer-mop:slot-definition
closer-mop:slot-definition-allocation
closer-mop:slot-definition-initargs
closer-mop:slot-definition-initform
closer-mop:slot-definition-initfunction
closer-mop:slot-definition-location
closer-mop:slot-definition-name
closer-mop:slot-definition-readers
closer-mop:slot-definition-type
closer-mop:slot-definition-writers
closer-mop:specializer-direct-generic-functions
closer-mop:specializer-direct-methods
closer-mop:standard-accessor-method
See also
This:
(defclass/std example ()
((slot1 slot2 slot3)))
expands to:
(defclass example ()
((slot1
:accessor slot1
:initarg :slot1
:initform nil)
(slot2
:accessor slot2
:initarg :slot2
:initform nil)
(slot3
:accessor slot3
:initarg :slot3
:initform nil)))
It does much more and it is very flexible, however it is seldom used by the
Common Lisp community: use at your own risk©.
Methods
Diving in
(greet :anything)
;; Are you a person ? You are a KEYWORD.
;; NIL
(greet p1)
;; Are you a person ? You are a PERSON.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Method combination: before, after, around.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(greet p1)
;; -- before person
;; Hello me
;; e o e
;; call-next-method
;;;;;;;;;;;;;;;;;
;; Adding in &key
;;;;;;;;;;;;;;;;;
(greet p1 :talkative t) ;; ok
(greet p1 :foo t) ;; still ok
;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;
;;; Specializers
;;;;;;;;;;;;;;;;
(feed c1 :dessert)
;; mmh, dessert !
(feed p1 :soup)
;; eating
(feed c1 :soup)
;; bwark
The required parameters in the method’s lambda list may take one of the
following three forms:
1- a simple variable:
(defmethod greet (foo)
...)
In this case, the variable foo is bound to the corresponding argument only if
that argument is of specializer class person or a subclass, like child
(indeed, a “child” is also a “person”).
If any argument fails to match its specializer then the method is not
applicable and it cannot be executed with those arguments.We’ll get an
error message like “there is no applicable method for the generic function
xxx when called with arguments yyy”.
Only required parameters can be specialized. We can’t specialize on
optional &key arguments.
(feed c1 :soup)
;; "bwark"
In place of a simple symbol (:soup), the eql specializer can be any lisp
form. It is evaluated at the same time of the defmethod.
You can define any number of methods with the same function name but
with different specializers, as long as the form of the lambda list is
congruent with the shape of the generic function. The system chooses the
most specific applicable method and executes its body. The most specific
method is the one whose specializers are nearest to the head of the class-
precedence-list of the argument (classes on the left of the lambda list are
more specific). A method with specializers is more specific to one without
any.
Notes:
All the methods with the same function name belong to the same
generic function.
All slot accessors and readers defined by defclass are methods. They
can override or be overridden by other methods on the same generic
function.
Multimethods
no arguments, in which case the next method will receive exactly the
same arguments as this method did, or
For example:
(defmethod greet ((obj child))
(format t "ur so cute~&")
(when (next-method-p)
(call-next-method)))
;; STYLE-WARNING: REDEFINING GREET (#<STANDARD-CLASS CHILD>) in
;; #<STANDARD-METHOD GREET (child) {1003D3DB43}>
(greet c1)
;; ur so cute
;; Hello Alice !
Note finally that the body of every method establishes a block with the
same name as the method’s generic function. If you return-from that name
you are exiting the current method, not the call to the enclosing generic
function.
Method qualifiers (before, after, around)
In our “Diving in” examples, we saw some use of the :before, :after and
:around qualifiers:
The generic function returns the value of the primary method. Any
values of the before or after methods are ignored. They are used for their
side effects.
And then we have around-methods. They are wrappers around the core
mechanism we just described. They can be useful to catch return values or
to set up an environment around the primary method (set up a catch, a lock,
timing an execution,…).
a. run all the :before methods, in order, ignoring any return values
and not permitting calls to call-next-method or next-method-p;
e. after the primary method(s) have completed, run all the :after
methods, in reverse order, ignoring any return values and not
permitting calls to call-next-method or next-method-p.
The default method combination type we just saw is named standard, but
other method combination types are available, and no need to say that you
can define your own.
The built-in types are:
You notice that these types are named after a lisp operator. Indeed, what
they do is they define a framework that combines the applicable primary
methods inside a call to the lisp operator of that name. For example, using
the progn combination type is equivalent to calling all the primary methods
one after the other:
(progn
(method-1 args)
(method-2 args)
(method-3 args))
Here, unlike the standard mechanism, all the primary methods applicable
for a given object are called, the most specific first.
(dishes c1)
;; - bring the baby dishes
;; - bring a person's dishes
;; - clean and dry.
(greet c1)
;; ur so cute --> only the most applicable method was called.
Similarly, using the list type is equivalent to returning the list of the values
of the methods.
(list
(method-1 args)
(method-2 args)
(method-3 args))
(tidy c1)
;; (:toys :books :foo)
(tidy c1)
;; I'm going to clean up (toys book foo)
;; that's too much !
Note that these operators don’t support before, after and around methods
(indeed, there is no room for them anymore). They do support around
methods, where call-next-method is allowed, but they don’t support
calling call-next-method in the primary methods (it would indeed be
redundant since all primary methods are called, or clunky to not call one).
In SBCL, we can use (trace foo :methods t). See this post by an SBCL
core developer.
(foo 2.0d0)
0: (FOO 2.0d0)
1: ((SB-PCL::COMBINED-METHOD FOO) 2.0d0)
2: ((METHOD FOO (FLOAT)) 2.0d0)
3: ((METHOD FOO (T)) 2.0d0)
3: (METHOD FOO (T)) returned 3
2: (METHOD FOO (FLOAT)) returned 9
2: ((METHOD FOO :AFTER (DOUBLE-FLOAT)) 2.0d0)
2: (METHOD FOO :AFTER (DOUBLE-FLOAT)) returned DOUBLE
1: (SB-PCL::COMBINED-METHOD FOO) returned 9
0: FOO returned 9
9
Now, later in your work session, you decide that you don’t need the one
specializing on child any more. You delete its source code. But the
method still exists in the image. You have to programmatically remove the
method, see below.
Had you used defgeneric, all the methods would have been updated, added
or deleted. We have defined the tidy generic function already with three
methods:
(defgeneric tidy (obj)
(:method-combination list)
(:method list (obj)
:foo)
(:method list ((obj person))
:books)
(:method list ((obj child))
:toys))
It works for any object type, a person or a child. Try it on a string: (tidy
"tidy what?"), it works.
Removing a method
You could use (fmakunbound 'goodbye), but this makes all methods
unbound.
MOP
We gather here some examples that make use of the framework provided by
the meta-object protocol, the configurable object system that rules Lisp’s
object system. We touch advanced concepts so, new reader, don’t worry:
you don’t need to understand this section to start using the Common Lisp
Object System.
We won’t explain much about the MOP here, but hopefully sufficiently to
make you see its possibilities or to help you understand how some CL
libraries are built. We invite you to read the books referenced in the
introduction.
Metaclasses
But we’ll change it to one of our own, so that we’ll be able to count the
creation of instances. This same mechanism could be used to auto
increment the primary key of a database system (this is how the Postmodern
or Mito libraries do), to log the creation of objects, etc.
(unintern 'person)
;; this is necessary to change the metaclass of person.
;; or (setf (find-class 'person) nil)
;; https://stackoverflow.com/questions/38811931/how-to-change-cl
(defclass person ()
((name
:initarg :name
:accessor name))
(:metaclass counted-class)) ;; <- metaclass
;; #<COUNTED-CLASS PERSON>
;; ^^^ not standard-class anymore.
See that an :after qualifier is the safest choice, we let the standard method
run as usual and return a new instance.
Now testing:
(defvar p3 (make-instance 'person :name "adam"))
#<PERSON {1007A8F5B3}>
(slot-value p3 'counter)
;; => error. No, our new slot isn't on the person class.
(slot-value (find-class 'person) 'counter)
;; 1
It’s working.
A typical example would be to validate the initial values. Here we’ll check
that the person’s name is longer than 3 characters:
(defmethod initialize-instance :after ((obj person) &key)
(with-slots (name) obj
(assert (>= (length name) 3))))
So while we’re at it, here’s an assertion that uses the debugger features to
offer to change “name”. We give assert a list of places that can be changed
from the debugger:
(defmethod INITIALIZE-INSTANCE :after ((obj person) &key)
(with-slots (name) obj
(assert (>= (length name) 3)
(name) ;; <-- list of places
"The value of name is ~a. It should be longer than 3
We get:
The value of name is me. It should be longer than 3 characters.
[Condition of type SIMPLE-ERROR]
Restarts:
0: [CONTINUE] Retry assertion with new value for NAME.
^^^^^^^^^^^^ our new restart
1: [RETRY] Retry SLIME REPL evaluation request.
2: [*ABORT] Return to SLIME's top level.
Suppose you created a “circle” class, with coordinates and a diameter. Later
on, you decide to replace the diameter by a radius. You want all the existing
objects to be cleverly updated: the radius should have the diameter value,
divided by 2. Use update-instance-for-redefined-class.
We actually don’t call the method direcly, but we use a :before method:
(defmethod update-instance-for-redefined-class
:before ((obj circle) added deleted plist-values &key)
(format t "plist values: ~a~&" plist-values)
(let ((diameter (getf plist-values 'diameter)))
(setf (radius obj) (/ diameter 2))))
Nothing happens yet, you don’t see the output of our “plist values” print.
Inspect or describe the object: now it will be updated, and you’ll find the
radius slot.
Now imagine you are working with the circle class, but you realize you
only need a surface kind of objects. You will discard the circle class
altogether, but you want your existing objects to be updated -to this new
class, and compute new slots intelligently. Use update-instance-for-
different-class.
This implies a fact that in Common Lisp objects have types, while
variables do not. This might be surprising at first if you come from a C/C++
background.
For example:
(defvar *var* 1234)
*VAR*
(type-of *var*)
(INTEGER 0 4611686018427387903)
The function type-of returns the type of the given object. The returned
result is a type-specifier. In this case the first element is the type and the
remaining part is extra information (lower and upper bound) of that type.
You can safely ignore it for now. Also remember that integers in Lisp have
no limit!
* (type-of *var*)
(SIMPLE-ARRAY CHARACTER (5))
Type Hierarchy
The inheritance relationship of Lisp types consists a type graph and the root
of all types is T. For example:
* (describe 'integer)
COMMON-LISP:INTEGER
[symbol]
The function describe shows that the symbol integer is a primitive type-
specifier that has optional information lower bound and upper bound.
Meanwhile, it is a built-in class. But why?
Most common Lisp types are implemented as CLOS classes. Some types
are simply “wrappers” of other types. Each CLOS class maps to a
corresponding type. In Lisp types are referred to indirectly by the use of
type specifiers.
There are some differences between the function type-of and class-of.
The function type-of returns the type of a given object in type specifier
format while class-of returns the implementation details.
* (type-of 1234)
(INTEGER 0 4611686018427387903)
* (class-of 1234)
#<BUILT-IN-CLASS COMMON-LISP:FIXNUM>
Checking Types
The function typep can be used to check if the first argument is of the given
type specified by the second argument.
* (typep 1234 'integer)
T
The function subtypep can be used to inspect if a type inherits from the
another one. It returns 2 values:
For example:
* (subtypep 'integer 'number)
T
T
Sometimes you may want to perform different actions according to the type
of an argument. The macro typecase is your friend:
* (defun plus1 (arg)
(typecase arg
(integer (+ arg 1))
(string (concatenate 'string arg "1"))
(t 'error)))
PLUS1
* (plus1 100)
101 (7 bits, #x65, #o145, #b1100101)
* (plus1 "hello")
"hello1"
* (plus1 'hello)
ERROR
Type Specifier
A type specifier is a form specifying a type. As mentioned above, returning
value of the function type-of and the second argument of typep are both
type specifiers.
Here the complementary information of the type vector is its elements type
and size respectively.
As you may have guessed, the type specifier above can be shortened as
following:
* (typep '#(1 2 3) 'vector)
T
Its body should be a macro checking whether given argument is of this type
(see defmacro).
Now let us define a new data type. The data type should be a array with at
most 10 elements. Also each element should be a number smaller than 10.
See following code for an example:
* (defun small-number-array-p (thing)
(and (arrayp thing)
(<= (length thing) 10)
(every #'numberp thing)
(every (lambda (x) (< x 10)) thing)))
* (plus1 1)
2 (2 bits, #x2, #o2, #b10)
* (plus1 "hello")
; Debugger entered on #<SIMPLE-TYPE-ERROR expected-type: NUMBER
However, similar to the :type slot introduced in CLOS section, the effects
of type declarations are undefined in Lisp standard and are implementation
specific. So there is no guarantee that the Lisp compiler will perform
compile-time type checking.
Let’s recall first that Lisp already warns about simple type warnings. The
following function wrongly wants to concatenate a string and a number.
When we compile it, we get a type warning.
(defconstant +foo+ 3)
(defun bar ()
(concatenate 'string "+" +foo+))
; caught WARNING:
; Constant 3 conflicts with its asserted type SEQUENCE.
; See also:
; The SBCL Manual, Node "Handling of Types"
Use the macro declaim with a type declaration identifier (other identifiers
are "ftype, inline, notinline, optimize…).
Let’s declare that our global variable *name* is a string. You can type the
following in any order in the REPL:
(declaim (type (string) *name*))
(defparameter *name* "book")
We can do the same with our custom types. Let’s quickly declare the type
list-of-strings:
(deftype list-of-strings ()
`(satisfies list-of-strings-p))
Composing types
We use again the declaim macro, with ftype (function …) instead of just
type:
(declaim (ftype (function (fixnum) fixnum) add))
;; ^^input ^^output [optional]
(defun add (n)
(+ n 1))
If we use add inside another function, to a place that expects a string, we get
a warning:
(defun bad-concat (n)
(concatenate 'string (add n)))
; caught WARNING:
; Derived type of (ADD N) is
; (VALUES FIXNUM &REST T),
; conflicting with its asserted type
; SEQUENCE.
If we use add inside another function, and that function declares its
argument types which appear to be incompatible with those of add, we get a
warning:
(declaim (ftype (function (string)) bad-arg))
(defun bad-arg (n)
(add n))
; caught WARNING:
; Derived type of N is
; (VALUES STRING &OPTIONAL),
; conflicting with its asserted type
; FIXNUM.
This all happens indeed at compile time, either in the REPL, either with a
simple C-c C-c in Slime, or when we load a file.
For example:
(declaim (ftype (function (string &key (:n integer))) foo)) (defun foo (bar
&key n) …)
In the following, we declare a fruit type and we write a function that uses a
single fruit argument, so compiling placing-order gives us a type warning
as expected:
(deftype fruit () '(member :apple :orange :pear))
(defun placing-order ()
(one-order :bacon))
But in this version, we use &rest parameters, and we don’t have a type
warning anymore:
(declaim (ftype (function (&rest fruit)) place-order))
(defun place-order (&rest selections)
(dolist (s selections)
(format t "Ordering ~S~%" s)))
(defun placing-orders ()
(place-order :orange :apple :bacon)) ;; => no type warning
The declaration is correct, but our compiler doesn’t check it. A well-placed
declare gives us the compile-time warning back:
(defun placing-orders ()
(place-order :orange :apple :bacon))
=>
The value
:BACON
is not of type
(MEMBER :PEAR :ORANGE :APPLE)
A class slot accepts a :type slot option. It is however generally not used to
check the type of the initform. SBCL, starting with version 1.5.9 released
on november 2019, now gives those warnings, meaning that this:
(defclass foo ()
((name :type number :initform "17")))
It also allows:
Limitations
Complex types involving satisfies are not checked inside a function body
by default, only at its boundaries. Even if it does a lot, SBCL doesn’t do as
much as a statically typed language.
However, if we had the problematic line at the function’s boundary we’d get
the warning:
(defun bad-adder ()
(let ((res 10))
(loop for name in '("alice")
return (incf res name))))
; in: DEFUN BAD-ADDER
; (SB-INT:NAMED-LAMBDA BAD-ADDER
; NIL
; (BLOCK BAD-ADDER
; (LET ((RES 10))
; (LOOP FOR NAME IN *ALL-NAMES* RETURN (INCF RES NAME)
;
; caught WARNING:
; Derived type of ("a hairy form" NIL (SETQ RES (+ NAME RES)))
; (VALUES (OR NULL NUMBER) &OPTIONAL),
; conflicting with the declared function return type
; (VALUES STRING &REST T).
We could also use a the declaration in the loop body to get a compile-time
warning:
do (incf res (the string name)))
What can we conclude? This is yet another reason to decompose your code
into small functions.
See also
the article Static type checking in SBCL, by Martin Cracauer
the article Typed List, a Primer - let’s explore Lisp’s fine-grained type
hierarchy! with a shallow comparison to Haskell.
the Coalton library: an efficient, statically typed functional
programming language that supercharges Common Lisp. It is as an
embedded DSL in Lisp that resembles Haskell or Standard ML, but
lets you seamlessly interoperate with non-statically-typed Lisp code
(and vice versa).
exhaustiveness type checking at compile-time with Serapeum for
enum types and union types (ecase-of, etypecase-of).
TCP/IP
As usual, we will use quicklisp to load usocket.
(ql:quickload “usocket”)
Now we need to create a server. There are 2 primary functions that we need
to call. usocket:socket-listen and usocket:socket-accept.
Mistake 2 - You need to close both the new socket and the server socket.
Again this is pretty obvious but since my initial code was only closing the
connection, I kept running into a socket in use problem. Of course one more
option is to reuse the socket when we listen.
Once you get past these mistakes, it’s pretty easy to do the rest. Close the
connections and the server socket and boom you are done!
(defun create-server (port)
(let* ((socket (usocket:socket-listen "127.0.0.1" port))
(connection (usocket:socket-accept socket :element-type
'character)))
(unwind-protect
(progn
(format (usocket:socket-stream connection)
"Hello World~%")
(force-output (usocket:socket-stream connection)))
(progn
(format t "Closing sockets~%")
(usocket:socket-close connection)
(usocket:socket-close socket)))))
Now for the client. This part is easy. Just connect to the server port and you
should be able to read from the server. The only silly mistake I made here
was to use read and not read-line. So, I ended up seeing only a “Hello” from
the server. I went for a walk and came back to find the issue and fix it.
(defun create-client (port)
(usocket:with-client-socket (socket stream "127.0.0.1" port
:element-type 'character)
(unwind-protect
(progn
(usocket:wait-for-input socket)
(format t "Input is: ~a~%" (read-line stream)))
(usocket:socket-close socket))))
So, how do you run this? You need two REPLs, one for the server and one
for the client. Load this file in both REPLs. Create the server in the first
REPL.
(create-server 12321)
Now you are ready to run the client on the second REPL
(create-client 12321)
UDP/IP
As a protocol, UDP is connection-less, and therefore there is no concept of
binding and accepting a connection. Instead we only do a socket-connect
but pass a specific set of parameters to make sure that we create an UDP
socket that’s waiting for data on a particular port.
So, what were the problems I faced due to my mistakes? Mistake 1 - Unlike
TCP, you don’t pass host and port to socket-connect. If you do that, then
you are indicating that you want to send a packet. Instead, you pass nil but
you set :local-host and :local-port to the address and port that you
want to receive data on. This part took some time to figure out, because the
documentation didn’t cover it. Instead reading a bit of code from
blackthorn-engine-3d helped a lot.
Also, since UDP is connectionless, anyone can send data to it at any time.
So, we need to know which host/port did we get data from so that we can
respond on it. So we bind multiple values to socket-receive and use those
values to send back data to our peer “client”.
(defun create-server (port buffer)
(let* ((socket (usocket:socket-connect nil nil
:protocol :datagram
:element-type '(unsigned-byte 8)
:local-host "127.0.0.1"
:local-port port)))
(unwind-protect
(multiple-value-bind (buffer size client receive-port)
(usocket:socket-receive socket buffer 8)
(format t "~A~%" buffer)
(usocket:socket-send socket (reverse buffer) size
:port receive-port
:host client))
(usocket:socket-close socket))))
Now for the sender/receiver. This part is pretty easy. Create a socket, send
data on it and receive data back.
(defun create-client (port buffer)
(let ((socket (usocket:socket-connect "127.0.0.1" port
:protocol :datagram
:element-type '(unsigned-byte 8))))
(unwind-protect
(progn
(format t "Sending data~%")
(replace buffer #(1 2 3 4 5 6 7 8))
(format t "Receiving data~%")
(usocket:socket-send socket buffer 8)
(usocket:socket-receive socket buffer 8)
(format t "~A~%" buffer))
(usocket:socket-close socket))))
So, how do you run this? You need again two REPLs, one for the server and
one for the client. Load this file in both REPLs. Create the server in the first
REPL.
Now you are ready to run the client on the second REPL
Voilà! You should see a vector #(1 2 3 4 5 6 7 8) on the first REPL and
#(8 7 6 5 4 3 2 1) on the second one.
Credit
This guide originally comes from shortsightedsid
Interfacing with your OS
The ANSI Common Lisp standard doesn’t mention this topic. (Keep in
mind that it was written at a time where Lisp Machines were at their peak.
On these boxes Lisp was your operating system!) So almost everything that
can be said here depends on your OS and your implementation. There are,
however, some widely used libraries, which either come with your
Common Lisp implementation, or are easily available through Quicklisp.
These include:
You should also note that some of these implementations also provide the
ability to set these variables. These include ECL (si:setenv) and
AllegroCL, LispWorks, and CLISP where you can use the functions from
above together with setf. This feature might be important if you want to
start subprocesses from your Lisp environment.
Also note that the Osicat library has the method (environment-variable
"name"), on POSIX-like systems including Windows. It is also fset-able.
$ sbcl my-command-line-arg
….
* sb-ext:*posix-argv*
("sbcl" "my-command-line-arg")
*
More on using this to write standalone Lisp scripts can be found in the
SBCL Manual
Here’s a quick function to return the argument strings list across multiple
implementations:
(defun my-command-line ()
(or
#+SBCL *posix-argv*
#+LISPWORKS system:*line-arguments-list*))
Now it would be handy to access them in a portable way and to parse them
according to a schema definition.
Synchronously
uiop:run-program either takes a string as argument, denoting the name of
the executable to run, or a list of strings, for the program and its arguments:
(uiop:run-program "firefox")
or
(uiop:run-program (list "firefox" "http:url"))
This will process the program output as specified and return the processing
results when the program and its output processing are complete.
It will always call a shell (rather than directly executing the command when
possible) if force-shell is specified. Similarly, it will never call a shell if
force-shell is specified to be nil.
Asynchronously
With uiop:launch-program.
Output (stdout) from the launched program is set using the output
keyword:
The exit code is also stored in the exit-code slot of our process-info
object. We see from the class definition above that it has no accessor, so
we’ll use slot-value. It has an initform to nil, so we don’t have to check
if the slot is bound. We can do:
(slot-value *my-process* 'uiop/launch-program::exit-code)
0
The trick is that we must run wait-process beforehand, otherwise the result
will be nil.
Note that run-program returns the exit code as the third value.
If the input keyword is set to :stream, then a stream is created and can be
written to in the same way as a file. The stream can be accessed using
uiop:process-info-input:
where write-line writes the string to the given stream, adding a newline at
the end. The force-output call attempts to flush the stream, but does not wait
for completion.
You can ask the same to :error-output and, in addition, you can ask
uiop:run-program to not signal an error, thus to not enter the interactive
debugger, with :ignore-error-status t.
In that case, you can check the success or the failure of the program with
the returned exit-code. 0 is success.
It works for more commands (sudo, vim…), however not for all interactive
programs, such as less or fzf.
Piping
Here’s an example to do the equivalent of ls | sort. Note that “ls” uses
launch-program (async) and outputs to a stream, where “sort”, the last
command of the pipe, uses run-program and outputs to a string.
(uiop:run-program "sort"
:input
(uiop:process-info-output
(uiop:launch-program "ls"
:output :stream))
:output :string)
On SBCL:
(sb-posix:getpid)
Note: You should read the relevant chapter from the CLISP implementation
notes before you proceed.
Also, in the present example, you can use allocation :ALLOCA, like you’d do
in C: stack-allocate a temporary. Why make things worse when using Lisp
than when using C?
This yields the following useful signature for your foreign function:
(ffi:def-c-call-out gethostname
(:arguments (name (ffi:c-ptr (ffi:c-array-max ffi:char 256)
:out :alloca)
(len ffi:int))
;; (:return-type BOOLEAN) could have been used here
;; (Solaris says it's either 0 or 1)
;; (Solaris says it's either 0 or -1).
(:return-type ffi:int))
(defun myhostname ()
(multiple-value-bind (success name)
;; :OUT or :IN-OUT parameters are returned via multiple val
(gethostname 256)
(if (zerop success)
(subseq name 0 (position #\null name))
(error ... ; errno may be set
...))))
(defvar hostname (myhostname))
This is how the same example above would be written in Allegro Common
Lisp version 6 and above. ACL doesn’t explicitly distinguish between
input and output arguments. The way to declare an argument as output
(i.e., modifiable by C) is to use an array, since arrays are passed by
reference and C therefore receives a pointer to a memory location (which is
what it expects). In this case things are made even easier by the fact that
gethostname() expects an array of char, and a SIMPLE-ARRAY of CHARACTER
represents essentially the same thing in Lisp. The foreign function
definition is therefore the following:
(def-foreign-call (c-get-hostname "gethostname")
((name (* :char) (simple-array 'character (*)))
(len :int integer))
:returning :int)
Let’s read this line by line: this form defines a Lisp function called C-GET-
HOSTNAME that calls the C function gethostname(). It takes two arguments:
the first one, called NAME, is a pointer to a char (*char in C), and a SIMPLE-
ARRAY of characters in Lisp; the second one is called LEN, and is an integer.
The function returns an integer value.
This function creates the NAME array, calls C-GET-HOSTNAME to fill it and then
checks the returned value. If the value is zero, then the call was successful,
and we return the contents of NAME up to the first 0 character (the string
terminator in C), otherwise we signal an error. Note that, unlike the
previous example, we allocate the string in Lisp, and we rely on the Lisp
garbage collector to get rid of it after the function terminates. Here is a
usage example:
* (get-hostname)
"terminus"
Working with strings is, in general, easier than the previous example
showed. Let’s say you want to call getenv() from Lisp to access the value
of an environment variable. getenv() takes a string argument (the variable
name) and returns another string (the variable value). To be more precise,
the argument is a pointer to a sequence of characters that should have been
allocated by the caller, and the return value is a pointer to an already-
existing sequence of chars (in the environment). Here is the definition of C-
GETENV:
(def-foreign-call (c-getenv "getenv")
((var (* :char) string))
:returning :int
:strings-convert t)
The argument in this case is still a pointer to char in C, but we can declare it
a STRING to Lisp. The return value is a pointer, so we declare it as integer.
Finally, the :STRINGS-CONVERT keyword argument specifies that ACL
should automatically translate the Lisp string passed as the first argument
into a C string. Here is how it’s used:
* (c-getenv "SHELL")
-1073742215
If you are surprised by the return value, just remember that C-GETENV
returns a pointer, and we must tell Lisp how to interpret the contents of the
memory location pointed to by it. Since in this case we know that it will
point to a C string, we can use the FF:NATIVE-TO-STRING function to
convert it to a Lisp string:
* (native-to-string (c-getenv "SHELL"))
"/bin/tcsh"
9
9
(The second and third values are the number of characters and bytes copied,
respectively). One caveat: if you ask for the value of a non-existent
variable, C-GETENV will return 0, and NATIVE-TO-STRING will fail. So a safer
example would be:
* (let ((ptr (c-getenv "NOSUCHVAR")))
(unless (zerop ptr)
(native-to-string ptr)))
NIL
Threads, concurrency, parallelism
Introduction
By threads, we mean separate execution strands within a single Lisp
process, sharing the same address space. Typically, execution is
automatically switched between these strands by the system (either by the
lisp kernel or by the operating system) so that tasks appear to be completed
in parallel (asynchronously). This page discusses the creation and
management of threads and some aspects of interactions between them. For
information about the interaction between lisp and other processes, see
Interfacing with your OS.
An instant pitfall for the unwary is that most implementations refer (in
nomenclature) to threads as processes - this is a historical feature of a
language which has been around for much longer than the term thread. Call
this maturity a sign of stable implementations, if you will.
The ANSI Common Lisp standard doesn’t mention this topic. We will
present here the portable bordeaux-threads library, an example
implementation via SBCL threads from the SBCL Manual, and the lparallel
library (GitHub).
For more libraries on parallelism and concurrency, see the Awesome CL list
and Quickdocs such as quickdocks on thread and concurrency.
Why bother?
The first question to resolve is: why bother with threads? Sometimes your
answer will simply be that your application is so straightforward that you
need not concern yourself with threads at all. But in many other cases it’s
difficult to imagine how a sophisticated application can be written without
multi-threading. For example:
In the case of system (native OS) threads, the scheduling and context
switching is ultimately determined by the OS. This is the case with Java
threads and Common Lisp threads.
In the case of “green” threads, that is to say threads that are completely
managed by the program, the scheduling can be completely controlled by
the program itself. Erlang is a great example of this approach.
Java’s Swing toolkit and JavaScript are both single-threaded, and yet they
can give the appearance of simultaneity because of the context switching
behind the scenes. Of course, concurrency is implemented using multiple
threads/processes in most cases.
For instance, if we have a task where part of the work can be done on a
different thread (possibly on a different core/processor), but the thread
which spawns this thread is logically dependent on the results of the
spawned thread (and as such has to “join” on that thread), it is still
Concurrency!
Bordeaux threads
The Bordeaux library provides a platform independent way to handle basic
threading on multiple Common Lisp implementations. The interesting bit is
that it itself does not really create any native threads — it relies entirely on
the underlying implementation to do so.
On the other hand, it does provide some useful extra features in its own
abstractions over the lower-level threads.
Also, you can see from the demo programs that a lot of the Bordeaux
functions seem quite similar to those used in SBCL. I don’t really think that
this is a coincidence.
You can refer to the documentation for more details (check the “Wrap-up”
section).
Installing Bordeaux Threads
(:BT-SEMAPHORE)
If there were no thread support, it would show “NIL” as the value of the
expression.
Depending on the specific library being used, we may also have different
ways of checking for concurrency support, which may be used instead of
the common check mentioned above.
For instance, in our case, we are interested in using the Bordeaux library. To
check whether there is support for threads using this library, we can see
whether the *supports-threads-p* global variable is set to NIL (no
support) or T (support available):
CL-USER> bt:*supports-threads-p*
T
Okay, now that we’ve got that out of the way, let’s test out both the
platform-independent library (Bordeaux) as well as the platform-specific
support (SBCL in this case).
Basics — list current thread, list all threads, get thread name
Update a global variable from a thread
Print a message onto the top-level using a thread
Print a message onto the top-level — fixed
Print a message onto the top-level — better
Modify a shared resource from multiple threads
Modify a shared resource from multiple threads — fixed using locks
Modify a shared resource from multiple threads — using atomic
operations
Joining on a thread, destroying a thread example
Basics — list current thread, list all threads, get thread name
;;; Print the current thread, all the threads, and the curre
(defun print-thread-info ()
(let* ((curr-thread (bt:current-thread))
(curr-thread-name (bt:thread-name curr-thread))
(all-threads (bt:all-threads)))
(format t "Current thread: ~a~%~%" curr-thread)
(format t "Current thread name: ~a~%~%" curr-thread-name
All threads:
#<THREAD "repl-thread" RUNNING {10043B8003}>
#<THREAD "auto-flush-thread" RUNNING {10043B7DA3}>
#<THREAD "swank-indentation-cache-thread" waiting on: #<WAIT
#<THREAD "reader-thread" RUNNING {1003A20063}>
#<THREAD "control-thread" waiting on: #<WAITQUEUE {1003A19E
#<THREAD "Swank Sentinel" waiting on: #<WAITQUEUE {10037900
#<THREAD "main thread" RUNNING {1002991CE3}>
NIL
(defun test-update-global-variable ()
(bt:make-thread
(lambda ()
(sleep 1)
(incf *counter*)))
*counter*)
Another point to note is that unlike some other languages (Java, for
instance), there is no separation from creating the thread object and
starting/running it. In this case, as soon as the thread is created, it is
executed.
The output:
CL-USER> (test-update-global-variable)
0
CL-USER> *counter*
1
As we can see, because the main thread returned immediately, the initial
value of *counter* is 0, and then around a second later, it gets updated to 1
by the anonymous thread.
So what went wrong? The problem is variable binding. Now, the ’t’
parameter to the format function refers to the top-level, which is a Common
Lisp term for the main console stream, also referred to by the global
variable *standard-output*. So we could have expected the output to be
shown on the main console screen.
The same code would have run fine if we had not run it in a separate thread.
What happens is that each thread has its own stack where the variables are
rebound. In this case, even for *standard-output*, which being a global
variable, we would assume should be available to all threads, is rebound
inside each thread! This is similar to the concept of ThreadLocal storage in
Java.
Print a message onto the top-level — fixed
So how do we fix the problem of the previous example? By binding the top-
level at the time of thread creation of course. Pure lexical scoping to the
rescue!
;;; Print a message onto the top-level using a thread — fixe
(defun print-message-top-level-fixed ()
(let ((top-level *standard-output*))
(bt:make-thread
(lambda ()
(format top-level "Hello from thread!"))
:name "hello"))
nil)
Which produces:
CL-USER> (print-message-top-level-fixed)
Hello from thread!
NIL
Phew! However, there is another way of producing the same result using a
very interesting reader macro as we’ll see next.
(eval-when (:compile-toplevel)
(defun print-message-top-level-reader-macro ()
(bt:make-thread
(lambda ()
(format #.*standard-output* "Hello from thread!")))
nil))
(print-message-top-level-reader-macro)
And the output:
CL-USER> (print-message-top-level-reader-macro)
Hello from thread!
NIL
So it works, but what’s the deal with the eval-when and what is that strange
#. symbol before *standard-output*?
The #. symbol is what is called a “Reader macro”. A reader (or read) macro
is called so because it has special meaning to the Common Lisp Reader,
which is the component that is responsible for reading in Common Lisp
expressions and making sense out of them. This specific reader macro
ensures that the binding of *standard-output* is done at read time.
Now this is where the eval-when bit comes into play. By wrapping the
whole function definition inside the eval-when, and ensuring that
evaluation takes place during compile time, the correct value of *standard-
output* is bound. If we had skipped the eval-when, we would see the
following error:
error:
don't know how to dump #<SWANK/GRAY::SLIME-OUTPUT-STREAM
==>
#<SWANK/GRAY::SLIME-OUTPUT-STREAM {100439EEA3}>
And that makes sense because SBCL cannot make sense of what this output
stream returns since it is a stream and not really a defined value (which is
what the ‘format’ function expects). That is why we see the “unreachable
code” error.
Note that if the same code had been run on the REPL directly, there would
be no problem since the resolution of all the symbols would be done
correctly by the REPL thread.
(defclass bank-account ()
((id :initarg :id
:initform (error "id required")
:accessor :id)
(name :initarg :name
:initform (error "name required")
:accessor :name)
(balance :initarg :balance
:initform 0
:accessor :balance)))
And we have a simple client which apparently does not believe in any form
of synchronisation:
(defparameter *rich*
(make-instance 'bank-account
:id 1
:name "Rich"
:balance 0))
; compiling (DEFPARAMETER *RICH* ...)
(defun demo-race-condition ()
(loop repeat 100
do
(bt:make-thread
(lambda ()
(loop repeat 10000 do (deposit *rich* 100))
(loop repeat 10000 do (withdraw *rich* 100))))))
This is all we are doing – create a new bank account instance (balance 0),
and then create a 100 threads, each of which simply deposits an amount of
100 10000 times, and then withdraws the same amount the same number of
times. So the final result should be the same as that of the opening balance,
which is 0, right? Let’s check that and see.
Whoa! The reason for this discrepancy is that incf and decf are not atomic
operations — they consist of multiple sub-operations, and the order in
which they are executed is not in our control.
This is what is called a “race condition” — multiple threads contending for
the same shared resource with at least one modifying thread which, more
likely than not, reads the wrong value of the object while modifying it. How
do we fix it? One simple way it to use locks (mutex in this case, could be
semaphores for more complex situations).
(defun demo-race-condition-locks ()
(loop repeat 100
do
(bt:make-thread
(lambda ()
(loop repeat 10000 do (bt:with-lock-held (*lock*)
(deposit *rich* 100)))
(loop repeat 10000 do (bt:with-lock-held (*lock*)
(withdraw *rich* 100))))))
; compiling (DEFUN DEMO-RACE-CONDITION-LOCKS ...)
Excellent! Now this is better. Of course, one has to remember that using a
mutex like this is bound to affect performance. There is a better way in
quite a few circumstances — using atomic operations when possible. We’ll
cover that next.
Atomic operations are operations that are guaranteed by the system to all
occur inside a conceptual transaction, i.e., all the sub-operations of the main
operation all take place together without any interference from outside. The
operation succeeds completely or fails completely. There is no middle
ground, and there is no inconsistent state.
The Bordeaux library does not provide any real support for atomics, so we
will have to depend on the specific implementation support for that. In our
case, that is SBCL, and so we will have to defer this demo to the SBCL
section.
A simple demo:
(defmacro until (condition &body body)
(let ((block-name (gensym)))
`(block ,block-name
(loop
(if ,condition
(if ,condition
(return-from ,block-name nil)
(progn
,@body))))))
(defun join-destroy-thread ()
(let* ((s *standard-output*)
(joiner-thread
(bt:make-thread
(lambda ()
(loop for i from 1 to 10
do
(format s "~%[Joiner Thread] Working..."
(sleep (* 0.01 (random 100)))))))
(destroyer-thread
(bt:make-thread
(lambda ()
(loop for i from 1 to 1000000
do
(format s "~%[Destroyer Thread] Working
(sleep (* 0.01 (random 10000))))))))
(format t "~%[Main Thread] Waiting on joiner thread...")
(bt:join-thread joiner-thread)
(format t "~%[Main Thread] Done waiting on joiner thread
(if (bt:thread-alive-p destroyer-thread)
(progn
(format t "~%[Main Thread] Destroyer thread alive.
(bt:destroy-thread destroyer-thread))
(format t "~%[Main Thread] Destroyer thread is alrea
(until (bt:thread-alive-p destroyer-thread)
(format t "[Main Thread] Waiting for destroyer th
(format t "~%[Main Thread] Destroyer thread dead")
(format t "~%[Main Thread] Adios!~%")))
The until macro simply loops around until the condition becomes true. The
rest of the code is pretty much self-explanatory — the main thread waits for
the joiner-thread to finish, but it immediately destroys the destroyer-thread.
Now let’s move onto some more comprehensive examples which tie
together all the concepts discussed thus far.
Timeouts
Useful functions
SBCL threads
SBCL provides support for native threads via its sb-thread package. These
are very low-level functions, but we can build our own abstractions on top
of these as shown in the demo examples.
You can refer to the documentation for more details (check the “Wrap-up”
section).
You can see from the examples below that there is a strong correspondence
between Bordeaux and SBCL Thread functions. In most cases, the only
difference is the change of package name from bt to sb-thread.
It is evident that the Bordeaux thread library was more or less based on the
SBCL implementation. As such, explanation will be provided only in those
cases where there is a major difference in syntax or semantics.
Basics — list current thread, list all threads, get thread name
The code:
;;; Print the current thread, all the threads, and the curre
(defun print-thread-info ()
(let* ((curr-thread sb-thread:*current-thread*)
(curr-thread-name (sb-thread:thread-name curr-threa
(all-threads (sb-thread:list-all-threads)))
(format t "Current thread: ~a~%~%" curr-thread)
(format t "Current thread name: ~a~%~%" curr-thread-name
(format t "All threads:~% ~{~a~%~}~%" all-threads))
nil)
All threads:
#<THREAD "repl-thread" RUNNING {10043B8003}>
#<THREAD "auto-flush-thread" RUNNING {10043B7DA3}>
#<THREAD "swank-indentation-cache-thread" waiting on: #<WAIT
#<THREAD "reader-thread" RUNNING {1003A20063}>
#<THREAD reader thread RUNNING {1003A20063}>
#<THREAD "control-thread" waiting on: #<WAITQUEUE {1003A19E
#<THREAD "Swank Sentinel" waiting on: #<WAITQUEUE {10037900
#<THREAD "main thread" RUNNING {1002991CE3}>
NIL
The code:
;;; Update a global variable from a thread
(defparameter *counter* 0)
(defun test-update-global-variable ()
(sb-thread:make-thread
(lambda ()
(sleep 1)
(incf *counter*)))
*counter*)
The code:
;;; Print a message onto the top-level using a thread
(defun print-message-top-level-wrong ()
(sb-thread:make-thread
(lambda ()
(format *standard-output* "Hello from thread!")))
nil)
The code:
;;; Print a message onto the top-level using a thread - fixe
(defun print-message-top-level-fixed ()
(let ((top-level *standard-output*))
(sb-thread:make-thread
(lambda ()
(format top-level "Hello from thread!"))))
nil)
The code:
;;; Print a message onto the top-level using a thread - read
(eval-when (:compile-toplevel)
(defun print-message-top-level-reader-macro ()
(sb-thread:make-thread
(lambda ()
(format #.*standard-output* "Hello from thread!")))
nil))
The code:
;;; Modify a shared resource from multiple threads
(defclass bank-account ()
((id :initarg :id
:initform (error "id required")
:accessor :id)
(name :initarg :name
:initform (error "name required")
:accessor :name)
(balance :initarg :balance
:initform 0
:accessor :balance)))
(defparameter *rich*
(make-instance 'bank-account
:id 1
:name "Rich"
:balance 0))
(defun demo-race-condition ()
(loop repeat 100
do
(sb-thread:make-thread
(lambda ()
(loop repeat 10000 do (deposit *rich* 100))
(loop repeat 10000 do (withdraw *rich* 100))))))
The code:
(defvar *lock* (sb-thread:make-mutex))
(defun demo-race-condition-locks ()
(loop repeat 100
do
(sb-thread:make-thread
(lambda ()
(loop repeat 10000 do (sb-thread:with-mutex (*lock
(deposit *rich* 100)))
(loop repeat 10000 do (sb-thread:with-mutex (*lock
(withdraw *rich* 100))))))
(defun demo-race-condition-atomics ()
(loop repeat 100
do (sb-thread:make-thread
(lambda ()
(loop repeat 10000 do (atomic-deposit *rich* 100)
(loop repeat 10000 do (atomic-withdraw *rich* 100
Opening: 0
Closing: 0
Opening: 0
Closing: 0
Opening: 0
Closing: 0
Opening: 0
Closing: 0
Opening: 0
Closing: 0
NIL
As you can see, SBCL’s atomic functions are a bit quirky. The two
functions used here: sb-ext:incf and sb-ext:atomic-decf have the
following signatures:
and
The interesting bit is that the “place” parameter must be any of the
following (as per the documentation):
This is the reason for the bizarre construct used in the atomic-deposit and
atomic-decf methods.
With locks:
CL-USER> (time
(loop repeat 100
do (demo-race-condition-locks)))
Evaluation took:
57.711 seconds of real time
431.451639 seconds of total run time (408.014746 user, 23.
747.61% CPU
126,674,011,941 processor cycles
3,329,504 bytes consed
NIL
With atomics:
CL-USER> (time
(loop repeat 100
do (demo-race-condition-atomics)))
Evaluation took:
2.495 seconds of real time
8.175454 seconds of total run time (6.124259 user, 2.05119
[ Run times consist of 0.420 seconds GC time, and 7.756 se
327.66% CPU
5,477,039,706 processor cycles
3,201,582,368 bytes consed
NIL
The results? The locks version took around 57s whereas the lockless
atomics version took just 2s! This is a massive difference indeed!
The code:
;;; Joining on and destroying a thread
;;; Joining on and destroying a thread
(defun join-destroy-thread ()
(let* ((s *standard-output*)
(joiner-thread
(sb-thread:make-thread
(lambda ()
(loop for i from 1 to 10
do
(format s "~%[Joiner Thread] Working...")
(sleep (* 0.01 (random 100)))))))
(destroyer-thread
(sb-thread:make-thread
(lambda ()
(loop for i from 1 to 1000000
do
(format s "~%[Destroyer Thread] Working..."
(sleep (* 0.01 (random 10000))))))))
Useful functions
Here is a summarised list of the functions, macros and global variables used
in the examples along with some extras:
You should follow up on your own by reading a lot more on this topic. I
share some of my own references here:
Next up, the final post in this mini-series: parallelism in Common Lisp
using the lparallel library.
Note that not all the examples shown in this post are necessarily parallel.
Asynchronous constructs such as Promises and Futures are, in particular,
more suited to concurrent programming than parallel programming.
The modus operandi of using the lparallel library (for a basic use case) is as
follows:
Note that the onus of ensuring that the tasks being carried out are logically
parallelisable as well as taking care of all mutable state is on the developer.
Installation
And that’s all it took! Now let’s see how this library actually works.
First, let’s get hold of the number of threads that we are going to use for our
parallel examples. Ideally, we’d like to have a 1:1 match between the
number of worker threads and the number of available cores.
We can use the great Serapeum library to this end, which has a count-cpus
function, that works on all major platforms.
Install it:
CL-USER> (ql:quickload "serapeum")
Common Setup
In this example, we will go through the initial setup bit, and also show some
useful information once the setup is done.
(:LPARALLEL)
Note that the *kernel* global variable can be rebound — this allows
multiple kernels to co-exist during the same run. Now, some useful
information about the kernel:
CL-USER> (defun show-kernel-info ()
(let ((name (lparallel:kernel-name))
(count (lparallel:kernel-worker-count))
(context (lparallel:kernel-context))
(bindings (lparallel:kernel-bindings)))
(format t "Kernel name = ~a~%" name)
(format t "Worker threads count = ~d~%" count)
(format t "Kernel context = ~a~%" context)
(format t "Kernel bindings = ~a~%" bindings)))
WARNING: redefining COMMON-LISP-USER::SHOW-KERNEL-INFO in DEFUN
SHOW-KERNEL-INFO
CL-USER> (show-kernel-info)
Kernel name = custom-kernel
Worker threads count = 8
Kernel context = #<FUNCTION FUNCALL>
Kernel bindings = ((*STANDARD-OUTPUT* . #<SLIME-OUTPUT-STREAM {1
(*ERROR-OUTPUT* . #<SLIME-OUTPUT-STREAM {1004
NIL
End the kernel (this is important since *kernel* does not get garbage
collected until we explicitly end it):
CL-USER> (lparallel:end-kernel :wait t)
(#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
#<SB-THREAD:THREAD "custom--kernel" FINISHED values: NIL {10072
For these demos, we will be using the following initial setup from a coding
perspective:
(require ‘lparallel)
(require ‘bt-semaphore)
(defpackage :lparallel-user
(:use :cl :lparallel :lparallel.queue :bt-semaphore))
(in-package :lparallel-user)
;;; initialise the kernel
(defun init ()
(setf *kernel* (make-kernel 8 :name "channel-queue-kernel")))
(init)
So we will be using a kernel with 8 worker threads (one for each CPU core
on the machine).
And once we’re done will all the examples, the following code will be run
to close the kernel and free all used system resources:
;;; shut the kernel down
(defun shutdown ()
(end-kernel :wait t))
(shutdown)
Now let’s try submitting multiple tasks to the same channel. In this simple
example, we are simply creating three tasks that square, triple, and
quadruple the supplied input respectively.
Which produces:
LPARALLEL-USER> (test-queue-properties)
T
17
51
55
42
82
T
NIL
LPARALLEL-USER> (check-queue-types)
Of course, you can always create instances of the specific queue types
yourself, but it is always better, when you can, to stick to the generic
interface and letting the library create the proper type of queue for you.
Here we submit a single task that repeatedly scans the queue till it’s empty,
pops the available values, and pushes them into the res list.
And the output:
LPARALLEL-USER> (test-basic-queue)
9604 9409 9216 9025 8836 8649 8464 8281 8100 7921 7744 7569
NIL
Killing tasks
All tasks which are created are by default assigned a category of :default.
The dynamic property, *task-category* holds this value, and can be
dynamically bound to different values (as we shall see).
;;; kill default tasks
(defun test-kill-all-tasks ()
(let ((channel (make-channel))
(stream *query-io*))
(dotimes (i 10)
(submit-task
channel
(lambda (x)
(sleep (random 10))
(format stream "~d~%" (* x x))) (random 10)))
(sleep (random 2))
(kill-tasks :default)))
Sample run:
LPARALLEL-USER> (test-kill-all-tasks)
16
1
8
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
Since we had created 10 tasks, all the 8 kernel worker threads were
presumably busy with a task each. When we killed tasks of category
:default, all these threads were killed as well and had to be regenerated
(which is an expensive operation). This is part of the reason why
lparallel:kill-tasks must be avoided.
Now, in the example above, all running tasks were killed since all of them
belonged to the :default category. Suppose we wish to kill only specific
tasks, we can do that by binding *task-category* when we create those
tasks, and then specifying the category when we invoke lparallel:kill-
tasks.
For example, suppose we have two categories of tasks – tasks which square
their arguments, and tasks which cube theirs. Let’s assign them categories
’squaring-tasks and ’cubing-tasks respectively. Let’s then kill tasks of a
randomly chosen category ’squaring-tasks or ’cubing-tasks.
[Cubing] 2 = 8
[Squaring] 4 = 16
[Cubing] 4
= [Cubing] 643 = 27
"Killing squaring tasks"
4
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
[Cubing] 1 = 1
[Cubing] 0 = 0
LPARALLEL-USER> (test-kill-random-tasks)
[Squaring] 1 = 1
[Squaring] 3 = 9
"Killing cubing tasks"
5
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
[Squaring] 2 = 4
WARNING: lparallel: Replacing lost or dead worker.
WARNING: lparallel: Replacing lost or dead worker.
[Squaring] 0 = 0
[Squaring] 4 = 16
To check whether the promise has been fulfilled yet or not, we can use the
lparallel:fulfilledp predicate function. Finally, the lparallel:force
function is used to extract the value out of the promise. Note that this
function blocks until the operation is complete.
Now, let’s take a bigger example. Assuming that we don’t want to have to
wait for the promise to be fulfilled, and instead have the current do some
useful work, we can delegate the promise fulfillment to external explicitly
as seen in the next example.
Consider we have a function that squares its argument. And, for the sake of
argument, it consumes a lot of time doing so. From our client code, we want
to invoke it, and wait till the squared value is available.
(defun promise-with-threads ()
(let ((p (promise))
(stream *query-io*)
(n (progn
(princ "Enter a number: ")
(read))))
(format t "In main function...~%")
(bt:make-thread
(lambda ()
(sleep (random 10))
(format stream "Inside thread... fulfilling promise~%")
(fulfill p (* n n))))
(bt:make-thread
(lambda ()
(loop
when (fulfilledp p)
do (return)
do (progn
(format stream "~d~%" (random 100))
(sleep (* 0.01 (random 100)))))))
Meanwhile, in the main thread, we spawn off another thread that keeps
checking if the promise has been fulfilled or not. If not, it prints some
random number and continues checking. Once the promise has been
fulfilled, we can extract the value using lparallel:force in the main
thread as shown.
This shows that promises can be fulfilled by different threads while the
code that created the promise need not wait for the promise to be fulfilled.
This is especially important since, as mentioned before, lparallel:force
is a blocking call. We want to delay forcing the promise until the value is
actually available.
Another point to note when using promises is that once a promise has been
fulfilled, invoking force on the same object will always return the same
value. That is to say, a promise can be successfully fulfilled only once.
For instance:
(defun multiple-fulfilling ()
(let ((p (promise)))
(dotimes (i 10)
(fulfill p (random 100))
(format t "~d~%" (force p)))))
Which produces:
LPARALLEL-USER> (multiple-fulfilling)
15
15
15
15
15
15
15
15
15
15
NIL
Secondly, the future itself is spawned off on a separate thread by the library,
so it does not interfere with the execution of the current thread very much
unlike promises as could be seen in the promise-with-threads example
(which needed an explicit thread for the fulfilling code in order to avoid
blocking the current thread).
The most interesting bit is that (even in terms of the actual theory
propounded by Dan Friedman and others), a Future is conceptually
something that fulfills a Promise. That is to say, a promise is a contract that
some value will be generated sometime in the future, and a future is
precisely that “something” that does that job.
What this means is that even when using the lparallel library, the basic use
of a future would be to fulfill a promise. This means that hacks like
promise-with-threads need not be made by the user.
Here’s the scenario: we want to read in a number and calculate its square.
So we offload this work to another function, and continue with our own
work. When the result is ready, we want it to be printed on the console
without any intervention from us.
Explanation: All right, so first off, we create a promise to hold the squared
value when it is generated. This is the p object. The input value is stored in
the local variable n.
Then we create a future object f. This future simply squares the input value
and fulfills the promise with this value. Finally, since we want to print the
output in its own time, we force an anonymous future which simply prints
the output string as shown.
Note that this is very similar to the situation in an environment like Node,
where we pass callback functions to other functions with the understanding
that the callback will be called when the invoked function is done with its
work.
Finally note that the following snippet is still fine (even if it uses the
blocking lparallel:force call because it’s on a separate thread):
(force (future
(format stream "Square of ~d = ~d~%" n (force p))))
To summarise, the general idiom of usage is: define objects which will
hold the results of asynchronous computations in promises, and use
futures to fulfill those promises.
Cognates are arguably the raison d’etre of the lparallel library. These
constructs are what truly provide parallelism in the lparallel. Note, however,
that most (if not all) of these constructs are built on top of futures and
promises.
In the first case we don’t have much explicit control over the operations
themselves. We mostly rely on the fact that the library itself will optimise
and parallelise the forms to whatever extent it can. In this post, we will
focus on the second category of cognates.
Suppose we had a list of random strings of length varying from 3 to 10, and
we wished to collect their lengths in a vector.
Let’s first set up the helper functions that will generate the random strings:
(defvar *chars*
(remove-duplicates
(sort
(loop for c across "The quick brown fox jumps over the lazy
( p q j p y
when (alpha-char-p c)
collect (char-downcase c))
#'char<)))
And here’s how the Common Lisp map version of the solution might look
like:
;;; map demo
(defun test-map ()
(map 'vector #'length (get-random-strings 100)))
which produces:
LPARALLEL-USER> (test-pmap)
#(8 7 6 7 6 4 5 6 5 7)
LPARALLEL-USER>
As you can see from the definitions of test-map and test-pmap, the syntax
of the lparallel:map and lparallel:pmap functions are exactly the same
(well, almost - lparallel:pmap has a few more optional arguments).
Some useful cognate functions and macros (all of them are functions except
when marked so explicitly. Note that there are quite a few cognates, and I
have chosen a few to try and represent every category through an example:
Sample run:
LPARALLEL-USER> (test-pmap)
#(0 1 4 9 16 25 36 49 64 81)
The behaviour is that it returns the first non-nil element amongst its
arguments. However, due to the parallel nature of this macro, that element
varies.
;;; por - macro
(defun test-por ()
(let ((a 100)
(b 200)
(c nil)
(d 300))
(por a b c d)))
Sample run:
LPARALLEL-USER> (dotimes (i 10)
(print (test-por)))
300
300
100
100
100
300
100
100
100
100
NIL
In the case of the normal or operator, it would always have returned the first
non-nil element viz. 100.
Sample run:
LPARALLEL-USER> (test-pdotimes)
39
29
81
42
56
NIL
Sample run:
LPARALLEL-USER> (test-pfuncall)
120
This very important function also takes two optional keyword arguments:
:parts (same meaning as explained), and :recurse. If :recurse is non-nil,
it recursively applies lparallel:preduce to its arguments, otherwise it
default to using reduce.
;;; preduce - function
(defun test-preduce ()
(let ((numbers (loop for i from 1 to 100
collect i)))
(preduce #'+
numbers
:parts (length numbers)
:recurse t)))
Sample run:
LPARALLEL-USER> (test-preduce)
5050
lparallel:premove-if-not: parallel version of remove-if-not.
Sample run:
LPARALLEL-USER> (test-premove-if-not)
(2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42
56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94
Sample run:
LPARALLEL-USER> (test-pevery)
(NIL T)
In this example, we are performing two checks - firstly, whether all the
numbers in the range [1,100] are even, and secondly, whether all the
numbers in the same range are integers.
Sample run:
LPARALLEL-USER> (test-pcount)
(defun test-psort ()
(let* ((names (list "Peter" "Sybil" "Basil" "Candy" "Olga"
(people (loop for name in names
collect (make-person :name name
:age (+ (random 20)
20)))))
(print "Before sorting...")
(print people)
(fresh-line)
(print "After sorting...")
(psort
people
(lambda (x y)
(< (person-age x)
(person-age y)))
:test #'=)))
Sample run:
LPARALLEL-USER> (test-psort)
"Before sorting..."
(#S(PERSON :NAME "Peter" :AGE 24) #S(PERSON :NAME "Sybil" :A
#S(PERSON :NAME "Basil" :AGE 22) #S(PERSON :NAME "Candy" :A
#S(PERSON :NAME "Olga" :AGE 33))
"After sorting..."
(#S(PERSON :NAME "Sybil" :AGE 20) #S(PERSON :NAME "Basil" :A
#S(PERSON :NAME "Candy" :AGE 23) #S(PERSON :NAME "Peter" :A
#S(PERSON :NAME "Olga" :AGE 33))
Error handling
The thread on the current line can be killed with k, or if there’s a lot of
threads to kill, several lines can be selected and k will kill all the threads in
the selected region.
g will update the thread list, but when you have a lot of threads starting and
stopping it may be too cumbersome to always press g, so there’s a variable
slime-threads-update-interval, when set to a number X the thread list
will be automatically updated each X seconds, a reasonable value would be
0.5.
References
There are, of course, a lot more functions, objects, and idiomatic ways of
performing parallel computations using the lparallel library. This post
barely scratches the surface on those. However, the general flow of
operation is amply demonstrated here, and for further reading, you may find
the following resources useful:
ASDF
ASDF is the standard build system for Common Lisp. It is shipped in most
Common Lisp implementations. It includes UIOP, “the Utilities for
Implementation- and OS- Portability”. You can read its manual and the
tutorial and best practices.
Simple examples
Loading a system definition
When you start your Lisp, it knows about its internal modules and, by
default, it has no way to know that your shiny new project is located under
your ~/code/foo/bar/new-ideas/ directory. So, in order to load your
project in your image, you have one of three ways:
Loading a system
Once your Lisp knows what your system is and where it lives, you can load
it.
(in-package :foobar)
(some-fun ...)
Quicklisp calls ASDF under the hood, with the advantage that it will
download and install any dependency if they are not already installed.
(ql:quickload "foobar")
;; =>
;; installs all dependencies
;; and loads the system.
Also, you can use SLIME to load a system, using the M-x slime-load-
system Emacs command or the , load-system comma command in the
prompt. The interesting thing about this way of doing it is that SLIME
collects all the system warnings and errors in the process, and puts them in
the *slime-compilation* buffer, from which you can interactively inspect
them after the loading finishes.
Testing a system
Designating a system
The proper way to designate a system in a program is with lower-case
strings, not symbols, as in:
(asdf:load-system "foobar")
(asdf:test-system "foobar")
A trivial system would have a single Lisp file called foobar.lisp, located
at the project’s root. That file would depend on some existing libraries, say
alexandria for general purpose utilities, and trivia for pattern-matching.
To make this system buildable using ASDF, you create a system definition
file called foobar.asd, with the following contents:
(asdf:defsystem "foobar"
:depends-on ("alexandria" "trivia")
:components ((:file "foobar")))
Note how the type lisp of foobar.lisp is implicit in the name of the file
above. As for contents of that file, they would look like this:
(defpackage :foobar
(:use :common-lisp :alexandria :trivia)
(:export
#:some-function
#:another-function
#:call-with-foobar
#:with-foobar))
(in-package :foobar)
Instead of using multiple complete packages, you might want to just import
parts of them:
(defpackage :foobar
(:use #:common-lisp)
(:import-from #:alexandria
#:some-function
#:another-function))
(:import-from #:trivia
#:some-function
#:another-function))
...)
If your Lisp was already started when you created that file, you may have
to, either:
Even the most trivial of systems needs some tests, if only because it will
have to be modified eventually, and you want to make sure those
modifications don’t break client code. Tests are also a good way to
document expected behavior.
The :in-order-to clause in the first system allows you to use (asdf:test-
system :foobar) which will chain into foobar/tests. The :perform
clause in the second system does the testing itself.
In the test system, fiveam is the name of a popular test library, and the
content of the perform method is how to invoke this library to run the test
suite :foobar. Obvious YMMV if you use a different library.
Install with
(ql:quickload “cl-project”)
Create a project:
(cl-project:make-project #p"lib/cl-sample/"
:author "Eitaro Fukamachi"
:email "[email protected]"
:license "LLGPL"
:depends-on '(:clack :cl-annot))
;-> writing /Users/fukamachi/Programs/lib/cl-sample/.gitignore
; writing /Users/fukamachi/Programs/lib/cl-sample/README.markd
; writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample-te
; writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample.as
; writing /Users/fukamachi/Programs/lib/cl-sample/src/hogehoge
; writing /Users/fukamachi/Programs/lib/cl-sample/t/hogehoge.l
;=> T
Print debugging
Well of course we can use the famous technique of “print debugging”. Let’s just
recap a few print functions.
(format t "~a" …), with the aesthetic directive, prints a string (in t, the
standard output stream) and returns nil, whereas format nil … doesn’t print
anything and returns a string. With many format controls we can print several
variables at once.
print has this useful debugging feature that it prints and returns the result form it
was given as argument. You can intersperse print statements in the middle of
your algorithm, it won’t break it.
(+ 2 (print 40))
Logging
Logging is already a good evolution from print debugging ;)
log4cl is the popular, de-facto logging library although it isn’t the only one.
Download it:
(ql:quickload "log4cl")
We can use log4cl with its log nickname, then it is as simple to use as:
(log:info *foo*)
;; <INFO> [13:36:49] cl-user () - *FOO*: (:A :B :C)
We can interleave strings and expressions, with or without format control strings:
(log:info "foo is " *foo*)
;; <INFO> [13:37:22] cl-user () - foo is *FOO*: (:A :B :C)
(log:info "foo is ~{~a~}" *foo*)
;; <INFO> [13:39:05] cl-user () - foo is ABC
With its companion library log4slime, we can interactively change the log level:
globally
per package
per function
and by CLOS methods and CLOS hierarchy (before and after methods)
It is very handy, when we have a lot of output, to turn off the logging of functions
or packages we know to work, and thus narrowing our search to the right area.
We can even save this configuration and re-use it in another image, be it on
another machine.
We can do all this through commands, keyboard shortcuts and also through a
menu or mouse clicks.
“changing the log level with log4slime”
We usually need to create some data to test our function(s). This is a subsequent
art of the REPL existence and it may be a new discipline for newcomers. A trick
is to write the test data alongside your functions but below a #+nil feature test (or
safer, +(or): it is still possible that someone pushed NIL to the *features* list)
so that only you can manually compile them:
#+nil
(progn
(defvar *test-data* nil)
(setf *test-data* (make-instance 'foo …)))
When you load this file, *test-data* won’t exist, but you can manually create it
with C-c C-c.
All that being said, keep in mind to write unit tests when time comes ;)
(inspect *foo*)
2. 2: :C
> q
We can also, in editors that support it, right-click on any object in the REPL and
inspect them (or C-c I on the object to inspect in Slime). We are presented a
screen where we can dive deep inside the data structure and even change it.
Trace
trace allows us to see when a function was called, what arguments it received, and
the value it returned.
(defun factorial (n)
(if (plusp n)
(* n (factorial (1- n)))
1))
To start tracing a function, just call trace with the function name (or several
function names):
(trace factorial)
(factorial 2)
0: (FACTORIAL 3)
1: (FACTORIAL 2)
2: (FACTORIAL 1)
3: (FACTORIAL 0)
3: FACTORIAL returned 1
2: FACTORIAL returned 1
1: FACTORIAL returned 2
0: FACTORIAL returned 6
6
(untrace factorial)
If you don’t see recursive calls, that may be because of the compiler’s
optimizations. Try this before defining the function to be traced:
(declaim (optimize (debug 3))) ;; or C-u C-c C-c to compile with max
Trace options
trace accepts options. For example, you can use :break t to invoke the
debugger at the start of the function, before it is called (more on break below):
(trace factorial :break t)
(factorial 2)
We can define many things in one call to trace. For instance, options that appear
before the first function name to trace are global, they affect all traced functions
that we add afterwards. Here, :break t is set for every function that follows:
factorial, foo and bar:
On the contrary, if an option comes after a function name, it acts as a local option,
only for its preceding function. That’s how we first did. Below foo and bar come
after, they are not affected by :break:
(trace factorial :break t foo bar)
But do you actually want to break before the function call or just after it? With
:break as with many options, you can choose. These are the options for :break:
Note that we explained the trace function of SBCL. Other implementations may
have the same feature with another syntax and other option names. For example,
in LispWorks it is “:break-on-exit” instead of “:break-after”, and we write (trace
(factorial :break t)).
Below are some other options but first, a trick with :break.
The argument to an option can be any form. Here’s a trick, on SBCL, to get the
break window when we are about to call factorial with 0. (sb-debug:arg 0)
refers to n, the first argument.
CL-USER> (trace factorial :break (equal 0 (sb-debug:arg 0)))
;; WARNING: FACTORIAL is already TRACE'd, untracing it first.
;; (FACTORIAL)
Running it again:
CL-USER> (factorial 3)
0: (FACTORIAL 3)
1: (FACTORIAL 2)
2: (FACTORIAL 1)
3: (FACTORIAL 0)
Restarts:
0: [CONTINUE] Return from BREAK.
1: [RETRY] Retry SLIME REPL evaluation request.
2: [*ABORT] Return to SLIME's top level.
3: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING
{1003551BC3}>)
Backtrace:
0: (FACTORIAL 1)
Locals:
N = 1 <---------- before calling (factorial 0), n equals
1.
SBCL trace
CCL trace
LispWorks trace
Allegro trace
In SBCL, we can use (trace foo :methods t) to trace the execution order of
method combination (before, after, around methods). For example:
(trace foo :methods t)
(foo 2.0d0)
0: (FOO 2.0d0)
1: ((SB-PCL::COMBINED-METHOD FOO) 2.0d0)
2: ((METHOD FOO (FLOAT)) 2.0d0)
3: ((METHOD FOO (T)) 2.0d0)
3: (METHOD FOO (T)) returned 3
2: (METHOD FOO (FLOAT)) returned 9
2: ((METHOD FOO :AFTER (DOUBLE-FLOAT)) 2.0d0)
2: (METHOD FOO :AFTER (DOUBLE-FLOAT)) returned DOUBLE
1: (SB-PCL::COMBINED-METHOD FOO) returned 9
0: FOO returned 9
9
It presents the error message, the available actions (restarts), and the backtrace. A
few remarks:
Usually your compiler will optimize things out and this will reduce the amount of
information available to the debugger. For example sometimes we can’t see
intermediate variables of computations. We can change the optimization choices
with:
(declaim (optimize (speed 0) (space 0) (debug 3)))
and recompile our code. You can achieve the same with a handy shortcut: C-u C-
c C-c: the form is compiled with maximum debug settings. You can on the
contrary use a negative prefix argument (M--) to compile for speed. And use a
numeric argument to set the setting to it (you should read the docstring of slime-
compile-defun).
Step
step is an interactive command with similar scope than trace. This:
;; note: we copied factorial over to a file, to have more debug infor
(step (factorial 3))
gives an interactive pane with available actions (restarts) and the backtrace:
Evaluating call:
(FACTORIAL 3)
With arguments:
3
[Condition of type SB-EXT:STEP-FORM-CONDITION]
Restarts:
0: [STEP-CONTINUE] Resume normal execution <---------- stepping
actions
1: [STEP-OUT] Resume stepping after returning from this function
2: [STEP-NEXT] Step over call
3: [STEP-INTO] Step into call
4: [RETRY] Retry SLIME REPL evaluation request.
5: [*ABORT] Return to SLIME's top level.
--more--
Backtrace:
0: (FACTORIAL 3) <----------- press Enter to fold/unfold.
Locals:
N = 3 <----------- want to check? Move the point
here and
press "e" to evaluate code on
that frame.
(again, be sure you compiled your function with maximum debug settings (see
above). Otherwise, your compiler might do optimizations under the hood and you
might not see useful information such as local variables, or you might not be able
to step at all.)
You have many options here. If you are using Emacs (or any other editor
actually), keep in mind that you have a “SLDB” menu that shows you the
available actions, in addition to the step window.
follow the restarts to continue stepping: continue the execution, step out of
this function, step into the function call the point is on, step over to the next
function call, or abort everything. The shortcuts are:
c: continue
s: step
x: step next
o: step out
inspect the backtrace and the source code. You can go to the source file
with v, on each stackframe (each line of the backtrace). Press Enter or t
(“toggle details”) on the stackframe to see more information, such as the
function parameters for this call. Use n and p to navigate, use M-n and M-p to
navigate to the next or previous stackframe and to open the corresponding
source file at the same time. The point will be placed on the function being
called.
evaluate code from within the context of that stackframe. In Slime, use e
(“eval in frame” and d to pretty-pint the result) and type a Lisp form. It will
be executed in the context of the stackframe the point is on. Look, you can
even inspect variables and have Slime open another inspector window. If
you are on the first frame (0:), press i, then “n” to inspect the intermediate
variable.
resume execution from where you want. Use r to restart the frame the point
is on. For example, go change the source code (without quitting the
interactive debugger), re-compile it, re-run the frame to see if it works better.
You didn’t restart all the program execution, you just restarted your program
from a precise point. Use R to return from a stackframe, by giving its return
value.
NB: let’s think about it, this is awesome! We just restarted our program from any
point in time. If we work with long-running computations, we don’t need to
restart it from the start. We can change, re-compile our erroneous code and
resume execution from where it is needed to pass, no more.
In this video you will find a demo that shows the process explained above: how to
fix a buggy function and how to resume the program execution from anywhere in
the stack, without running everything from zero again. The video shows it with
Emacs and Slime, the Lem editor, both with SBCL.
Break
A call to break makes the program enter the debugger, from which we can inspect
the call stack, and do everything described above in the stepper.
Breakpoints in Slime
Look at the SLDB menu, it shows navigation keys and available actions. Of which:
Once we are in a frame and detect a suspicious behavior, we can even re-compile
a function at runtime and resume the program execution from where it stopped
(using the “step-continue” restart or using r (“restart frame”) on a given
stackframe).
See also the Slime-star Emacs extension to set breakpoints without code
annotations.
Cross-referencing
Your Lisp can tell you all the places where a function is referenced or called,
where a global variable is set, where a macro is expanded, and so on. For
example, slime-who-calls (C-c C-w C-c or the Slime > Cross-Reference menu)
will show you all the places where a function is called.
Unit tests
Last but not least, automatic testing of functions in isolation might be what you’re
looking for! See the testing section and a list of test frameworks and libraries.
Remote debugging
You can have your software running on a machine over the network, connect to it
and debug it from home, from your development environment.
The steps involved are to start a Swank server on the remote machine (Swank is
the backend companion of Slime), create an ssh tunnel and connect to the Swank
server from our editor. Then we can browse and evaluate code on the running
instance transparently.
To test this, let’s define a function that prints forever.
(require :swank)
(require :bordeaux-threads)
(defparameter *counter* 0)
(defun dostuff ()
(format t "hello world ~a!~%" *counter*))
(defun runner ()
(swank:create-server :port 4006)
(format t "we are past go!~%")
(bt:make-thread (lambda ()
(loop repeat 5 do
(sleep 5)
(dostuff)
(incf *counter*)))
:name "do-stuff"))
(runner)
If you check with (bt:all-threads), you’ll see your Swank server running on
port 4006, as well as the other thread ready to do stuff:
this will securely forward port 4006 on the server at example.com to our local
computer’s port 4006 (Swank only accepts connections from localhost).
and eval it as usual with C-c C-c or M-x slime-eval-region for instance. The
output should change.
That’s how Ron Garret debugged the Deep Space 1 spacecraft from the earth in
1999:
We were able to debug and fix a race condition that had not shown up during
ground testing. (Debugging a program running on a $100M piece of
hardware that is 100 million miles away is an interesting experience. Having
a read-eval-print loop running on the spacecraft proved invaluable in finding
and fixing the problem.
References
“How to understand and use Common Lisp”, chap. 30, David Lamkins
(book download from author’s site)
Malisper: debugging Lisp series
Two Wrongs: debugging Common Lisp in Slime
Slime documentation: connecting to a remote Lisp
cvberrycom: remotely modifying a running Lisp program using Swank
Ron Garret: Lisping at the JPL
the Remote Agent experiment: debugging code from 60 million miles away
(youtube) (“AMA” on reddit)
Performance Tuning and Tips
Many Common Lisp implementations translate the source code into
assembly language, so the performance is really good compared with some
other interpreted languages.
Finding Bottlenecks
Acquiring Execution Time
The macro time is very useful for finding out bottlenecks. It takes a form,
evaluates it and prints timing information in *trace-output*, as shown
below:
* (defun collect (start end)
"Collect numbers [start, end] as list."
(loop for i from start to end
collect i))
Evaluation took:
0.000 seconds of real time
0.000001 seconds of total run time (0.000001 user, 0.000000 sys
100.00% CPU
3,800 processor cycles
0 bytes consed
By using the time macro it is fairly easy to find out which part of your
program takes too much time.
Please note that the timing information provided here is not guaranteed to be
reliable enough for marketing comparisons. It should only be used for tuning
purpose, as demonstrated in this chapter.
You might find sb-sprof more useful than the deterministic profiler
when profiling functions in the common-lisp-package, SBCL internals,
or code where the instrumenting overhead is excessive.
See also tracer, a tracing profiler for SBCL. Its output is suitable for display
in Chrome’s or Chromium’s Tracing Viewer (chrome://tracing).
The function disassemble takes a function and prints the compiled code of
it to *standard-output*. For example:
* (defun plus (a b)
(+ a b))
PLUS
* (disassemble 'plus)
; disassembly for PLUS
; Size: 37 bytes. Origin: #x52B8063B
; 3B: 498B5D60 MOV RBX, [R13+96] ; no-arg-parsing entry poi
; thread.binding-stack-poi
; 3F: 48895DF8 MOV [RBP-8], RBX
; 43: 498BD0 MOV RDX, R8
; 46: 488BFE MOV RDI, RSI
; 49: FF14250102 CALL QWORD PTR [#x52100] ; GENERIC-+
; 50: 488B75E8 MOV RSI, [RBP-24]
; 54: 4C8B45F0 MOV R8, [RBP-16]
; 58: 488BE5 MOV RSP, RBP
; 5B: F8 CLC
; 5C: 5D POP RBP
; 5D: C3 RET
; 5E: CC0F BREAK 15 ; Invalid argument count trap
* (disassemble 'plus)
Disassembly of function PLUS
2 required arguments
0 optional arguments
No rest parameter
No keyword parameters
4 byte-code instructions:
0 (LOAD&PUSH 2)
1 (LOAD&PUSH 2)
2 (CALLSR 2 55) ; +
5 (SKIP&RET 3)
NIL
It is because SBCL compiles the Lisp code into machine code, while CLISP
does not.
In general, declare expressions can occur only at the beginning of the bodies
of certain forms, or immediately after a documentation string if the context
allows. Also, the content of a declare expression is restricted to limited
forms. Here we introduce some of them that are related to performance
tuning.
Please keep in mind that these optimization skills introduced in this section
are strongly connected to the Lisp implementation selected. Always check
their documentation before using declare!
Lisp allows you to specify several quality properties for the compiler using
the declaration optimize. Each quality may be assigned a value from 0 to 3,
with 0 being “totally unimportant” and 3 being “extremely important”.
* (disassemble 'max-original)
; disassembly for MAX-ORIGINAL
; Size: 144 bytes. Origin: #x52D450EF
; 7A7: 8D46F1 lea eax, [rsi-15] ; no-arg
; 7AA: A801 test al, 1
; 7AC: 750E jne L0
; 7AE: 3C0A cmp al, 10
; 7B0: 740A jeq L0
; 7B2: A80F test al, 15
; 7B4: 7576 jne L5
; 7B6: 807EF11D cmp byte ptr [rsi-15], 29
; 7BA: 7770 jnbe L5
; 7BC: L0: 8D43F1 lea eax, [rbx-15]
; 7BF: A801 test al, 1
; 7C1: 750E jne L1
; 7C3: 3C0A cmp al, 10
; 7C5: 740A jeq L1
; 7C7: A80F test al, 15
; 7C9: 755A jne L4
; 7CB: 807BF11D cmp byte ptr [rbx-15], 29
; 7CF: 7754 jnbe L4
; 7D1: L1: 488BD3 mov rdx, rbx
; 7D4: 488BFE mov rdi, rsi
; 7D7: B9C1030020 mov ecx, 536871873 ; generic->
; 7DC: FFD1 call rcx
; 7DE: 488B75F0 mov rsi, [rbp-16]
; 7E2: 488B5DF8 mov rbx, [rbp-8]
; 7E6: 7E09 jle L3
; 7E8: 488BD3 mov rdx, rbx
; 7EB: L2: 488BE5 mov rsp, rbp
; 7EE: F8 clc
; 7EF: 5D pop rbp
; 7F0: C3 ret
; 7F1: L3: 4C8BCB mov r9, rbx
; 7F4: 4C894DE8 mov [rbp-24], r9
; 7F8: 4C8BC6 mov r8, rsi
; 7FB: 4C8945E0 mov [rbp-32], r8
; 7FF: 488BD3 mov rdx, rbx
; 802: 488BFE mov rdi, rsi
; 805: B929040020 mov ecx, 536871977 ; generic-=
; 80A: FFD1 call rcx
; 80C: 4C8B45E0 mov r8, [rbp-32]
; 810: 4C8B4DE8 mov r9, [rbp-24]
; 814: 488B75F0 mov rsi, [rbp-16]
; 818: 488B5DF8 mov rbx, [rbp-8]
; 81C: 498BD0 mov rdx, r8
; 81F: 490F44D1 cmoveq rdx, r9
; 823: EBC6 jmp L2
; 825: L4: CC0A break 10 ; error trap
; 827: 04 byte #X04
; 828: 13 byte #X13 ; OBJECT-NOT-REAL-ER
; 829: FE9B01 byte #XFE, #X9B, #X01 ; RBX
; 82C: L5: CC0A break 10 ; error trap
; 82E: 04 byte #X04
; 82F: 13 byte #X13 ; OBJECT-NOT-REAL-ER
; 830: FE1B03 byte #XFE, #X1B, #X03 ; RSI
; 833: CC0A break 10 ; error trap
; 835: 02 byte #X02
; 836: 19 byte #X19 ; INVALID-ARG-COUNT-
; 837: 9A byte #X9A ; RCX
* (defun max-with-speed-3 (a b)
(declare (optimize (speed 3) (safety 0)))
(max a b))
MAX-WITH-SPEED-3
* (disassemble 'max-with-speed-3)
; disassembly for MAX-WITH-SPEED-3
; Size: 92 bytes. Origin: #x52D452C3
; 3B: 48895DE0 mov [rbp-32], rbx ; n
; 3F: 488945E8 mov [rbp-24], rax
; 43: 488BD0 mov rdx, rax
; 46: 488BFB mov rdi, rbx
; 49: B9C1030020 mov ecx, 536871873 ; generic->
; 4E: FFD1 call rcx
; 50: 488B45E8 mov rax, [rbp-24]
; 54: 488B5DE0 mov rbx, [rbp-32]
; 58: 7E0C jle L1
; 5A: 4C8BC0 mov r8, rax
; 5D: L0: 498BD0 mov rdx, r8
; 60: 488BE5 mov rsp, rbp
; 63: F8 clc
; 64: 5D pop rbp
; 65: C3 ret
; 66: L1: 488945E8 mov [rbp-24], rax
; 6A: 488BF0 mov rsi, rax
; 6D: 488975F0 mov [rbp-16], rsi
; 71: 4C8BC3 mov r8, rbx
; 74: 4C8945F8 mov [rbp-8], r8
; 78: 488BD0 mov rdx, rax
; 7B: 488BFB mov rdi, rbx
; 7E: B929040020 mov ecx, 536871977 ; generic-=
; 83: FFD1 call rcx
; 85: 488B45E8 mov rax, [rbp-24]
; 89: 488B75F0 mov rsi, [rbp-16]
; 8D: 4C8B45F8 mov r8, [rbp-8]
; 91: 4C0F44C6 cmoveq r8, rsi
; 95: EBC6 jmp L0
As you can see, the generated assembly code is much shorter (92 bytes VS
144). The compiler was able to perform optimizations. Yet we can do better
by declaring types.
Type Hints
As mentioned in the Type System chapter, Lisp has a relatively powerful type
system. You may provide type hints so that the compiler may reduce the size
of the generated code.
* (defun max-with-type (a b)
(declare (optimize (speed 3) (safety 0)))
(declare (type integer a b))
(max a b))
MAX-WITH-TYPE
* (disassemble 'max-with-type)
; disassembly for MAX-WITH-TYPE
; Size: 42 bytes. Origin: #x52D48A23
; 1B: 488BF7 mov rsi, rdi ; n
; 1E: 488975F0 mov [rbp-16], rsi
; 22: 488BD8 mov rbx, rax
; 25: 48895DF8 mov [rbp-8], rbx
; 29: 488BD0 mov rdx, rax
; 2C: B98C030020 mov ecx, 536871820 ; generic-<
; 31: FFD1 call rcx
; 33: 488B75F0 mov rsi, [rbp-16]
; 37: 488B5DF8 mov rbx, [rbp-8]
; 3B: 480F4CDE cmovl rbx, rsi
; 3F: 488BD3 mov rdx, rbx
; 42: 488BE5 mov rsp, rbp
; 45: F8 clc
; 46: 5D pop rbp
; 47: C3 ret
The size of generated assembly code shrunk to about 1/3 of the size. What
about speed?
* (time (dotimes (i 10000) (max-original 100 200)))
Evaluation took:
0.000 seconds of real time
0.000107 seconds of total run time (0.000088 user, 0.000019 sys
100.00% CPU
361,088 processor cycles
0 bytes consed
You see, by specifying type hints, our code runs much faster!
If you try to evaluate a declare form in the top level, you might get the
following error:
Execution of a form compiled with errors.
Form:
(DECLARE (SPEED 3))
Compile-time error:
There is no function named DECLARE. References to DECLARE in s
(like starts of blocks) are unevaluated expressions, but here the
being evaluated, which invokes undefined behaviour.
[Condition of type SB-INT:COMPILED-PROGRAM-ERROR]
The macro declaim provides such possibility. It can be used as a top level
form in a file and the declarations will be made at compile-time.
* (declaim (optimize (speed 0) (safety 3)))
NIL
* (defun max-original (a b)
(max a b))
MAX-ORIGINAL
* (disassemble 'max-original)
; disassembly for MAX-ORIGINAL
; Size: 181 bytes. Origin: #x52D47D9C
...
* (defun max-original (a b)
(max a b))
MAX-ORIGINAL
* (disassemble 'max-original)
; disassembly for MAX-ORIGINAL
; Size: 142 bytes. Origin: #x52D4815D
If the function returns nil, its return type is null. This declaration does not
put any restriction on the types of arguments by itself. It only takes effect if
the provided arguments have the specified types – otherwise no error is
signaled and declaration has no effect. For example, the following
declamation states that if the argument to the function square is a fixnum,
the value of the function will also be a fixnum:
(declaim (ftype (function (fixnum) fixnum) square))
(defun square (x) (* x x))
Now let’s try to optimize the speed. The compiler will state that there is type
uncertainty:
(defun do-some-arithmetic (x)
(declare (optimize (speed 3) (debug 0) (safety 0)))
(the fixnum (+ x (square x))))
; compiling (DEFUN DO-SOME-ARITHMETIC ...)
; file: /tmp/slimeRzDh1R
in: DEFUN DO-SOME-ARITHMETIC
; (+ TEST-FRAMEWORK::X (TEST-FRAMEWORK::SQUARE TEST-FRAMEWORK
;
; note: forced to do GENERIC-+ (cost 10)
; unable to do inline fixnum arithmetic (cost 2) because:
; The first argument is a NUMBER, not a FIXNUM.
; unable to do inline (signed-byte 64) arithmetic (cost 5)
; The first argument is a NUMBER, not a (SIGNED-BYTE 64).
; etc.
;
; compilation unit finished
; printed 1 note
(disassemble 'do-some-arithmetic)
; disassembly for DO-SOME-ARITHMETIC
; Size: 53 bytes. Origin: #x52CD1D1A
; 1A: 488945F8 MOV [RBP-8], RAX ; no-arg-parsing
; 1E: 488BD0 MOV RDX, RAX
; 21: 4883EC10 SUB RSP, 16
; 25: B902000000 MOV ECX, 2
; 2A: 48892C24 MOV [RSP], RBP
; 2E: 488BEC MOV RBP, RSP
; 31: E8C2737CFD CALL #x504990F8 ; #<FDEFN SQUARE>
; 36: 480F42E3 CMOVB RSP, RBX
; 3A: 488B45F8 MOV RAX, [RBP-8]
; 3E: 488BFA MOV RDI, RDX
; 41: 488BD0 MOV RDX, RAX
; 44: E807EE42FF CALL #x52100B50 ; GENERIC-+
; 49: 488BE5 MOV RSP, RBP
; 4C: F8 CLC
; 4D: 5D POP RBP
; 4E: C3 RET
NIL
Now we can add a type declaration for x, so the compiler can assume that
the expression (square x) is a fixnum, and use the fixnum-specific +:
(defun do-some-arithmetic (x)
(declare (optimize (speed 3) (debug 0) (safety 0)))
(declare (type fixnum x))
(the fixnum (+ x (square x))))
(disassemble 'do-some-arithmetic)
Code Inline
The declaration inline replaces function calls with function body, if the
compiler supports it. It will save the cost of function calls but will potentially
increase the code size. The best situation to use inline might be those small
but frequently used functions. The following snippet shows how to
encourage and prohibit code inline.
;; The globally defined function DISPATCH should be open-coded,
;; if the implementation supports inlining, unless a NOTINLINE
;; declaration overrides this effect.
(declaim (inline dispatch))
(defun dispatch (x) (funcall (get (car x) 'dispatch) x))
;; Here is an example where inlining would be encouraged.
;; Because function DISPATCH was defined as INLINE, the code
;; inlining will be encouraged by default.
(defun use-dispatch-inline-by-default ()
(dispatch (read-command)))
Please note that when the inlined functions change, all the callers must be re-
compiled.
* (defun func-using-plus (a b)
(plus a b))
FUNC-USING-PLUS
* (defun func-using-plus-inline (a b)
(declare (inline plus))
(plus a b))
FUNC-USING-PLUS-INLINE
* (time
(dotimes (i 100000)
(func-using-plus 100 200)))
Evaluation took:
0.018 seconds of real time
0.017819 seconds of total run time (0.017800 user, 0.000019 sys
100.00% CPU
3 lambdas converted
71,132,440 processor cycles
6,586,240 bytes consed
* (time
(dotimes (i 100000)
(func-using-plus-inline 100 200)))
Evaluation took:
0.001 seconds of real time
0.000326 seconds of total run time (0.000326 user, 0.000000 sys
0.00% CPU
1,301,040 processor cycles
0 bytes consed
The inlining is not enabled by default because once inlined, changes made to
methods will not be reflected.
When this feature is present, all inlinable generic functions are inlined unless
it is declared notinline.
Block compilation
SBCL got block compilation on version 2.0.2, which was in CMUCL since
1991 but a little forgotten since.
But local calls, the ones inside a top-level functions (for example lambdas,
labels and flets) are fast.
These calls are more ‘static’ in the sense that they are treated more like
function calls in static languages, being compiled “together” and at the
same time as the local functions they reference, allowing them to be
optimized at compile-time. For example, argument checking can be
done at compile time because the number of arguments of the callee is
known at compile time, unlike in the full call case where the function,
and hence the number of arguments it takes, can change dynamically at
runtime at any point. Additionally, the local call calling convention can
allow for passing unboxed values like floats around, as they are put into
unboxed registers never used in the full call convention, which must use
boxed argument and return value registers.
So enabling block compilation kind of turns your code into a giant labels
form.
(defun foo (x y)
(print (bar x y))
(bar x y))
(defun bar (x y)
(+ x y))
you [will] see that FOO and BAR are now compiled into the same
component (with local calls), and both have valid external entry points.
This improves locality of code quite a bit and still allows calling both
FOO and BAR externally from the file (e.g. in the REPL). […]
For more explanations, I refer you to the mentioned blog post, the current
de-facto documentation for SBCL, in addition to CMUCL’s documentation
(note that the form-by-form level granularity in CMUCL ((declaim
(start-block ...)) ... (declaim (end-block ..))) is missing in
SBCL, at the time of writing).
Finally, be aware that “block compiling and inlining currently does not
interact very well [in SBCL]”.
Scripting. Command line
arguments. Executables.
Using a program from a REPL is fine and well, but once it’s ready we’ll
surely want to call it from the terminal. We can run Lisp scripts for this.
Lisp implementations differ in their processes, but they all create self-
contained executables, for the architecture they are built on. The final user
doesn’t need to install a Lisp implementation, he can run the software right
away.
Start-up times are near to zero, specially with SBCL and CCL.
Binaries size are large-ish. They include the whole Lisp including its
libraries, the names of all symbols, information about argument lists to
functions, the compiler, the debugger, source code location information, and
more.
Note that we can similarly build self-contained executables for web apps.
Say you don’t bother with an .asd project definition yet, you just want to
write a quick script, but you need to load a quicklisp dependency. You’ll
need a bit more ceremony:
#!/usr/bin/env -S sbcl --script
(require :uiop)
Also note that when you put a ql:quickload in the middle of your code,
you can’t load the file anymore, you can’t C-c C-k from your editor. This is
because the reader will see the “quickload” without running it yet, then sees
“str:concat”, a call to a package that doesn’t exist (it wasn’t loaded yet).
Common Lisp has you covered, with a form that executes code during the
read phase:
;; you shouldn't need this. Use an .asd system definition!
(eval-when (:load-toplevel :compile-toplevel :execute)
(ql:quickload "str" :silent t))
but ASDF project definitions are here for a reason. Find me another
language that makes you install dependencies in the middle of the
application code.
(sb-ext:save-lisp-and-die #P"path/name-of-executable"
:toplevel #'my-app:main-function
:executable t)
We must run the command from a simple SBCL repl, from the terminal.
That gives:
(asdf:load-asd "my-app.asd")
(ql:quickload "my-app")
(sb-ext:save-lisp-and-die #p"my-app-binary"
:toplevel #'my-app:main
:executable t)
From the command line, or from a Makefile, use --load and --eval:
build:
sbcl --load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval "(sb-ext:save-lisp-and-die #p\"my-app\"
:toplevel #'my-app:main :executable t)"
With ASDF
Now that we’ve seen the basics, we need a portable method. Since its
version 3.1, ASDF allows to do that. It introduces the make command, that
reads parameters from the .asd. Add this to your .asd declaration:
:build-operation "program-op" ;; leave as is
:build-pathname "<here your final binary name>"
:entry-point "<my-package:main-function>"
build:
$(LISP) --load my-app.asd \
--eval '(ql:quickload :my-app)' \
--eval '(asdf:make :my-app)' \
--eval '(quit)'
All this is good, you can create binaries that work on your machine… but
maybe not on someone else’s or on your server. Your program probably
relies on C shared libraries that are defined somewhere on your filesystem.
For example, libssl might be located on
/usr/lib/x86_64-linux-gnu/libssl.so.1.1
It will create a bin/ directory with your binary and the required foreign
libraries. It will auto-discover the ones your program needs, but you can
also help it (or tell it to not do so much).
Its use is very close to the above recipe with asdf:make and the .asd
project configuration. Use this:
:defsystem-depends-on (:deploy) ;; (ql:quickload "deploy") befo
:build-operation "deploy-op" ;; instead of "program-op"
:build-pathname "my-application-name" ;; doesn't change
:entry-point "my-package:my-start-function" ;; doesn't change
Success!
A note regarding libssl. It’s easier, on Linux at least, to rely on your OS’
current installation, so we’ll tell Deploy to not bother shipping it (nor
libcrypto):
The day you want to ship a foreign library that Deploy doesn’t find, you can
instruct it like this:
(deploy:define-library cl+ssl::libcrypto
;; ^^^ CFFI system name.
;; Find it with a call to "apropos".
:path "/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1")
A last remark. Once you built your binary and you run it for the first time,
you might get a funny message from ASDF that tries to upgrade itself, finds
nothing into a ~/common-lisp/asdf/ repository, and quits. To tell it to not
upgrade itself, add this into your .asd:
;; Tell ASDF to not update itself.
(deploy:define-hook (:deploy asdf) (directory)
(declare (ignorable directory))
#+asdf (asdf:clear-source-registry)
#+asdf (defun asdf:upgrade-asdf () nil))
You can also silence Deploy’s start-up messages by adding this in your
build script, before asdf:make is called:
This is how we can make our application easily installable by others, with a
ros install my-app. See Roswell’s documentation.
Be aware that ros build adds core compression by default. That adds a
significant startup overhead of the order of 150ms (for a simple app, startup
time went from about 30ms to 180ms). You can disable it with ros build
<app.ros> --disable-compression. Of course, core compression reduces
your binary size significantly. See the table below, “Size and startup times
of executables per implementation”.
Example usage:
buildapp --output myapp \
--asdf-path . \
--asdf-tree ~/quicklisp/dists \
--load-system my-app \
--entry my-app:main
Note that this runs the production webserver, not a development one, so we
can run the binary on our VPS right away and access the application from
the outside.
We have one thing to take care of, it is to find and put the thread of the
running web server on the foreground. In our main function, we can do
something like this:
(defun main ()
(start-app :port 9003) ;; our start-app, for example clack:cla
;; let the webserver run.
;; warning: hardcoded "hunchentoot".
;; You can simply run (sleep most-positive-fixnum)
(handler-case (bt:join-thread (find-if (lambda (th)
(search "hunchentoot
(bt:all-threads)))
;; Catch a user's C-c
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+clisp system::simple-interrupt-condition
#+ecl ext:interactive-interrupt
#+allegro excl:interrupt-signal
() (progn
(format *error-output* "Aborting.~&")
(clack:stop *server*)
(uiop:quit)))
(error (c) (format t "Woops, an unknown error occured:~&~a~&
CCL’s binaries seem to be as fast to start up as SBCL and nearly half the
size.
| program size | implementation | CPU | startup time |
|--------------+----------------+------+--------------|
| 28 | /bin/true | 15% | .0004 |
| 1005 | ecl | 115% | .5093 |
| 48151 | sbcl | 91% | .0064 |
| 27054 | ccl | 93% | .0060 |
| 10162 | clisp | 96% | .0170 |
| 4901 | ecl.big | 113% | .8223 |
| 70413 | sbcl.big | 93% | .0073 |
| 41713 | ccl.big | 95% | .0094 |
| 19948 | clisp.big | 97% | .0259 |
Regarding compilation times, CCL is famous for being fast in that regards.
ECL is more involved and takes the longer to compile of these three
implementations.
Your SBCL must be built with core compression, see the documentation:
Saving-a-Core-Image
Is it the case ?
(find :sb-core-compression *features*)
:SB-CORE-COMPRESSION
With SBCL
With ASDF
However, we prefer to do this with ASDF (or rather, UIOP). Add this in
your .asd:
#+sb-core-compression
(defmethod asdf:perform ((o asdf:image-op) (c asdf:system))
(uiop:dump-image (asdf:output-file o c)
:executable t
:compression t))
With Deploy
Also, the Deploy library can be used to build a fully standalone application.
It will use compression if available.
And voilà !
That’s good, but we also want to parse the arguments, have facilities to
check short and long options, build a help message automatically, etc.
We chose the Clingon library, because it may have the richest feature set:
it handles subcommands,
it supports various kinds of options (flags, integers, booleans, counters,
enums…),
it generates Bash and Zsh completion files as well as man pages,
it is extensible in many ways,
we can easily try it out on the REPL
etc
(ql:quickload “clingon”)
we first declare the options that our application accepts, their kind
(flag, string, integer…), their long and short names and the required
ones.
we ask Clingon to parse the command-line options and run our app.
Declaring options
So first, let’s create options. Clingon already handles “–help” for us, but not
the short version. Here’s how we use clingon:make-option to create an
option:
(clingon:make-option
:flag ;; <--- option kind. A "flag" does not exp
:description "short help"
;; :long-name "help" ;; <--- long name, sans the "--" prefix, b
:short-name #\h ;; <--- short name, a character
;; :required t ;; <--- is this option always required? In
:key :help) ;; < the internal reference to use with
:key :help) ;; <--- the internal reference to use with
We’ll create a second option (“–name” or “-n” with a parameter) and we put
everything in a litle function.
;; The naming with a "/" is just our convention.
(defun cli/options ()
"Returns a list of options for our main command"
(list
(clingon:make-option
:flag
:description "short help."
:short-name #\h
:key :help)
(clingon:make-option
:string ;; <--- string type: expects one parame
:description "Name to greet"
:short-name #\n
:long-name "name"
:env-vars '("USER") ;; <-- takes this default value if t
:initial-value "lisper" ;; <-- default value if nothing else
:key :name)))
The second option we created is of kind :string. This option expects one
argument, which will be parsed as a string. There is also :integer, to parse
the argument as an integer.
There are more option kinds of Clingon, which you will find on its good
documentation: :choice, :enum, :list, :filepath, :switch and so on.
Top-level command
And finally, we’ll use clingon:run in our main function (the entry point of
our binary) to parse the command-line arguments, and apply our
command’s logic. During development, we can also manually call
clingon:parse-command-line to try things out.
It works!
We can even inspect this command object, we would see its properties
(name, hooks, description, context…), its list of options, etc.
In that case, we are dropped into the interactive debugger, which says
Unknown option -x of kind SHORT
[Condition of type CLINGON.CONDITIONS:UNKNOWN-OPTION]
Last but not least, we can see how Clingon prints our CLI tool’s usage
information:
CL-USER> (clingon:print-usage (cli/command) t)
NAME:
hello - say hello
USAGE:
hello [options] [arguments ...]
OPTIONS:
--help display usage information and exit
--version display version and exit
-h short help.
-n, --name <VALUE> Name to greet [default: lisper] [env:
$USER]
AUTHORS:
John Doe <[email protected]
LICENSE:
BSD 2-Clause
We can tweak the “USAGE” part with the :usage key parameter of the lop-
level command.
Handling options
It is with them that we will write the handler of our top-level command:
(defun cli/handler (cmd)
"The handler function of our top-level command"
(let ((free args (clingon:command arguments cmd))
(let ((free-args (clingon:command-arguments cmd))
(name (clingon:getopt cmd :name))) ;; <-- using the opt
(format t "Hello, ~a!~%" name)
(format t "You have provided ~a more free arguments~%"
(length free-args))
(format t "Bye!~%")))
We now only have to write the main entry point of our binary and we’re
done.
This can be any function, but to use Clingon, use its run function:
(defun main ()
"The main entrypoint of our CLI program"
(clingon:run (cli/command)))
To use this main function as your binary entry point, see above how to build
a Common Lisp binary. A reminder: set it in your .asd system declaration:
:entry-point "my-package::main"
And that’s about it. Congratulations, you can now properly parse command-
line arguments!
If your application needs some clean-up logic, you can use an unwind-
protect form. However, it might not be appropriate for all cases, so
Clingon advertises to use the with-user-abort helper library.
We built a simple binary, we ran it and pressed C-c. Let’s read the
stacktrace:
$ ./my-app
sleep…
^C
debugger invoked on a SB-SYS:INTERACTIVE-INTERRUPT in thread
<== condition name
#<THREAD "main thread" RUNNING {1003156A03}>:
Interactive interrupt at #x7FFFF6C6C170.
This code is only for SBCL though. We know about trivial-signal, but we
were not satisfied with our test yet. So we can use something like this:
(handler-case
(run-my-app free-args)
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+clisp system::simple-interrupt-condition
#+ecl ext:interactive-interrupt
#+allegro excl:interrupt-signal
()
(opts:exit)))
See also
SBCL-GOODIES - Allows to distribute SBCL binaries with foreign
libraries: libssl, libcrypto and libfixposix are statically baked in.
This removes the need of Deploy, when only these three foreign
libraries are used.
it was released on February, 2023.
Credit
cl-torrents’ tutorial
lisp-journey/web-dev
Testing the code
So you want to easily test the code you’re writing? The following recipe
covers how to write automated tests and see their code coverage. We also
give pointers to plug those in modern continuous integration services like
GitHub Actions, Gitlab CI, Travis CI or Coveralls.
Previously on the Cookbook, the recipe was cooked with Prove. It used to be
a widely liked testing framework but, because of some shortcomings, its
repository was later archived. Its successor Rove is not stable enough and
lacks some features, so we didn’t pick it. There are also some other testing
frameworks to explore if you feel like it.
FiveAM has an API documentation. You may inspect it or simply read the
docstrings in code. Most of the time, they would provide sufficient
information that answers your questions… if you didn’t find them here. Let’s
get started.
1. A check is a single assertion that checks that its argument is truthy. The
most used check is is. For example, (is (= 2 (+ 1 1))).
2. A test is the smallest runnable unit. A test case may contain multiple
checks. Any check failure leads to the failure of the whole test.
3. A suite is a collection of tests. When a suite is run, all tests inside
would be performed. A suite allows paternity, which means that
running a suite will run all the tests defined in it and in its children
suites.
A simple code sample containing the 3 basic blocks mentioned above can be
shown as follows:
(def-suite* my-suite)
(test my-test
(is (= 2 (+ 1 1))))
It is totally up to the user to decide the hierarchy of tests and suites. Here we
mainly focus on the usage of FiveAM.
Suppose we have built a rather complex system and the following functions
are part of it:
;; We have a custom "file doesn't exist" condition.
(define-condition file-not-existing-error (error)
((filename :type string :initarg :filename :reader filename)))
The package is named fiveam with a nickname 5am. For the sake of
simplicity, we will ignore the package prefix in the following code samples.
It is like we :used fiveam in our test package definition. You can also follow
along in the REPL with (use-package :fiveam).
The code below defines a suite named my-system. We will use it as the root
suite for the whole system.
(def-suite my-system
:description "Test my system")
Defining tests
Before diving into tests, here is a brief introduction of the available checks
you may use inside tests:
The is macro is likely the most used check. It simply checks if the
given expression returns a true value and generates a test-passed or
test-failure result accordingly.
The skip macro takes a reason and generates a test-skipped result.
The signals macro checks if the given condition was signaled during
execution.
There is also:
Please note that all the checks accept an optional reason, as string, that can
be formatted with format directives (see more below). When omitted,
FiveAM generates a report that explains the failure according to the
arguments passed to the function.
The test macro provides a simple way to define a test with a name.
Note that below, we expect two files to exist: /tmp/hello.txt should contain
“hello” and /tmp/empty.txt should be empty.
;; Our first "base" case: we read a file that contains "hello".
(test read-file-as-string-normal-file
(let ((result (read-file-as-string "/tmp/hello.txt")))
;; Tip: put the expected value as the first argument of = or
;; FiveAM generates a more readable report following this con
(is (string= "hello" result))))
In the above code, three tests were defined with 5 checks in total. Some
checks were actually redundant for the sake of demonstration. You may put
all the checks in one big test, or in multiple scenarios. It is up to you.
The macro test is a convenience for def-test to define simple tests. You
may read its docstring for a more complete introduction, for example to read
about :depends-on.
Running tests
FiveAm provides multiple ways to run tests. The macro run! is a good start
point during development. It accepts a name of suite or test and run it, then
prints testing report in standard output. Let’s run the tests now!
(run! 'my-system)
; Running test suite MY-SYSTEM
; Running test READ-FILE-AS-STRING-EMPTY-FILE ..
; Running test READ-FILE-AS-STRING-NON-EXISTING-FILE ..
; Running test READ-FILE-AS-STRING-NORMAL-FILE .
; Did 5 checks.
; Pass: 5 (100%)
; Skip: 0 ( 0%)
; Fail: 0 ( 0%)
; => T, NIL, NIL
Under normal circumstances, a test is written and compiled (with the usual
C-c C-c in Slime) separately from the moment it is run. If you want to run
the test when it is defined (with C-c C-c), set this:
(setf fiveam:*run-test-when-defined* t)
We said earlier that a check accepts an optional custom reason that can be
formatted with format directives. Here’s a simple example.
When we run! it, we see this somewhat lengthy but informative output (and
that’s very important):
Running test suite NIL
Running test SIMPLE-MATHS f
Did 1 check.
Pass: 0 ( 0%)
Skip: 0 ( 0%)
Fail: 1 (100%)
Failure Details:
--------------------------------
SIMPLE-MATHS []:
(+ 1 1)
evaluated to
2
which is not
to
--------------------------------
Failure Details:
--------------------------------
SIMPLE-MATHS []:
Maths should work, right? T. Another parameter is: :FOO
--------------------------------
Fixtures
FiveAM also provides a feature called fixtures for setting up testing context.
The goal is to ensure that some functions are not called and always return the
same result. Think functions hitting the network: you want to isolate the
network call in a small function and write a fixture so that in your tests, this
function always returns the same, known result. (But if you do so, you might
also need an “end to end” test that tests with real data and all your code…)
Random checking
The goal of random testing is to assist the developer in generating test cases,
and thus, to find cases that the developer would not have thought about.
(funcall (gen-float))
9.220082e37
And we have a function to run 100 checks, taking each turn a new value
from the given generators: for-all:
(test randomtest
(for-all ((a (gen-integer :min 1 :max 10))
(b (gen-integer :min 1 :max 10)))
"Test random tests."
(is (<= a b))))
When you run! 'randomtest this, I expect you will hit an error. You can’t
possibly always get a lower than b, can you?
(asdf:defsystem mitogrator/test
;; Parts omitted.
:perform (test-op (op c)
(symbol-call :fiveam :run!
(find-symbol* :my-system :my-sys
The last line tells ASDF to load symbol :my-system from my-system/test
package and call fiveam:run!. It fact, it is equivalent to (run! 'my-system)
as mentioned above.
Until now, we ran our tests from our editor’s REPL. How can we run them
from a terminal window?
and you could invoke it like so, from a source file or from a Makefile:
rlwrap sbcl --non-interactive --load mysystem.asd --eval '(ql:qui
;; we assume Quicklisp is installed and loaded. This can be done
Before going that route however, have a look at the CI-Utils tool that we
use in the Continuous Integration section below. It provides a run-fiveam
command that can do all that for you.
But let us highlight something you’ll have to take care of if you ran your
tests like this: the exit code. Indeed, (run!) prints a report, but it doesn’t say
to your Lisp wether the tests were successful or not, and wether to exit with
an exit code of 0 (for success) or more (for errors). So, if your testst were run
on a CI system, the CI status would be always green, even if tests failed. To
remedy that, replace run! by:
(let ((result (run!)))
(cond
((null result)
(log:info "Tests failed!") ;; FiveAM printed the report alr
(uiop:quit 1))
(t
(log:info "All pass.")
(uiop:quit))))
Check with echo $? on your shell that the exit code is correct.
It is possible to generate our own testing report. The macro run! is nothing
more than a composition of explain! and run.
Instead of generating a testing report like its cousin run!, the function run
runs suite or test passed in and returns a list of test-result instance,
usually instances of test-failure or test-passed sub-classes.
A class text-explainer is defined as a basic class for testing report
generator. A generic function explain is defined to take a text-plainer
instance and a test-result instance (returned by run) and generate testing
report. The following 2 code snippets are equivalent:
(run! 'read-file-as-string-non-existing-file)
Use :backtrace to print a backtrace, continue to run the following tests and
print FiveAM’s report.
The default is nil: carry on the tests execution and print the report.
Code coverage
A code coverage tool produces a visual output that allows to see what parts
of our code were tested or not:
Such capabilities are included into Lisp implementations. For example,
SBCL has the sb-cover module and the feature is also built-in in CCL or
LispWorks.
Continuous Integration
Continuous Integration is important to run automatic tests after a commit or
before a pull request, to run code quality checks, to build and distribute your
software… well, to automate everything about software.
We’ll also quickly show how to publish coverage reports to the Coveralls
service. cl-coveralls helps to post our coverage to the service.
We’ll use CI-Utils, a set of utilities that comes with many examples. It also
explains more precisely what is a CI system and compares a dozen of
services.
It relies on Roswell to install the Lisp implementations and to run the tests.
They all are installed with a bash one-liner:
curl -L
https://raw.githubusercontent.com/roswell/roswell/release/scripts/install-for-
ci.sh | bash
It also ships with a test runner for FiveAM, which eases some rough parts
(like returning the right error code to the terminal). We install ci-utils with
Roswell, and we get the run-fiveam executable.
addons:
homebrew:
update: true
packages:
- roswell
apt:
packages:
- libc6-i386 # needed for a couple implementations
- default-jre # needed for abcl
fast_finish: true
install:
- curl -L https://raw.githubusercontent.com/roswell/roswell/rel
- ros install ci-utils #for run-fiveam
# - ros install rove #for [run-] rove
script:
- run-fiveam -e t -l foo/test :foo-tests
#- rove foo.asd
Below with Gitlab CI, we’ll use a Docker image that already contains the
Lisp binaries and every Debian package required to build Quicklisp libraries.
Gitlab CI
image: clfoundation/sbcl:latest
before_script:
- install-quicklisp
- git clone https://github.com/foo/bar ~/quicklisp/local-
projects/
test:
script:
- make test
Gitlab CI is based on Docker. With image we tell it to use the latest tag of
the clfoundation/sbcl image. This includes the latest version of SBCL, many
OS packages useful for CI purposes, and a script to install Quicklisp. Gitlab
will load the image, clone our project and put us at the project root with
administrative rights to run the rest of the commands.
We can try locally ourselves. If we already installed Docker and started its
daemon (sudo service docker start), we can do:
This will download the lisp image (±300MB compressed), mount some local
code in the image where indicated, and drop us in bash. Now we can try a
make test.
Here is a more complete example that tests against several CL
implementations in parallel:
variables:
IMAGE_TAG: latest
QUICKLISP_ADD_TO_INIT_FILE: "true"
QUICKLISP_DIST_VERSION: latest
image: clfoundation/$LISP:$IMAGE_TAG
stages:
- test
- build
before_script:
- install-quicklisp
- git clone https://github.com/foo/bar ~/quicklisp/local-projec
.test:
stage: test
script:
- make test
abcl test:
extends: .test
variables:
LISP: abcl
ccl test:
extends: .test
variables:
LISP: ccl
ecl test:
extends: .test
variables:
LISP: ecl
sbcl test:
extends: .test
variables:
LISP: sbcl
build:
stage: build
variables:
LISP: sbcl
only:
- tags
script:
- make build
artifacts:
paths:
- some-file-name
Here we defined two stages (see environments), “test” and “build”, defined
to run one after another. A “build” stage will start only if the “test” one
succeeds.
“build” is asked to run only when a new tag is pushed, not at every commit.
When it succeeds, it will make the files listed in artifacts’s paths
available for download. We can download them from Gitlab’s Pipelines UI,
or with an url. This one will download the file “some-file-name” from the
latest “build” job:
https://gitlab.com/username/project-name/-/jobs/artifacts/master/raw/some-
file-name?job=build
SourceHut
It’s very easy to set up SourceHut’s CI system for Common Lisp. Here is a
minimal .build.yml file that you can test via the build manifest tester:
image: archlinux
packages:
- sbcl
- quicklisp
sources:
- https://git.sr.ht/~fosskers/cl-transducers
tasks:
# If our project isn't in the special `common-lisp` directory, qu
# be able to find it for loading.
- move: |
mkdir common-lisp
mv cl-transducers ~/common-lisp
- quicklisp: |
sbcl --non-interactive --load /usr/share/quicklisp/quicklisp
- test: |
cd common-lisp/cl-transducers
sbcl --non-interactive --load ~/quicklisp/setup.lisp --load r
Since the Docker image we’re given is nearly empty, we need to install sbcl
and quicklisp manually. Notice also that we’re running a run-tests.lisp
file to drive the tests. Here’s what it could look like:
(ql:quickload :transducers/tests)
(in-package :transducers/tests)
References
Tutorial: Working with FiveAM, by Tomek “uint” Kurcz
Comparison of Common Lisp Testing Frameworks, by Sabra Crolleton.
the CL Foundation Docker images
See also
cl-cookieproject, a project skeleton with a FiveAM tests structure.
Database Access and Persistence
The Database section on the Awesome-cl list is a resource listing popular
libraries to work with different kind of databases. We can group them
roughly in four categories:
We’ll begin with an overview of Mito. If you must work with an existing
DB, you might want to have a look at cl-dbi and clsql. If you don’t need a
SQL database and want automatic persistence of Lisp objects, you also
have a choice of libraries.
Overview
Mito is “an ORM for Common Lisp with migrations, relationships and
PostgreSQL support”.
connecting to the DB
writing CLOS classes to define models
running migrations to create or alter tables
creating objects, saving same in the DB,
and iterating.
Connecting to a DB
Models
Defining models
In Mito, you can define a class which corresponds to a database table with
the deftable macro:
(mito:deftable user ()
((name :col-type (:varchar 64))
(email :col-type (or (:varchar 128) :null)))
The deftable macro automatically adds some slots: a primary key named
id if there’s no primary key, and created_at and updated_at for recording
timestamps. Specifying (:auto-pk nil) and (:record-timestamps nil)
in the deftable form will disable these behaviours. A deftable class will
also come with initializers, named after the slot, and accessors, of form
<class-name>-<slot-name>, for each named slot. For example, for the
name slot in the above table definition, the initarg :name will be added to the
constuctor, and the accessor user-name will be created.
(c2mop:class-direct-superclasses *)
;=> (#<STANDARD-CLASS MITO.DAO.TABLE:DAO-CLASS>)
This may be useful when you define methods which can be applied for all
table classes.
For more information on using the Common Lisp Object System, see the
clos page.
So a helper function:
(defun ensure-tables ()
(mapcar #'mito:ensure-table-exists '(user foo bar)))
When you alter the model you’ll need to run a DB migration, see the next
section.
Fields
Fields types
:bytea,
Optional fields
Field constraints
Relationships
One-to-one
One-to-many, many-to-one
The relationship is defined with a foreign key on the “many” side linking
back to the “one” side. Here the tweet class defines a user foreign key, so a
tweet can only have one user. You didn’t need to edit the user class.
Many-to-many
And, thanks to the join table, we can store more information about the
relationship.
A user can have many books, and a book (as the title, not the physical copy)
is likely to be in many people’s library. Here’s the intermediate class:
(mito:deftable user-books ()
((user :col-type user)
(book :col-type book)))
But someone may very well own many copies of one book. This is an
information we can store in the join table:
(mito:deftable user-books ()
((user :col-type user)
(book :col-type book)
;; Set the quantity, 1 by default:
(quantity :col-type :integer)))
(mito:table-definition 'temporary-user)
;=> (#<SXQL-STATEMENT: CREATE TABLE temporary_user (
; id BIGSERIAL NOT NULL PRIMARY KEY,
; name VARCHAR(64) NOT NULL,
; email VARCHAR(128) NOT NULL,
; registered_at TIMESTAMP NOT NULL,
; created_at TIMESTAMP,
; updated_at TIMESTAMP,
; UNIQUE (email)
; )>)
If you need a ‘template’ for tables which aren’t related to any database
tables, you can use DAO-TABLE-MIXIN in a defclass form. The has-email
class below will not create a table.
(defclass has-email ()
((email :col-type (:varchar 128)
:initarg :email
:accessor object-email))
(:metaclass mito:dao-table-mixin)
(:unique-keys email))
;=> #<MITO.DAO.MIXIN:DAO-TABLE-MIXIN COMMON-LISP-USER::HAS-EMAIL
(mito:table-definition 'user)
;=> (#<SXQL-STATEMENT: CREATE TABLE user (
; id BIGSERIAL NOT NULL PRIMARY KEY,
; name VARCHAR(64) NOT NULL,
; email VARCHAR(128) NOT NULL,
; created_at TIMESTAMP,
; updated_at TIMESTAMP,
; UNIQUE (email)
; )>)
Troubleshooting
it is certainly because you first wrote a class definition and then added the
Mito metaclass and tried to evaluate the class definition again.
If this happens, you must remove the class definition from the current
package:
(setf (find-class 'foo) nil)
or, with the Slime inspector, click on the class and find the “remove”
button.
Migrations
(ensure-table-exists 'user)
;-> ;; CREATE TABLE IF NOT EXISTS "user" (
; "id" BIGSERIAL NOT NULL PRIMARY KEY,
; "name" VARCHAR(64) NOT NULL,
; "email" VARCHAR(128),
; "created_at" TIMESTAMP,
; "updated_at" TIMESTAMP
; ) () [0 rows] | MITO.DAO:ENSURE-TABLE-EXISTS
Queries
Creating objects
You should not export the user class and create objects outside of its
package (it is good practice anyway to keep all database-related operations
in say a models package and file). You should instead use a helper function:
(defun make-user (&key name)
(make-instance 'user :name name))
Updating fields
Deleting
(mito:delete-dao me)
;-> ;; DELETE FROM `user` WHERE (`id` = ?) (1) [0 rows] | MITO.D
;; or:
(mito:delete-by-values 'user :id 1)
;-> ;; DELETE FROM `user` WHERE (`id` = ?) (1) [0 rows] | MITO.D
Get the primary key value
(mito:object-id me)
;=> 1
Count
(mito:count-dao 'user)
;=> 1
Find one
Find all
Find by relationship
As seen above:
(mito:find-dao 'tweet :user *user*)
Custom queries
It is with select-dao that you can write more precise queries by giving it
SxQL statements.
Example:
(select-dao 'tweet
(where (:like :status "%Japan%")))
another:
(select (:id :name :sex)
(from (:as :person :p))
(where (:and (:>= :age 18)
(:< :age 65)))
(order-by (:desc :age)))
For the example sake, an author is a string, not a link to another table:
(mito:deftable book ()
((title :col-type (:varchar 128))
(author :col-type (:varchar 128))
(ean :col-type (or (:varchar 128) :null))))
You want to add a clause that searches on both fields for each word.
(defun find-books (&key query (order :desc))
"Return a list of books.
If a query string is given, search on both the title
and the author fields."
(mito:select-dao 'book
(when (str:non-blank-string-p query)
(sxql:where
`(:and
,@(loop for word in (str:words query)
:collect `(:or (:like :title
,(str:concat "%" word "%"))
(:like :authors
,(str:concat "%" word "%")))
(sxql:order-by `(,order :created-at))))
By the way, we are still using a LIKE statement, but with a non-small dataset
you’ll want to use your database’s full text search engine.
Clauses
(group-by :sex)
(limit 0 10)
Operators
:not
:is-null, :not-null
:asc, :desc
:distinct
:=, :!=
:<, :>, :<= :>=
:a<, :a>
:as
:in, :not-in
:like
:and, :or
:+, :-, :* :/ :%
:raw
Triggers
Inflation/Deflation
Eager loading
One of the pains in the neck to use ORMs is the “N+1 query” problem.
;; BAD EXAMPLE
(defvar *tweets-contain-japan*
(select-dao 'tweet
(where (:like :status "%Japan%"))))
This example sends a query to retrieve a user like “SELECT * FROM user
WHERE id = ?” at each iteration.
To prevent this performance issue, add includes to the above query which
only sends a single WHERE IN query instead of N queries:
;; GOOD EXAMPLE with eager loading
(defvar *tweets-contain-japan*
(select-dao 'tweet
(includes 'user)
(where (:like :status "%Japan%"))))
;-> ;; SELECT * FROM `tweet` WHERE (`status` LIKE ?) ("%Japan%")
;-> ;; SELECT * FROM `user` WHERE (`id` IN (?, ?, ?)) (1, 3, 12)
;=> (#<TWEET {1003513EC3}> #<TWEET {1007BABEF3}> #<TWEET {1007BB
Schema versioning
$ ros install mito
$ mito
Usage: mito command [option...]
Commands:
generate-migrations
migrate
Options:
-t, --type DRIVER-TYPE DBI driver type (one of
"mysql", "postgres" or "sqlite3")
-d, --database DATABASE-NAME Database name to use
-u, --username USERNAME Username for RDBMS
-p, --password PASSWORD Password for RDBMS
-s, --system SYSTEM ASDF system to load
(several -s's allowed)
-D, --directory DIRECTORY Directory path to keep
migration SQL files (default:
"/Users/nitro_idiot/Programs/lib/mito/db/")
--dry-run List SQL expressions to
migrate
Introspection
(defparameter user-slots *)
(mito.class.column:table-column-not-null-p
(first user-slots))
;; T
(mito.class.column:table-column-not-null-p
(second user-slots))
;; NIL
Testing
We don’t want to test DB operations against the production one. We need to
create a temporary DB before each test.
The macro below creates a temporary DB with a random name, creates the
tables, runs the code and connects back to the original DB connection.
(defpackage my-test.utils
(:use :cl)
(:import-from :my.models
:*db*
:*db-name*
:connect
:ensure-tables-exist
:migrate-all)
(:export :with-empty-db))
(in-package my-test.utils)
See also
exploring an existing (PostgreSQL) database with postmodern
mito-attachment
mito-auth
Finally, a key part in building software is how to build it and ship it to users.
Here also, we can build self-contained binaries, for the three main operating
systems, that users can run with a double click.
We aim here to give you the relevant information to help you choose the
right GUI framework and to put you on tracks. Don’t hesitate to contribute,
to send more examples and to furnish the upstream documentations.
Introduction
In this recipe, we’ll present the following GUI toolkits:
NEW! 🎉
Windows, on Linux platforms, Free BSD and on the Mac.
since Allegro CL 10.1 (released in March of 2022), the
IDE, and the Common Graphics GUI toolkit, runs in the browser.
It is called CG/JS.
CCL’s built-in Cocoa interface, used to build applications such as
Opusmodus.
Clozure CL’s built-in Objective-C bridge and CocoaInterface, a Cocoa
interface for CCL. Build Cocoa user interface windows dynamically
using Lisp code and bypass the typical Xcode processes.
the bridge is good at catching ObjC errors and turning them into
Lisp errors, so one can have an iterative REPL-based development
cycle for a macOS GUI application.
McCLIM and Garnet are toolkit in 100% Common Lisp. McClim even
has a prototype running in the browser with the Broadway protocol and
Garnet has an ongoing interface to Gtk.
Alloy, another very new toolkit in 100% Common Lisp, used for
example in the Kandria game.
eql, eql5, eql5-android, embedded Qt4 and Qt5 Lisp, embedded in
ECL, embeddable in Qt. Port of EQL5 to the Android platform.
this demo using Java Swing from ABCL
examples of using Gtk without C files with SBCL, as well as GTK-
server.
and, last but not least, Ceramic, to ship a cross-platform web app with
Electron.
Tk (or Tcl/Tk, where Tcl is the programming language) has the infamous
reputation of having an outdated look. This is not (so) true anymore since its
version 8 of 1997 (!). It is probably better than you think.
This is a simple GUI with nodgui’s built-in theme (more on that below):
A toy mediaplayer, showing a tree list, checkboxes, buttons and labels, with
the Arc theme:
This is a demo with a Macos theme:
In addition to those, we can use many of the ttkthemes, the Forest theme,
and more. See this tcl/tk list.
But what is Tk good for? Tk doesn’t have a great choice of widgets, but it
has a useful canvas, and it has a couple of unique features: we can develop a
graphical interface fully interactively and we can run the GUI remotely
from the core app. It is also cross-platform.
So, Tk isn’t native and doesn’t have the most advanced features, but it is a
used and proven GUI toolkit (and programming language) still used in the
industry. It can be a great choice to quickly create simple GUIs, to leverage
its ease of deployment, or when stability is required.
There are two Lisp bindings: Ltk and nodgui. Nodgui (“No Drama GUI”) is
a fork of Ltk, with added widgets (such as an auto-completion list widget),
an asynchronous event loop and, what we really enjoy, the surprisingly nice-
looking “Yaru” theme that comes with the library. It is also very easy to
install and use any other theme of our choice, see below.
Widgets: this is not the fort of Tk. It has a small set of default widgets,
and misses important ones, for example a date picker. We can find some
in extensions (such as in Nodgui), but they don’t feel native, at all. The
calendar is brought by a Tk extension and looks better.
Graphical builder: no
Other features:
remote execution: the connection between Lisp and Tcl/Tk is
done via a stream. It is thus possible to run the Lisp program on
one computer, and to display the GUI on another one. The only
thing required on the client computer is tcl/tk installed and the
remote.tcl script. See Ltk-remote.
Bindings documentation: short but complete. Nodgui too.
Bindings stability: very stable
Bindings activity: low for Ltk (mostly maintenance), active for nodgui
(new features).
Licence: Tcl/Tk is BSD-style, Ltk is LGPL.
Example applications:
Fulci - a program to organise your movie collections.
Ltk small games - snake and tic-tac-toe.
cl-pkr - a cross-platform color picker.
cl-torrents - searching torrents on popular trackers. CLI, readline
and a simple Tk GUI.
More examples:
https://peterlane.netlify.app/ltk-examples/: LTk examples for the
tkdocs tutorial.
LTk Plotchart - A wrapper around the tklib/plotchart library to
work with LTk. This includes over 20 different chart types (xy-
plots, gantt charts, 3d-bar charts etc…).
List of widgets
Ltk-megawidgets:
progress
history-entry
menu-entry
nodgui adds:
treelist tooltip searchable-listbox date-picker calendar
autocomplete-listbox
password-entry progress-bar-star notify-window
dot-plot bar-chart equalizer-bar
swap-list
Qt4 (Qtools)
Do we need to present Qt and Qt4? Qt is huge and contains everything and
the kitchen sink. Qt not only provides UI widgets, but numerous other layers
(networking, D-BUS…).
The Qtools bindings target Qt4. The Qt5 Lisp bindings are
https://github.com/commonqt/commonqt5/ and not ready for prime time..
A companion library for Qtools, that you’ll want to check out once you
made your first Qtool application, is Qtools-ui, a collection of useful widgets
and pre-made components. It comes with short demonstrations videos.
Gtk+3 (cl-cffi-gtk)
Gtk+3 is the primary library used to build GNOME applications. Its
(currently most advanced) lisp bindings is cl-cffi-gtk. While primarily
created for GNU/Linux, Gtk works fine under macOS and can now also be
used on Windows.
IUP (lispnik/IUP)
The Lisp bindings are lispnik/iup. They are nicely done in that they are
automatically generated from the C sources. They can follow new IUP
versions with a minimal work and the required steps are documented. All
this gives us good guarantee over the bus factor.
IUP stands as a great solution in between Tk and Gtk or Qt.
List of widgets
Radio, Tabs, FlatTabs, ScrollBox, DetachBox,
Button, FlatButton, DropButton, Calendar, Canvas, Colorbar,
ColorBrowser, DatePick, Dial, Gauge, Label, FlatLabel,
FlatSeparator, Link, List, FlatList, ProgressBar, Spin, Text,
Toggle, Tree, Val,
listDialog, Alarm, Color, Message, Font, Scintilla, file-dialog…
Cells, Matrix, MatrixEx, MatrixList,
GLCanvas, Plot, MglPlot, OleControl, WebBrowser (WebKit/Gtk+)…
drag-and-drop
WebBrowser
Nuklear (Bodge-Nuklear)
its Lisp binding is Bodge-Nuklear, and its higher level companions bodge-ui
and bodge-ui-window.
List of widgets
Non-exhaustive list:
buttons, progressbar, image selector, (collapsable) tree, list,
grid, range, slider, color picker,
date-picker
Getting started
Tk
All widgets are created with a regular make-instance and the widget name:
(make-instance 'button)
(make-instance 'treeview)
After we created some widgets, we must place them on the layout. There are
a few Tk systems for that, but the most recent one and the one we should
start with is the grid. grid is a function that takes as arguments the widget,
its column, its row, and a few optional parameters.
Reacting to events
Interactive development
When we start the Tk process in the background with (start-wish), we can
create widgets and place them on the grid interactively.
Nodgui
but hey, to load the demo with the better looking theme, do:
(nodgui.demo:demo :theme "yaru")
or
(setf nodgui:*default-theme* "yaru")
(nodgui.demo:demo)
Nodgui UI themes
To use the “yaru” theme that comes with nodgui, we can simply do:
(with-nodgui ()
(use-theme "yaru")
…)
or
(with-nodgui (:theme "yaru")
…)
or
(setf nodgui:*default-theme* "yaru")
(with-nodgui ()
…)
It is also possible to install and load another tcl theme. For example, clone
the Forest ttk theme or the ttkthemes. Your project directory would look like
this:
yourgui.asd
yourgui.lisp
ttkthemes/
Inside ttkthemes/, you will find themes under the png/ directory (the other
ones are currently not supported):
/ttkthemes/ttkthemes/png/arc/arc.tcl
You need to load the .tcl file with nodgui, and tell it to use this theme:
(with-nodgui ()
(eval-tcl-file "/ttkthemes/ttkthemes/png/arc/arc.tcl")
(use-theme "arc")
… code here …)
and that’s it. Your application now uses a new and decently looking GUI
theme.
Qt4
(ql:quickload '(:qtools :qtcore :qtgui))
(defpackage #:qtools-test
(:use #:cl+qt)
(:export #:main))
(in-package :qtools-test)
(in-readtable :qtools)
Reacting to events
Widgets already send their own signals: for example, a button sends a
“pressed” event. So, most of the time, we only need to connect to them.
However, had we extra needs, we can create our own set of signals.
Built-in events
Custom events
We start by defining the signal, which happens inside the main-window, and
which is of type string:
(define-signal (main-window name-set) (string))
We create a first slot to make our button react to the pressed and return-
pressed events. But instead of creating the message box here, as above, we
send the name-set signal, with the value of our input field..
(define-slot (main-window go-button) ()
(declare (connected go-button (pressed)))
(declare (connected name (return-pressed)))
(signal! main-window (name-set string) (q+:text name)))
It is possible to build a binary and bundle it together with all the necessary
shared libraries.
You might also like this Travis CI script to build a self-contained binary for
the three OSes.
Gtk3
(defpackage :gtk-tutorial
(:use :gtk :gdk :gdk-pixbuf :gobject
:glib :gio :pango :cairo :common-lisp))
(in-package :gtk-tutorial)
As with the other libraries, everything happens inside the main loop wrapper,
here with-main-loop.
How to create a window
All widgets have a corresponding class. We can create them with make-
instance 'widget-class, but we preferably use the constructors.
Reacting to events
Full example
(defun hello-world ()
;; in the docs, this is example-upgraded-hello-world-2.
(within-main-loop
(let ((window (make-instance 'gtk-window
:type :toplevel
:title "Hello Buttons"
:default-width 250
:default-height 75
:border-width 12))
(box (make-instance 'gtk-box
:orientation :horizontal
:spacing 6)))
(g-signal-connect window "destroy"
(lambda (widget)
(declare (ignore widget))
(leave-gtk-main)))
(let ((button (gtk-button-new-with-label "Button 1")))
(g-signal-connect button "clicked"
(lambda (widget)
(declare (ignore widget))
(format t "Button 1 was pressed.~%"))
(gtk-box-pack-start box button))
(let ((button (gtk-button-new-with-label "Button 2")))
(g-signal-connect button "clicked"
(lambda (widget)
(declare (ignore widget))
(format t "Button 2 was pressed.~%")))
(gtk-box-pack-start box button))
(gtk-container-add window box)
(gtk-widget-show-all window))))
IUP
Please check the installation instructions upstream. You may need one
system dependency on GNU/Linux, and to modify an environment variable
on Windows.
Finally, do:
(ql:quickload "iup")
We are not going to :use IUP (it is a bad practice generally after all).
(defpackage :test-iup
(:use :cl))
(in-package :test-iup)
As with all the bindings seen so far, widgets are shown inside a with-iup
macro, and with a call to iup:main-loop.
You can group widgets on frames, and stack them vertically or horizontally
(with vbox or hbox, see the example below).
Reacting to events
Most widgets take an :action parameter that takes a lambda function with
one parameter (the handle).
(iup:button :title "Test &1"
:expand :yes
:tip "Callback inline at control creation"
:action (lambda (handle)
(iup:message "title" "button1's action call
iup:+default+))
Below we create a label and put a button below it. We display a message
dialog when we click on the button.
(defun click-button ()
(iup:with-iup ()
(let* ((label (iup:label :title
(format nil "Hello, World!~%IUP ~A~%~A ~A"
(iup:version)
(lisp-implementation-type)
(lisp-implementation-version))))
(button (iup:button :title "Click me"
:expand :yes
:tip "yes, click me"
:action
(lambda (handle)
(declare (ignorable handle))
(iup:message "title"
"button clicked")
iup:+default+)))
(vbox
(iup:vbox (list label button)
:gap "10"
:margin "10x10"
:alignment :acenter))
(dialog (iup:dialog vbox :title "Hello, World!")))
(iup:show dialog)
(iup:main-loop))))
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(click-button))
Here’s a similar example to make a counter of clicks. We use a label and its
title to hold the count. The title is an integer.
(defun counter ()
(iup:with-iup ()
(let* ((counter (iup:label :title 0))
(label (iup:label :title
(format nil "The button was clicked ~a time(
(iup:attribute counter :title))))
(button (iup:button :title "Click me"
:expand :yes
:tip "yes, click me"
:action (lambda (handle)
(declare (ignorable hand
(setf (iup:attribute cou
(1+ (iup:attribute
(setf (iup:attribute lab
(format nil "The b
(iup:attri
iup:+default+)))
(vbox
(iup:vbox (list label button)
:gap "10"
:margin "10x10"
:alignment :acenter))
(dialog (iup:dialog vbox :title "Counter")))
(iup:show dialog)
(iup:main-loop))))
(defun run-counter ()
#-sbcl
(counter)
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(counter)))
Below we create three list widgets with simple and multiple selection, we set
their default value (the pre-selected row) and we place them horizontally
side by side.
(defun list-test ()
(iup:with-iup ()
(let* ((list-1 (iup:list :tip "List 1" ;; tooltip
;; multiple selection
:multiple :yes
:expand :yes))
(list-2 (iup:list :value 2 ;; default index of the
:tip "List 2" :expand :yes))
(list-3 (iup:list :value 9 :tip "List 3" :expand :yes
(frame (iup:frame
(iup:hbox
(progn
;; populate the lists: display integers.
(loop for i from 1 upto 10
do (setf (iup:attribute list-1 i)
(format nil "~A" i))
do (setf (iup:attribute list-2 i)
(format nil "~A" (+ i 10)))
do (setf (iup:attribute list-3 i)
(format nil "~A" (+ i 50))))
;; hbox wants a list of widgets.
(list list-1 list-2 list-3)))
:title "IUP List"))
(dialog (iup:dialog frame :menu "menu" :title "List e
(iup:map dialog)
(iup:show dialog)
(iup:main-loop))))
(defun run-list-test ()
#-sbcl (hello)
#+sbcl
(sb-int:with-float-traps-masked
(:divide-by-zero :invalid)
(list-test)))
Nuklear
Uncomment and evaluate this line only if you want to enable the OpenGL 2
renderer:
;; (cl:pushnew :bodge-gl2 cl:*features*)
Quickload bodge-ui-window:
(ql:quickload "bodge-ui-window")
(defpanel (main-panel
(:title "Hello Bodge UI")
(:origin 200 50)
(:width 400) (:height 400)
(:options :movable :resizable
:minimizable :scrollable
:closable))
(label :text "Nested widgets:")
(horizontal-layout
(radio-group
(radio :label "Option 1")
(radio :label "Option 2" :activated t))
(vertical-layout
(check-box :label "Check 1" :width 100)
(check-box :label "Check 2"))
(vertical-layout
(label :text "Awesomely" :align :left)
(label :text "Stacked" :align :centered)
(label :text "Labels" :align :right)))
(label :text "Expand by width:")
(horizontal-layout
(button :label "Dynamic")
(button :label "Min-Width" :width 80)
(button :label "Fixed-Width" :expandable nil :width 100))
(label :text "Expand by width:")
(horizontal-layout
(button :label "1.0" :expand-ratio 1.0)
(button :label "0.75" :expand-ratio 0.75)
(button :label "0.5" :expand-ratio 0.5))
(label :text "Rest:")
(button :label "Top-level Button"))
(defun run ()
(bodge-host:open-window (make-instance 'main-window)))
They take as argument a function with one argument, the panel. But beware:
they will be called on each rendering cycle when the widget is on the given
state, so potentially a lot of times.
Interactive development
If you ran the example in the REPL, you couldn’t see what’s cool. Put the
code in a lisp file and run it, so than you get the window. Now you can
change the panel widgets and the layout, and your changes will be
immediately applied while the application is running!
Conclusion
Have fun, and don’t hesitate to share your experience and your apps.
Web development
For web development as for any other task, one can leverage Common
Lisp’s advantages: the unmatched REPL that even helps to interact with a
running web app, the exception handling system, performance, the ability to
build a self-contained executable, stability, good threads story, strong typing,
etc. We can, say, define a new route and try it right away, there is no need to
restart any running server. We can change and compile one function at a time
(the usual C-c C-c in Slime) and try it. The feedback is immediate. We can
choose the degree of interactivity: the web server can catch exceptions and
fire the interactive debugger, or print lisp backtraces on the browser, or
display a 404 error page and print logs on standard output. The ability to
build self-contained executables eases deployment tremendously (compared
to, for example, npm-based apps), in that we just copy the executable to a
server and run it.
And when we have deployed our app, we can still interact with it, allowing
for hot reload, that even works when new dependencies have to be installed.
If you are careful and don’t want to use full live reload, you might still enjoy
this capability to reload, for example, a user’s configuration file.
We’ll present here some established web frameworks and other common
libraries to help you getting started in developing a web application. We do
not aim to be exhaustive nor to replace the upstream documentation. Your
feedback and contributions are appreciated.
Overview
Hunchentoot and Clack are two projects that you’ll often hear about.
Hunchentoot is
a web server and at the same time a toolkit for building dynamic
websites. As a stand-alone web server, Hunchentoot is capable of
HTTP/1.1 chunking (both directions), persistent connections (keep-
alive), and SSL. It provides facilities like automatic session handling
(with and without cookies), logging, customizable error handling, and
easy access to GET and POST parameters sent by the client.
Clack is
We’ll cite also Wookie, an asynchronous HTTP server, and its companion
library cl-async, for general purpose, non-blocking programming in
Common Lisp, built on libuv, the backend library in Node.js.
Clack being more recent and less documented, and Hunchentoot a de-facto
standard, we’ll concentrate on the latter for this recipe. Your contributions
are of course welcome.
Web frameworks build upon web servers and can provide facilities for
common activities in web development, like a templating system, access to a
database, session management, or facilities to build a REST api.
For a full list of libraries for the web, please see the awesome-cl list
#network-and-internet and Cliki. If you are looking for a featureful static site
generator, see Coleslaw.
Installation
Let’s install the libraries we’ll use:
(ql:quickload '("hunchentoot" "caveman2" "spinneret"
"djula" "easy-routes"))
We’ll start by serving local files and we’ll run more than one local server in
the running image.
Simple webserver
Serve local files
Hunchentoot
By default, Hunchentoot serves the files from the www/ directory in its source
tree. Thus, if you go to the source of easy-acceptor (M-. in Slime), which is
probably ~/quicklisp/dists/quicklisp/software/hunchentoot-
v1.2.38/, you’ll find the www/ directory. It contains:
Let’s create our index.html first. Put this in a new www/index.html at the
current directory (of the lisp repl):
<html>
<head>
<title>Hello!</title>
</head>
<body>
<h1>Hello local server!</h1>
<p>
We just served our own files.
</p>
</body>
</html>
Note that we just created another acceptor on a different port on the same
lisp image. This is already pretty cool.
With Hunchentoot we have nothing to do, we can see the server from the
internet right away.
Routing
Simple routes
Hunchentoot
(push
(hunchentoot:create-prefix-dispatcher "/hello.html" #'hello)
hunchentoot:*dispatch-table*)
Example:
(hunchentoot:define-easy-handler (say-yo :uri "/yo") (name)
(setf (hunchentoot:content-type*) "text/plain")
(format nil "Hey~@[ ~A~]!" name))
There are also keys to know for the lambda list. Please see the
documentation.
Easy-routes (Hunchentoot)
Here, :x captures the path parameter and binds it to the x variable into the
route body. y and &get z define URL parameters, and we can have &post
parameters to extract from the HTTP request body.
Now, imagine that we are deeper in our web application logic, and we want
to redirect our user to the route “/foo/3”. Instead of hardcoding the URL, we
can generate the URL from its name. Use easy-routes:genurl like this:
(easy-routes:genurl my-route-name :id 3)
;; => /foo/3
Decorators are functions that are executed before the route body. They
should call the next parameter function to continue executing the decoration
chain and the route body finally. Examples:
(defun @auth (next)
(let ((*user* (hunchentoot:session-value 'user)))
(if (not *user*)
(hunchentoot:redirect "/login")
(funcall next))))
Caveman
Caveman provides two ways to define a route: the defroute macro and the
@route pythonic annotation:
Hunchentoot
First of all, note that we can access query parameters anytime with
(hunchentoot:parameter "my-param")
'string (default),
'integer,
'character (accepting strings of length 1 only, otherwise it is nil)
or 'boolean
or a compound list:
'(:list <type>)
'(:array <type>)
'(:hash-table <type>)
Hunchentoot
Then you can parse this string to JSON with the library of your choice (jzon,
shasht…).
(easy-routes route-api-demo ("/api/:id/update" :method :post) ()
(let ((json (ignore-errors
(jzon:parse (hunchentoot:raw-post-data :force-tex
(when json
…)))
Error handling
In all frameworks, we can choose the level of interactivity. The web
framework can return a 404 page and print output on the repl, it can catch
errors and invoke the interactive lisp debugger, or it can show the lisp
backtrace on the html page.
Hunchentoot
The global variables to set to choose the error handling behaviour are:
Clack
Clack users might make a good use of plugins, like the clack-errors
middleware: https://github.com/CodyReichert/awesome-cl#clack-plugins.
It was initially based on continuations (they were removed to date) and thus
a lispy cousin of Smalltalk’s Seaside. We can also relate it to Haskell’s
Haste, OCaml’s Eliom, Elixir’s Phoenix LiveView and others.
Weblock’s unit of work is the widget. They look like a class definition:
(defwidget task ()
((title
:initarg :title
:accessor title)
(done
:initarg :done
:initform nil
:accessor done)))
Then all we have to do is to define the render method for this widget:
(defmethod render ((task task))
"Render a task."
(with-html
(:span (if (done task)
(with-html
(:s (title task)))
(title task)))))
It uses the Spinneret template engine by default, but we can bind any other
one of our choice.
Templates
Djula - HTML markup
and then we can declare and compile the ones we use, for example::
(defparameter +base.html+ (djula:compile-template* "base.html"))
(defparameter +welcome.html+ (djula:compile-template* "welcome.ht
A Djula template looks like this (forgive the antislash in {\%, this is a Jekyll
limitation):
{\% extends "base.html" \%}
{\% block title %}Memberlist{\% endblock \%}
{\% block content \%}
<ul>
{\% for user in users \%}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{\% endfor \%}
</ul>
{\% endblock \%}
Note that for efficiency Djula compiles the templates before rendering them.
It is, along with its companion access library, one of the most downloaded
libraries of Quicklisp.
Djula filters
Filters allow to modify how a variable is displayed. Djula comes with a good
set of built-in filters and they are well documented. They are not to be
confused with tags.
They look like this: {{ name | lower }}, where lower is an existing filter,
which renders the text into lowercase.
Filters sometimes take arguments. For example: {{ value | add:2 }} calls
the add filter with arguments value and 2.
Once you have written a custom filter, you can use it right away throughout
the application.
Filters are very handy to move non-trivial formatting or logic from the
templates to the backend.
The author finds it is easier to compose the HTML in separate functions and
macros than with the more famous cl-who. But it has more features under it
sleeves:
For example:
(push (hunchentoot:create-folder-dispatcher-and-handler
"/static/" (merge-pathnames
"src/static" ; <-- starts without a /
(asdf:system-source-directory :myproject)))
hunchentoot:*dispatch-table*)
Connecting to a database
Please see the databases section. The Mito ORM supports SQLite3,
PostgreSQL, MySQL, it has migrations and db schema versioning, etc.
(defun logout ()
"Log the user out of the session."
(setf (gethash :user *session*) nil))
If the user isn’t logged in, there will nothing in the session store, and we
render the login page. When all is well, we execute the macro’s body. We
use it like this:
(defroute "/account/logout" ()
"Show the log-out page, only if the user is logged in."
(with-logged-in
(logout)
(render #p"logout.html")))
Encrypting passwords
With cl-pass
You might also want to look at hermetic, a simple authentication system for
Clack-based applications.
In this recipe we do the encryption and verification ourselves. We use the de-
facto standard Ironclad cryptographic toolkit and the Babel charset
encoding/decoding library.
The following snippet creates the password hash that should be stored in
your database. Note that Ironclad expects a byte-vector, not a string.
(defun password-hash (password)
(ironclad:pbkdf2-hash-password-to-combined-string
(babel:string-to-octets password)))
The following function checks if a user is active and verifies the entered
password. It returns the user-id if active and verified and nil in all other
cases even if an error occurs. Adapt it to your application.
(defun check-user-password (user password)
(handler-case
(let* ((data (my-get-user-data user))
(hash (my-get-user-hash data))
(active (my-get-user-active data)))
(when (and active (ironclad:pbkdf2-check-password (babel
hash))
(my-get-user-id data)))
(condition () nil)))
And the following is an example on how to set the password on the database.
Note that we use (password-hash password) to save the password. The rest
is specific to the web framework and to the DB library.
(defun set-password (user password)
(with-connection (db)
(execute
(make-statement :update :web_user
(set= :hash (password-hash password))
(make-clause :where
(make-op := (if (integerp user)
:id_user
:email)
user))))))
To run our Lisp code from source, as a script, we can use the --load switch
from our implementation.
We must ensure:
(load "myproject.asd")
(ql:quickload "myproject")
(in-package :myproject)
(handler-case
;; The START function starts the web server.
(myproject::start :port (ignore-errors
(parse-integer
(uiop:getenv "PROJECT_PORT"))))
(error (c)
(format *error-output* "~&An error occured: ~a~&" c)
(uiop:quit 1)))
In addition we have allowed the user to set the application’s port with an
environment variable.
After loading the project, the web server is started in the background. We are
offered the usual Lisp REPL, from which we can interact with the running
application.
We can also connect to the running application from our preferred editor,
from home, and compile the changes in our editor to the running instance.
See the following section #connecting-to-a-remote-lisp-image.
It is as simple as this:
;; Load Ceramic and our app
(ql:quickload '(:ceramic :our-app))
;; start Ceramic
(ceramic:show-window window)
There is more:
Ceramic applications are compiled down to native code, ensuring both
performance and enabling you to deliver closed-source, commercial
applications.
Deployment
Deploying manually
We can start our executable in a shell and send it to the background (C-z
bg), or run it inside a tmux session. These are not the best but hey, it works©.
Most GNU/Linux distros now come with Systemd, so here’s a little example.
[Service]
WorkingDirectory=/path/to/your/project/directory/
ExecStart=/usr/bin/make run # or anything
Type=simple
Restart=on-failure
[Install]
WantedBy=network.target
and see our application’s logs (we can write to stdout or stderr, and Systemd
handles the logging):
journalctl -u my-app.service
(you can also use the -f option to see log updates in real time, and in that
case augment the number of lines with -n 50 or --lines).
See more:
https://www.freedesktop.org/software/systemd/man/systemd.service.html.
With Docker
There are several Docker images for Common Lisp. For example:
clfoundation/sbcl includes the latest version of SBCL, many OS
packages useful for CI purposes, and a script to install Quicklisp.
40ants/base-lisp-image is based on Ubuntu LTS and includes SBCL,
CCL, Quicklisp, Qlot and Roswell.
container-lisp/s2i-lisp is CentOs based and contains the source for
building a Quicklisp based Common Lisp application as a reproducible
docker image using OpenShift’s source-to-image.
With Guix
There is nothing CL-specific to run your Lisp web app behind Nginx. Here’s
an example to get you started.
We suppose you are running your Lisp app on a web server, with the IP
address 1.2.3.4, on the port 8001. Nothing special here. We want to access
our app with a real domain name (and eventuall benefit of other Nginx’s
advantages, such as rate limiting etc). We bought our domain name and we
created a DNS record of type A that links the domain name to the server’s IP
address.
We must configure our server with Nginx to tell it that all connections
coming from “your-domain-name.org”, on port 80, are to be sent to the Lisp
app running locally.
# Optional: serve static files with nginx, not the Lisp app.
location /files/ {
proxy_pass http://1.2.3.4:8001/files/;
}
}
$ nginx -s reload
and that’s it: you can access your Lisp app from the outside through
http://www.your-domain-name.org.
Monitoring
See Prometheus.cl for a Grafana dashboard for SBCL and Hunchentoot
metrics (memory, threads, requests per second,…).
Hot reload
This is an example from Quickutil. It is actually an automated version of the
precedent section.
It has to be run on the server (a simple fabfile command can call this through
ssh). Beforehand, a fab update has run git pull on the server, so new code
is present but not running. It connects to the local swank server, loads the
new code, stops and starts the app in a row.
See also
Feather, a template for web application development, shows a
functioning Hello World app with an HTML page, a JSON API, a
passing test suite, a Postgres DB and DB migrations. Uses Qlot,
Buildapp, SystemD for deployment.
lisp-web-template-productlist, a simple project template with
Hunchentoot, Easy-Routes, Djula and Bulma CSS.
lisp-web-live-reload-example - a toy project to show how to interact
with a running web app.
Credits
https://lisp-journey.gitlab.io/web-dev/
Web Scraping
The set of tools to do web scraping in Common Lisp is pretty complete and
pleasant. In this short tutorial we’ll see how to make http requests, parse
html, extract content and do asynchronous requests.
Our simple task will be to extract the list of links on the CL Cookbook’s
index page and check if they are reachable.
HTTP Requests
Easy things first. Install Dexador. Then we use the get function:
(defvar *url* "https://lispcookbook.github.io/cl-cookbook/")
(defvar *request* (dex:get *url*))
This returns a list of values: the whole page content, the return code (200),
the response headers, the uri and the stream.
"<!DOCTYPE html>
<html lang=\"en\">
<head>
<title>Home – the Common Lisp Cookbook</title>
[…]
"
200
#<HASH-TABLE :TEST EQUAL :COUNT 19 {1008BF3043}>
#<QURI.URI.HTTP:URI-HTTPS https://lispcookbook.github.io/cl-
cookbook/>
#<CL+SSL::SSL-STREAM for #<FD-STREAM for "socket
192.168.0.23:34897, peer: 151.101.120.133:443" {100781C133}>>
https://shinmera.github.io/lquery/
We first need to parse the html into an internal data structure. Use
(lquery:$ (initialize <html>)):
Note: to find out what should be the CSS selector of the element I’m
interested in, I right click on an element in the browser and I choose
“Inspect element”. This opens up the inspector of my browser’s web dev
tool and I can study the page structure.
I’d like to easily check what those elements are. To see the entire html, we
can end our lquery line with (serialize):
(lquery:$ *parsed-content* "#content li" (serialize))
#("<li><a href=\"license.html\">License</a></li>"
"<li><a href=\"getting-started.html\">Getting started</a></li>
"<li><a href=\"editor-support.html\">Editor support</a></li>"
[…]
And to see their textual content (the user-visible text inside the html), we
can use (text) instead:
(lquery:$ *parsed-content* "#content" (text))
#("License" "Editor support" "Strings" "Dates and Times" "Hash T
"Pattern Matching / Regular Expressions" "Functions" "Loop" "I
"Files and Directories" "Packages" "Macros and Backquote"
"CLOS (the Common Lisp Object System)" "Sockets" "Interfacing
"Foreign Function Interfaces" "Threads" "Defining Systems"
[…]
"Pascal Costanza’s Highly Opinionated Guide to Lisp"
"Loving Lisp - the Savy Programmer’s Secret Weapon by Mark Wat
"FranzInc, a company selling Common Lisp and Graph Database so
All right, so we see we are manipulating what we want. Now to get their
href, a quick look at lquery’s doc and we’ll use (attr "some-name"):
Nice, we now have the list (well, a vector) of links of the page. We’ll now
write an async program to check and validate they are reachable.
External resources:
CSS selectors
Async requests
In this example we’ll take the list of url from above and we’ll check if they
are reachable. We want to do this asynchronously, but to see the benefits
we’ll first do it synchronously !
We need a bit of filtering first to exclude the email addresses (maybe that
was doable in the CSS selector ?).
We remove the elements that start with “mailto:”: (a quick look at the
strings page will help)
(remove-if (lambda (it)
(string= it "mailto:" :start1 0
:end1 (length "mailto:")))
*urls*)
;; => #("license.html" "editor-support.html" "strings.html" "dat
;; […]
;; "process.html" "systems.html" "win32.html" "testing.html" "m
;; "license.html" "http://lisp-lang.org/"
;; "https://github.com/CodyReichert/awesome-cl"
;; https://github.com/CodyReichert/awesome cl
;; "http://www.lispworks.com/documentation/HyperSpec/Front/inde
;; […]
;; "https://franz.com/")
While we’re at it, we’ll only consider links starting with “http”, in order not
to write too much stuff irrelevant to web scraping:
(remove-if-not (lambda (it)
(string= it "http" :start1 0 :end1 (length "htt
*)
and now to the real work. For every url, we want to request it and check
that its return code is 200. We have to ignore certain errors. Indeed, a
request can timeout, be redirected (we don’t want that) or return an error
code.
To be in real conditions we’ll add a link that times out in our list:
(setf (aref *filtered-urls* 0) "http://lisp.org") ;; :/
We’ll take the simple approach to ignore errors and return nil in that case.
If all goes well, we return the return code, that should be 200.
(ignore-errors has the caveat that when there’s an error, we can not return
the element it comes from. We’ll get to our ends though.)
(map 'vector (lambda (it)
(ignore-errors
(nth-value 1 (dex:get it))))
*filtered-urls*)
we get:
#(NIL 200 200 200 200 200 200 200 200 200 200 NIL 200 200 200
200 200 200 200
200 200 200 200)
it works, but it took a very long time. How much time precisely ? with
(time …):
Evaluation took:
21.554 seconds of real time
0.188000 seconds of total run time (0.172000 user, 0.016000
system)
0.87% CPU
55,912,081,589 processor cycles
9,279,664 bytes consed
After installing lparallel and looking at its documentation, we see that the
parallel map pmap seems to be what we want. And it’s only a one word
edit. Let’s try:
(time (lparallel:pmap 'vector
(lambda (it)
(ignore-errors
(let ((status (nth-value 1 (dex:get it)))) status)))
*filtered-urls*)
;; Evaluation took:
;; 11.584 seconds of real time
;; 0.156000 seconds of total run time (0.136000 user, 0.020000
;; 1.35% CPU
;; 30,050,475,879 processor cycles
;; 7,241,616 bytes consed
;;
;;#(NIL 200 200 200 200 200 200 200 200 200 200 NIL 200 200 200
;; 200 200 200 200)
Bingo. It still takes more than 10 seconds because we wait 10 seconds for
one request that times out. But otherwise it proceeds all the http requests in
parallel and so it is much faster.
Shall we get the urls that aren’t reachable, remove them from our list and
measure the execution time in the sync and async cases ?
we get a vector of urls with a couple of nils: indeed, I thought I would have
only one unreachable url but I discovered another one. Hopefully I have
pushed a fix before you try this tutorial.
But what are they ? We saw the status codes but not the urls :S We have a
vector with all the urls and another with the valid ones. We’ll simply treat
them as sets and compute their difference. This will show us the bad ones.
We must transform our vectors to lists for that.
(set-difference (coerce *filtered-urls* 'list)
(set d e e ce (coe ce te ed u s st)
(coerce *valid-urls* 'list))
;; => ("http://lisp-lang.org/" "http://www.psg.com/~dlamkins/sl/
Gotcha !
BTW it takes 8.280 seconds of real time to me to check the list of valid urls
synchronously, and 2.857 seconds async.
we could use VCR, a store and replay utility to set up repeatable tests
or to speed up a bit our experiments in the REPL.
cl-async, carrier and others network, parallelism and concurrency
libraries to see on the awesome-cl list, Cliki or Quickdocs.
WebSockets
The Common Lisp ecosystem boasts a few approaches to building
WebSocket servers. First, there is the excellent Hunchensocket that is
written as an extension to Hunchentoot, the classic web server for Common
Lisp. I have used both and I find them to be wonderful.
In what follows, you will build a simple chat server and connect to it from a
web browser. The tutorial is written so that you can enter the code into your
REPL as you go, but in case you miss something, the full code listing can
be found at the end.
As a first step, you should load the needed libraries via quicklisp:
For the purposes of your chat server, you will want to handle three cases:
when a new user arrives to the channel, when a user sends a message to the
channel, and when a user leaves.
First, when a user connects to the server, you need to give that user a
nickname so that other users know whose chats belong to whom. You will
also need a data structure to map individual WebSocket connections to
nicknames:
Next, when a user sends a chat to the room, the rest of the room should be
notified. The message that the server receives is prepended with the
nickname of the user who sent it.
(defun broadcast-to-room (connection message)
(let ((message (format nil "~a: ~a"
(gethash connection *connections*)
message)))
(loop :for con :being :the :hash-key :of *connections* :do
(websocket-driver:send con message))))
Finally, when a user leaves the channel, by closing the browser tab or
navigating away, the room should be notified of that change, and the user’s
connection should be dropped from the *connections* table.
(defun handle-close-connection (connection)
(let ((message (format nil " .... ~a has left."
(gethash connection *connections*))))
(remhash connection *connections*)
(loop :for con :being :the :hash-key :of *connections* :do
(websocket-driver:send con message))))
Defining A Server
Using Clack, a server is started by passing a function to clack:clackup.
You will define a function called chat-server that you will start by calling
(clack:clackup #'chat-server :port 12345).
A Clack server function accepts a single plist as its argument. That plist
contains environment information about a request and is provided by the
system. Your chat server will not make use of that environment, but if you
want to learn more you can check out Clack’s documentation.
(websocket-driver:on :open ws
(lambda () (handle-new-connection ws)))
( a bda () ( a d e e co ect o s)))
(websocket-driver:on :message ws
(lambda (msg)
(broadcast-to-room ws msg)))
(websocket-driver:on :close ws
(lambda (&key code reason)
(declare (ignore code reason))
(handle-close-connection ws)))
(lambda (responder)
(declare (ignore responder))
(websocket-driver:start-connection ws)))) ; send the hands
(defvar *html*
"<!doctype html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>LISP-CHAT</title>
</head>
<body>
<ul id=\"chat-echo-area\">
</ul>
<div style=\"position:fixed; bottom:0;\">
<input id=\"chat-input\" placeholder=\"say something\" >
</div>
<script>
window.onload = function () {
const inputField = document.getElementById(\"chat-input
function receivedMessage(msg) {
let li = document.createElement(\"li\");
li.textContent = msg.data;
document.getElementById(\"chat-echo-area\").appendC
}
</script>
</body>
</html>
")
You might prefer to put the HTML into a file, as escaping quotes is kind of
annoying. Keeping the page data in a defvar was simpler for the purposes
of this tutorial.
You can see that the client-server function just serves the HTML content.
Go ahead and start it, this time on port 8080:
(defvar *client-handler* (clack:clackup #'client-server :port 80
Check it out!
Now open up two browser tabs and point them to http://localhost:8080
and you should see your chat app!
(websocket-driver:on :message ws
(lambda (msg)
(broadcast-to-room ws msg)))
(websocket-driver:on :close ws
(lambda (&key code reason)
(declare (ignore code reason))
(handle-close-connection ws)))
(lambda (responder)
(declare (ignore responder))
(websocket-driver:start-connection ws))))
(defvar *html*
"<!doctype html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>LISP-CHAT</title>
</head>
<body>
<ul id=\"chat-echo-area\">
</ul>
function receivedMessage(msg) {
let li = document.createElement(\"li\");
li.textContent = msg.data;
document.getElementById(\"chat-echo-area\").appendC
}
</script>
</body>
</html>
")
vindarel
Paul Nathan
nhabedi 1
Fernando Borretti
bill_clementson
chuchana
Ben Dudson
YUE Daian
Pierre Neidhardt
Rommel MARTINEZ
digikar99
nicklevine
Dmitry Petrov
otjura
skeptomai
alx-a
jgart
thegoofist
Francis St-Amour
Johan Widén
emres
jdcal
Boutade
airfoyle
contrapunctus
mvilleneuve
Alex Ponomarev
Alexander Artemenko
Johan Sjölén
Mariano Montone
albertoriva
Blue
Daniel Keogh
David Pflug
David Sun
Jason Legler
Jiho Sung
Kilian M. Haemmerle
Matteo Landi
Nikolaos Chatzikonstantinou
Nisar Ahmad
Nisen
Vityok
ctoid
ozten
reflektoin
Ahmad Edrisy
Alberto Ferreira
Amol Dosanjh
Andrew
Andrew Hill
André Alexandre Gomes
Ankit Chandawala
August Feng
B1nj0y
Bibek Panthi
Bo Yao
Brandon Hale
Burhanuddin Baharuddin
Coin Okay
Colin Woodbury
Daniel Uber
Eric Timmons
Giorgos Makris
HiPhish
Inc0n
John Zhang
Justin
Kevin Layer
Kevin Secretan
LdBeth
Matthew Kennedy
Momozor
NCM
Noor
Paul Donnelly
Pavel Kulyov
Phi-Long Nguyen
R Primus
Ralf Doering
Salad Tea
Victor Anyakin
alaskasquirrel
blackeuler
contrapunctus-1
convert-repo
dangerdyke
grobe0ba
jthing
mavis
mwgkgk
paul-donnelly
various-and-sundry
Štěpán Němec
Marco Antoniotti
Zach Beane
Pierpaolo Bernardi
Christopher Brown
Frederic Brunel
Jeff Caldwell
Bill Clementson
Martin Cracauer
Gerald Doussot
Paul Foley
Jörg-Cyril Höhle
Nick Levine
Austin King
Lieven Marchand
Drew McDermott
Kalman Reti
Alberto Riva
Rudi Schlatte
Emre Sevinç
Paul Tarvydas
Kenny Tilton
Reini Urban
Matthieu Villeneuve
Edi Weitz
Finally, the credit for finally giving birth to the project probably goes to Edi
Weitz who posted this message to comp.lang.lisp.