common lisp

2023-02-24

for loop

(loop for x in '(1 2 3)
      collect x) ;; returns list
(loop for x in '(1 2 3)
      do (print x)) ;; prints the numbers

functions

(defun <name> (list of arguments)
  "docstring"
  (body))

plist

defining a plist

(setf my-plist '(alt "yes" bar "no"))

retrieve a value by key

(getf my-plist 'alt)

change the value that corresponds to a key

(setf (getf my-plist 'alt) 45)

get a list of the attributes

(loop for (key value) on my-plist by #'cddr
      collect key)

unbind variable

(makunbound 'foo)

undefine funciton

(fmakunbound 'function)

quicklisp

need to install it manually afaik

curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp

then in sbcl

(quicklisp-quickstart:install :path "~/.quicklisp")
(ql:add-to-init-file)

then to install a package in sbcl:

(ql:quickload "cl-csv")

this will install the package if it doesnt exist, and if it exists it just loads it

classes

example class:

(defclass my-class ()
  ((property1 :accessor property1-accessor)
   (property2 :accessor property2-accessor)))

the accessor is the name you use to access a property1
we construct an object using make-instance:

(defvar p1 (make-instance 'my-class))

we can initialize the class with specific values, first we need to use the :initarg keyword on the class properties

(defclass my-class ()
  ((property1 :initarg property1-initarg :initform default-value :accessor property1-accessor)
   (property2 :accessor property2-accessor)))

then we can pass the values we want to make-instance
:initform is used to pass a default value to the slot incase :initarg argument isnt passed to make-instance

(defvar p1 (make-instance 'my-class :property1-initarg property1-value))

this is the syntax for accessing variables of a class

(property1-accessor p1)

we can do inheritance by passing the parent class in the parenthases after the class name:

(defclass my-class (my-parent-class))

the following syntax is used to define a function for a class, note that the function print-object is the function that gets called when trying to print an object using (print object) so thats what we're defining here

(defmethod print-object ((obj my-class) stream)
  (print-unreadable-object (obj stream :type t)
    (format stream "value of property1: ~a" (property1 obj))))

structs

structs are basically simpler classes with default functions/initializers/etc

(defstruct rgb ()
           (r 0) (g 0) (b 0)) ;; initialize slot values to 0

;; e.g. constructor built by default
(setf color (make-rgb :r 2)) ;; => #S(RGB :NIL NIL :R 2 :G 0 :B 0)

(list (rgb-r color) (rgb-b color)) ;; => (2 0)

arrays/matrices

to create an array:

(make-array 10 :initial-element 10)
10 10 10 10 10 10 10 10 10 10

multidimensional arrays:

(make-array '(4 3) :initial-element 15)
#2A((15 15 15) (15 15 15) (15 15 15) (15 15 15))

access slot in array

(aref (make-array '(4 3 1) :initial-element 15) 2 0 0)
15

get the length of the array (last argument is the dimension to check length of):

(print (array-dimension (make-array '(4 3) :initial-element 15) 0))
(print (array-dimension (make-array '(4 3) :initial-element 15) 1))

4 
3 

iterate through array:

(map nil #'print (make-array 3 :initial-element 15))

15 
15 
15 

iterate with index:

(let ((arr (make-array '(4 3) :initial-element 15)))
  (loop for i from 0 below (array-dimension arr 0)
    do (print (aref arr i 1))))

15 
15 
15 
15 

use displacement arrays to get the first element of every row in a 2d array:

(loop for i from 0 below (array-dimension arr 0)
      collect (let ((row (make-array
                          (array-dimension arr 1)
                          :displaced-to arr
                          :displaced-index-offset (* i (array-dimension arr 1)))))
                (aref row 0)))

we can create resizable arrays with the :adjustable keyword, :fill-pointer denotes the next position to be filled in the array and is automatically maintained by the array itself, but has to be initialized

(defparameter *x* (make-array 0 :adjustable t :fill-pointer 0))
(vector-push-extend 'a *x*)
(vector-push-extend 'b *x*)
*x*
A B

fill array manually:

(make-array '(3 3) :initial-contents '((1 0 0) (0 1 0) (0 0 1)))

vectors

create vector

(vector 3 5 1)

we can use length on vectors

(length (vector 3 5 1)) ==> 3

get nth element:

(defparameter *x* (vector 1 2 3))
(length *x*) ==> 3
(elt *x* 1)  ==> 2

we can have nested vectors

(let ((a (vector (vector 1 2) (vector 3) 4)))
  (print a)
  (print (elt a 0)))

#(#(1 2) #(3) 4) 
#(1 2) 

multithreading

in sbcl sb-thread:make-thread takes a function to call in a newly created thread.

(sb-thread:make-thread
 (lambda ()
   (progn
     (sleep 0) ;; give other threads a chance to run, then return here
     (setf c (+ a b))
     (print "ADDITION:")
     (print c))))

although notice that this is special to sbcl.
the lparallel library works great on multiple CL implementations, more on lparallel at https://lparallel.org/page/2/

macros

simple macro from https://lispcookbook.github.io/cl-cookbook/macros.html:

(defmacro setq2 (v1 v2 e)
  (list 'progn (list 'setq v1 e) (list 'setq v2 e)))
;; (setq2 v1 v2 3) => v1=3,v2=3

this macro is very close to the following function definition:

(defun setq2-function (v1 v2 e)
  (list 'progn (list 'setq v1 e) (list 'setq v2 e)))

consider the following macro when2 which behaves like the builtin when

(defmacro when2 (condition &rest body)
  `(if ,condition (progn ,@body)))
;; (when2 t (print "hey")) => "hey"
;; (when2 nil (print "hey")) => nothing

from http://www.lispworks.com/documentation/HyperSpec/Body/02_df.htm
if a comma is immediately followed by an at-sign, then the form following the at-sign is evaluated to produce a list of objects. these objects are then “spliced” into place in the template. for example, if x has the value (a b c), then
`(x ,x ,@x foo ,(cadr x) bar ,(cdr x) baz ,@(cdr x))
=> (x (a b c) a b c foo b bar (b c) baz b c)

body is just a list and can be used/modified

(defmacro test-macro (&rest body)
  `(quote ,body))

this is called an anaphoric macro where you wrap the body with a lexical function definition that shadows the global one

CL-USER> (defun foo () 1)
FOO
CL-USER> (defmacro with-different-foo (&body body)
           `(flet ((foo () 2))
              ,@body))
WITH-DIFFERENT-FOO
CL-USER> (progn
           (foo))
1
CL-USER> (with-different-foo
           (foo))
2

reader macros

the #+ and #- reader macros are pretty nice for commenting out sexps. they allow ignoring the following sexp, if the given symbol isn't/is found in *FEATURES*. Just pick a symbol not in *FEATURES*, and use it with #+ like this:

#+nil
(defun foo ()
  ...)

Now, the function definition will be ignored (unless NIL is in *FEATURES*, which is not very likely).

use double-floats by default

(setf *read-default-float-format* 'double-float)

enforce garbage collection

running

(gc :full t)

invokes the garbage collector

checking equalities

some equality tests may return results that are implementation-specific, i dont list those here, i only wrote the ones that are supposed to work on any cl implementation
string equality:

(equal "foo" (copy-seq "foo")) ;; => T

symbol equality (any equality operator works for symbols):

(eq 'foo 'foo) ;; => T

number equality:

;; 'eql' is "type sensitive"
(eql 3 3.0) ;; => NIL
(eql 3 3) ;; => T
;; 'equal' works for floats and integers all the same
(equal 3 3.0) ;; => T
(equal 3 3) ;; => T
;; 'equalp' works for floats and integers all the same
(equalp 3 3.0) ;; => T
(equalp 3 3) ;; => T

object equality (same object in memory):

(eq 'a 'a) ;; => T
(eq "foo" (copy-seq "foo")) ;; => NIL
(eq 3 3.0) ;; => NIL

sequence equality (and general object equality which compares the internal structure):
uppercase and lowercase letters in strings are considered by equal to be distinct. in contrast, equalp ignores case distinctions in strings. equalp works for arrays but equal doesnt ("works" as in does element-wise comparison, which is the intended equality check here)

;; strings
(equal "Foo" (copy-seq "Foo")) ;; => T
(equal "Foo" (copy-seq "foo")) ;; => NIL
(equalp "Foo" (copy-seq "foo")) ;; => NIL

;; vectors/arrays
(equal #(1 2 3) (copy-seq #(1 2 3))) ;; => NIL
(equalp #(1 2 3) (copy-seq #(1 2 3))) ;; => T

;; lists
(equal '(1 2 3) (copy-seq '(1 2 3))) ;; => T
(equalp '(1 2 3) (copy-seq '(1 2 3))) ;; => T

alist

we can append new pairs to an alist using push (there are many other ways):

(let ((my-alist '((a . b))))
  (push (cons 'x 'y) my-alist)
  (push '(10 . 20) my-alist))
((10 . 20) (X . Y) (A . B))

resources