Common Lisp Notes
Table of Contents
Resources
Source Files
- Common Lisp source files can have the
.lispextension
Multi-Paradigm
- ANSI Common Lisp is multi paradigm
- It supports functional, generic, object oriented and domain specific programming styles.
SLIME
- Common Lisp development environment for Emacs
- The official webpage is, https://common-lisp.net/project/slime/
- To use with org babel ensure that
lispis enabled in yourorg-babel-load-langauges - Start slime
M-x slimebefore attempting to evaluate a common lisp source block withC-c C-c - You should be able to access variables and functions inside the SLIME REPL that are defined in the source code blocks
- Use
C-c C-d dto view the documentation for the symbol at point- This might show you a link to http://www.ai.mit.edu to get the real documentation
CLISP
- Easy to use and portable common lisp compiler that runs on most operating systems
- Also provides debugger and interpreter
SBCL
- Steel Bank Common Lisp descends from Carnegie Mellon, Steel being Andrew Carnegie's industry and Banking being Andrew Mellon's industry
- Considered more heavy duty than CLISP
- Also provides debugger and interpreter
Common Lisp Syntax
Anonymous (Lambda) Functions
- This creates a lambda function that doubles the input
(lambda (x) (* 2 x))
Cons Pairs (Dotted Lists) vs Lists
- A list is really a chain of cons that ends with an empty list or nil
- For exmaple
'(1 2 3)isequalto(cons 1 (cons 2 (cons 3 nil)) - Any cons cell that doesn't end in nil is known as a dotted list
(cons 1 2)is displayed by the REPL as(1 . 2)- You can create dotted lists with a quote as well,
'(1 . 2) - You can make a list using quote and dot,
'(1 . (2 . (3 . nil)))is equal to'(1 2 3) (cdr '(1 . 2))is2not(2)(cdr '(1 2))is(2)not2- Cons pairs are useful for x/y coordinates and key value pairs
Circular Lists
- When using circular lists you should set the global
*print-circle*to true,(setf **print-circle** t) - The above enables complex printing features to be enabled when using self-referential lists
- Below is an example of creating a circular list
(defparameter x '(1 2 3)) (setf (cdddr x) x)
- You can use infinitely large index values in the above example
- For instance
(nth 1000 x)would return2
Literals
Char
- You can represent a character literal with a
#followed by a backward slash and the character - For example
#\ais the representation of the charactera
Newline
#\newline
Tab
#\tab
Space
#\space
nil
- nil can be represented with
nil,'nil,(), or'() nilis the only Lisp object that is both a symbol and a list
Hex Numbers
#xFF
Symbols
- Symbols in Common Lisp are case insensitive
- It is common to only use lowercase when writing common lisp source code
- To make a symbol that is case sensitive surround it with pipe,
|, characters- This also allows you to use punctuation in a symbol name
|Ca$e mATT3rz|is an example
Quoting
- Use the single quote
'to quote a list - To quasiquote use the backtick
`and the comma to unquote, `(one plus two is ,(+ 1 2))- The
#before the quote is used to indicate the quoted symbol is a function - Common Lisp is a LISP-2 rather than a LISP-1 like Scheme
- This means it has 2 separate scope for functions and data
- You can have a function and data with the same symbol name
- The caveat is you must use the
#when quoting a function
- When quasiquoting use a splicing command
,@lists are spread when the quoted code is expanded
Variables
Globals
- Use
defparameterto create global variables- The
defparameterfunction will mutate the value of a global if it already exists
- The
- It is a common practice to surround global variables with earmuffs (asterisks)
- For example a global named num would look like this
*num* - Another option to create globals is
defvar- If the variable already exists
defvarwill not change the existing value
- If the variable already exists
Local variables
- Use
letfunction to define local variables, you cannot reference other variables in the list - The
let*function is the same asletbut it lets you reference preceding variable names
(let* ((x 10)
(y (* 2 x)))
(+ x y))
Mutating
- Use
setfto change the value of an existing variable
Functions
Globals
- Use
defunto define a global function
(defun function_name (arguments) ...body)
Local Named Functions
- To define a local function use the
fletform fletis very similar toletfor variables but instead
(flet ((function_name (arguments)
...function body))
...body)
- If you want to reference other local functions in an
fletyou should uselabelsinstead - You can also
labelsto call a local named function recursively
(labels ((function_a (n)
(+ n 2))
(function_b (n)
(function_a (function_a n))))
(function_b 2))
Argument Limits
- There is a limit on the number of arguments a function can have
- You can check those limits in the Common Lisp REPL with
call-arguments-limit
Optional Arguments
- Adding
&optionalbefore an argument marks it as optional - This means that when the function is evaluated if you do not supply that argument it will not error
(defun fn-opt (not-optional &optional (val 10)) (list not-optional val)) (fn-opt 100)
- The above example shows an optional argument
valwith a default value of10 - It returns
(100 10)
Variadic Arguments
- Using
&restfollowed by a variable name collects a variable amount of arguments into a list
(defun fn-var (x &rest args) (append (if (listp x) x (list x)) args)) (fn-var (fn-var 10) 100 10 10)
- The above example returns the list
(10 100 10 10)
Keyword Arguments
- Using
&keyall of the arguments appearing after it will be named - To use a named argument it must be formatted with
:<NAME_OF_ARG - The preceding colon is a keyword form
(defun fn-key (&key x y w h) (list x y w h)) (fn-key :w 20 :h 10 :x 5 :y 2)
- The above example returns the list
(5 2 20 10) - It shows how when the arguments are used as keywords the order does not matter
Body Argument
- The
&bodyis the same as&restbut it allows editors to indent differently for the remainder since it is abody &bodycan only be used with macros
Default Values
- Optional and keyword arguments can have a default value
- To specify the default value you create a list with that starts with the local name of the argument followed by the default value
- Additionally you can supply a third item to the list that is a boolean indicating if the value was set or not
(defun fn-default (&optional (val 12 val-set)) (list val val-set)) (fn-default 100)
- The above example returns the list
(100 t)
Closures
- Referencing variables in a lambda expression can prevent values from being garbage collected
- This can be useful for memoizing functions
(let ((count 0)) (defun say-hello-count () (incf count) (format nil "Hello, for the ~a time" count))) (say-hello-count) (say-hello-count)
- The above example shows using lexical binding to capture a closure variable inside the function
- The count variable is incremented each time you call
say-hello-count - The
defunmacro will allow you to callsay-hello-countoutside of thelet
Get the function bound to a symbol
- Use the
symbol-functionfunction to get the function bound to a symbol - This is useful if you want to create a higher order function that wraps an existing function and you need to store the original function
Tail Call Optimization
- Tail call optimization is a way to get additional performance with recursive functions
- Normally when a function has a recursive call it will add to the stack
- If the function has too many recursive calls it can lead to a stack overflow
- If the recursive call is the final statement in the function the lisp compiler can recognize this as a candidate for tail call optimization
- The tail call optimization is the compiler not adding to the stack since you are already at the correct location
- Not all lisp compilers support this since it is not part of the standard, unlike Scheme which requires tail call optimization
- In clisp you need to explicitly
compilethe function to get the tail call optimization
Nullary Functions (Thunks)
- A nullary function is a function that has no arguments
- They are also commonly known as thunks or suspensions
Returning multiple values
- It is possible to write a function that returns multiple values
- When returning multiple values the first value returned is given preference when chaining the function
- This behavior could be achieved with a list but if it is a special case when you need more than one value this could be cleaner
values
- Use the
valuesfunction to return multiple values from a function
(defun multi-v () (values 'foo 'bar)) (multi-v)
- The above example will return
'foo; 'bar
multiple-value-bind
- To access all of the values returned by a function use
multiple-value-bind - This allows you to name the values returned by the function and access them in a
letstyle block
(multiple-value-bind (x y) ((lambda () (values 'foo 'bar))) (list x y))
- The above example will return the list
(foo bar)
Eval
- You can evaluate a quoted symbol with
eval (eval '(+ 2 2))should return4- Like
evalin JavaScript it can be a huge security risk in your program
Loops
loop
- Loops can be a good alternative to recursion, especially when using an implementation that does not have tail recursion support
- The most basic form of the
loopis shown below
(loop (sexp) (sexp) ... (when (predicate) (return)))
- The
returncommand exits the loop - Loop can be used to create lists
- Loop has a few keywords that tell it how to behave
- The
collectkeyword specifies what you want to put into the returned list for this iteration - The
repeatkeyword indicates how many times a loop should run
(loop repeat 10
collect 8)
- The
forandfrom/tokeywords let you specify a variable local to the loop that increments each loop iteraction - This is inclusive and on both ends
(loop for n from 1 to 10
collect n)
- The above example will return
(1 2 3 4 5 6 7 8 9 10) - The
forandbelowkeywords start from 0 and iterate through every integer less than the value afterbelow
(loop for i below 10
collect)
- The above example will return
(0 1 2 3 4 5 6 7 8 9) - There are many special tokens that can be passed to the
loopmacro that do special things
(loop for y below 10 collect (loop for x below 10 collect (cons x y)))
- The above example generates all of the 2d points between 0 - 10
- Each row is a list of cons cells of points for a given y value
(defparameter *test* '(1 2 3)) (loop for i below (length *test*) do (princ (format nil "~d = ~d~%" i (nth i *test*))))
- The above example returns the following
0 = 1 1 = 2 2 = 3
dotimes
dotimestakes a variable and an upper bound- It will perform the actions inside the body up to the upper bound
(dotimes (i 4) (princ (format nil "Loop #~d~%" i)))
- The above example will print the following output
Loop #0 Loop #1 Loop #2 Loop #3
Macros
- Use
defmacroto define a lisp macro - Macro definition is similar to function definition
- A macro usually return a quoted or quasi-quoted lisp expression that is expanded during compile time
- In order to view an expanded use the
macroexpandfunction - It is possible to define a macro that adds lexical variables, macros that do this are called anaphoric macros
Common Lisp Functions
Math
Incrementing
- You can use
1-to decrement by 1,(1- 10)evaluates to 9 - You can also use
1+to increment by 1,(1+ 9)evaluates to 10 - Additionally there are
incfanddecfto increment or decrement a value in place - Like
setftheincfanddecffunctions must be called with a variable as a parameter
(defparameter n 0) (incf n) (incf n 2) (decf n 3)
- In the above example
nstarts at 0, then goes to1, then to2and finally back to0 - The
incfanddecffunctions also return the new value in addition to mutating the variable
Exponent
- Use
exptto raise a number to an exponent,(expt 53 53)raises 53 to the 53rd power
Random numbers
- Use the
randomfunction to generate a random number - It takes the limit as an argument which can be either an int or a float
- The returned number will be between 0 and the limit
- If it is a float than the random number generated will also be a float
- This has the side effect of changing the internal
random-state
Arithmetic Shift (bitwise shift)
- https://en.wikipedia.org/wiki/Arithmetic_shift
- Use the
ashfunction which takes two arguments, the number and the amount of bits to shift left - To shift right use a negative number
Round
- The
roundfunction will take a number and return the two values, the rounded integer and the remainder - For example,
(round 16.8)returns17; -0.2 - When rounding up the remainder will be negative
Modulus (Remainder)
- Use the
modfunction to get the remainder between two numbers
(mod 5 2)
- The above example returns 1
Floor / Integer Truncation
- To get the integer truncation between two numbers use the
floorfunction
(floor 5 2)
- The above example returns 2
Largest and Smallest Integers
- The Common Lisp standard defines variables for the largest and smallest integers
most-positive-fixnumis the largest integermost-negative-fixnumis the smallest integer
Using / to get the reciprocal
- When you use the
/function with only a single value it will return the reciprocal - For example
(/ 4)returns1/4
Strings
Concatenate
- Use
concatenateto join multiple strings together - Use the symbol
'stringas the first argument to the function (concatenate 'string "abc" "def")should return"abcdef"
Converting to and from character lists
- Use
coercewith either thelistorstringtype (coerce "ABC" 'list)should return(#\A #\B #\C)(coerce '(#\A #\B #\C) 'string)should return"ABC"- This can be used with
mapcarto iterate over each character
(mapcar #'(lambda (c) (princ (format nil "char: ~C~C" c #\newline))) (coerce "abc123" 'list))
Converting symbols to strings
- Use
prin1-to-stringto convert symbols to strings (prin1-to-string 'abc)should return "ABC"- The
write-to-stringfunction also can convert data to strings
Multi Line Strings
- Lisp will store the newline character in the string
- So the
\ncharacter is not needed for multi line strings
(setq mystring "the first line the second line and the third")
- To insert a newline into a string use the
formatfunction and the~Ccontrol character - The
~Ccontrol chacter means insert a character literal, you can use the#\newlinecharacter literal to get a new line
(format nil "hello~Cworld" #\newline)
- Another way to do this is with
~%
(format nil "hello~%world")
Parsing an Integer from a String
- The
parse-integerfunction can be used to parse an integer from a string - The
:radixkeyword lets you specify the base of the number - The
:junk-allowedkeyword lets you indicate you want to just return nil if there is junk in the string
(parse-integer "beef" :radix 16 :junk-allowed t)
- The above example returns the integer 48879
Getting the Integer Code of an Char
- The
char-codefunction will return the integer ASCII code for the char - The
code-charwill give you the char for an integer ASCII code of a char
(code-char (char-code #\A))
- The above example will return the character
A
#\A
Turning a String into a Lisp Symbol
- The
internfunction can turn a string into a Lisp symbol
(intern "abc")
- The above example returns the symbol
abc - There is some overlap between
readandintern, in general if theinternfunctions does what you need then it is preferable since it is safer than thereadfunction
Getting the Position of Char in String
- The
positionfunction can be used to get the index of a char in a string, since a string is a sequence of characters - See Index of item in list
Creating a Fixed Size String
- The
make-stringfunction will create a string with a given length
(make-string 10 :initial-element #\x)
- The above example creates a string of length 10 filled with the char
x
Format
- Wikipedia: Format (Common Lisp)
- The
formatfunction takes 3 parameters, destination, control string and variadic values - The destination
nil,tor some stream - When the destination is
niltheformatfunction returns the formatted string - When it is not nil it will return nil and send the output to either
stdoutwithtor some other stream specified
Format Directives
c- single characterr- radix based- decimal (base 10) numberb- binary (base 2) numbero- octal (base 8) numberx- hexadecimal (base 16) numberf- floating point numbere- exponent notation for numberg- exponent or float, pickign automatically$- print with monetary conventionsa- print in human friendly manners- print symbol in format compatible with read functionw- print with printer control charactersi- indent a logical blockt- move cursor to columnp- prints singular or plural suffix%- newline unconditionally, similar toterpi&- newline conditionally, similar tofresh-line
Examples
- Strings
(format nil "Your message is: ~a~%" "Hello, World")
- Left pad zeroes
(format nil "You number is: ~3,'0d," 12)- The above example will ensure that the decimal value is 3 digits long and will pad to the left with
0if it is less than 3 digits
- The above example will ensure that the decimal value is 3 digits long and will pad to the left with
- Looping with Format
- You can loop through lists with format using
~{and~}
(format nil "~{The list has: ~a~%~}" '("abc" "def" "ghi"))
- You can loop through lists with format using
Lists
push
- Adds item to the beginning of a list
- The list must be a variable
(defparameter *some-list* nil) (push 4 *some-list*) (push 3 *some-list*) (push 2 *some-list*) (push 1 *some-list*) *some-list*
last
- You can get the last element of a list with
last
Using push to append
- Since a list is just a
conspairs, creating a newconspair with the last element will append - The
cdrof the last element of a list is an empty list ornil - If you
pushinto that empty list you will append to the list - For instance if
ais(1 2 3)this should append 4 to the list(push 4 (cdr (last a)))
pushnew
- The
pushnewfunction will only add an item to a list if it is not already in it
(defparameter *some-list* '(1 2 3)) (pushnew 1 *some-list*)
- In the above example 1 is already in the list so the results of
pushnewis the same list(1 2 3)
member
- Checks to see if an item is inside a list
(member 1 '(1 2 3 4))- This will return true when you check if
nilis in the list
find
- Use find to search through a list for the first item that matches
- The search value is the first argument
- The second argument is the list that is being searched
- The keyword parameter passed with
:keytells find how to determine if the list item matches the search (find 20 '((a 5) (b 20) (c 6) (d 20)) :key #'cadr)should return(b 20)
find-if
- Returns the first item in a list that satisifies the predicate
(find-if #'oddp '(2 4 5 6))- Returns nil if the item is not found
- This will not work when searching for
nilin a list
mapcar
- Use
mapcarto run a function on each element of a list (mapcar (lambda (n) (1+ n)) '(1 2 3))should return(2 3 4)- You can also run
mapcarover multiple sequences (mapcar (lambda (m n) (list m n)) '(1 2 3) '(a b c))should return((1 a) (2 b) (3 c))mapcis a more efficient version of mapcar that does not return the listmaplistis another variant ofmapcarthat gives the remainder of the list as an argument to the function rather than a single item
mapcan
- Similar to
mapcarbut allows the lambda to return a list - The list values are all appended together in the result
(mapcan (lambda (v) (case v (a '(1 2 3)) (b '(4 5 6 7)) (c '(8 9 10 11 12)))) '(a c b))
- The above example will return
(1 2 3 8 9 10 11 12 4 5 6 7)
apply
- Use
applyto call a functions once with all the elements of a list as its arguments
(defparameter *rect* '(40 40 20 30)) (defun rect-area (x y w h) (* w h)) (apply #'rect-area *rect*)
remove-if-not
- Removes all items from the list that do not satisfy the predicate
(remove-if-not #'oddp '(1 2 3 4 5))should return(1 3 5)
nth
- Use
nthto get the value at index n from a list (nth 2 '(7 8 9))should return9
use setf and nth to change list item value
- You can use
setfto mutate a list - For example if you have a list named
lwith the value(1 1 1) (setf (nth 2 l) 4)should mutatelto be(1 1 4)
subseq
- Use
subseqto get a sub sequence of a list - The start index is required and you can optionally add the end index
- The start index is inclusive and the end index is exclusive,
(start end] (subseq '(9 8 7 6) 1 3)should return(8 7)
Swapping list items with rotatef
- If you have the list
xwith the value(1 2 3) - You can swap the 1 and with like so
(rotatef (nth 0 x) (nth 2 x))- This should return
(3 2 1)
- This should return
- This will mutate the list
concatenate
- Use
concatenateto join multiple lists together - Use the symbol
'listas the first argument to the function (concatenate 'list '(1 2 3) '(4 5 6))should return(1 2 3 4 5 6)
Slicing an item out of a list
- You can generate a new list with a particular index sliced out using
concatenateandsubseq - If you have the list
dwith the value(1 2 3 4 5) (concatenate 'list (subseq d 0 2) (subseq d 3))should return(1 2 4 5)
Testing the values of a list with every some notevery notany
- These functions run a predicate and return a different boolean value based on their rules
everyreturns nil at the first instance of anilvalue, similar to logical andsomereturns true if any of the values return truenotanyreturns nil if any of the values return truenoteveryreturns true if all the values are false(every #'identity '(t t nil)should return nil(every #'identity '(t t t))should return true
substitute-if
- Replaces every item in a sequence with the first argument if it passes the second argument predicate
- For instance,
(substitute-if 0 #'oddp '(1 2 3 4 5))will return(0 2 0 4 0)
Index of item in list
- The index of the first instance of an item in a list can be found with the
positionfunction
(position 2 '(1 2 3 4 2))
- The above code will return
1since that is the index of the first 2 in the list
Difference between lists
- To get a list of the items that are difference between a list use the
set-differencefunction - This function gives you the items that are in the first list that are not in the second list
(set-difference '(1 2 3) '(a 2 c))should return the set(3 1), the order in the set does not necessarily match the order in the first list(set-difference '(a 2 c) '(1 2 3))should return the set(c a)
Intersection between lists
- To get the intersection of two lists use the
intersectionfunction (intersection '(1 2 3) '(a 2 c))should return(2)
Remove Duplicates
- The
remove-duplicatesfunction will return a list where no item repeats - You can give it a custom
:testfunction to use when comparing items (remove-duplicates '(1 1 2 2 3 3))should return(1 2 3)
Appling a predicate to a list with some
- The
somefunction will run a predicate on each item in a list in order - The first time the predicate returns true the it stops checking the list
(some #'oddp '(2 2 4 8 6))
- The above example will return
nilsince no members of the list are odd
(some #'oddp '(2 2 4 5 6))
- The above example will return
tsince there is one odd member
reduce
- Iterate through a sequence and reduce it down to a single value
- The
reducefunction takes two arguments, a function that reduces two items to 1 and a sequence of items
(reduce #'* '(1 2 3 4 5 6 7))
- The above example will return 5040, which is 1 * 2 * 3 * 4 * 5 * 6 * 7
- In the reducer function the first argument is the accumulated value and the second is the current item in the list
- It is possible to set an initial value for the accumulated value with the
:initial-valuekey
(reduce (lambda (acc i) (if (oddp i) (+ acc (* i 2)) acc)) '(2 4 5 1) :initial-value 100)
- The above example results in 112, since the initial value is 100 and the only two odd values in the list are 5 and 1 which * 2 are 12
- The reduce function is generic and can be used on all sequence types (arrays, lists, strings)
sort
- The sort function allows you to arbitrarily sort a list
(sort '(1 2 3 4 5 6) #'>)
- The above example sorts the list in descending order
Association Lists (alists)
- Use
assocto find the value of a key in an alist
(assoc 'mykey '((somekey (some-value))
(mykey (my-value))
(otherkey (other-value))))
- alists can have multiple instances of a key inside them
- When this happens
assocwill return the first instance - If you push new keys into the alist you can overwrite the value of a key while preserving the previous value
- You can use
setfto change the value of an list,(setf (cadr (assoc '2 alist)) t) - alists are not very efficient beyond a dozen items
- alists can also be implemented using cons pairs
(assoc 'mykey '((somekey . somevalue)
(mykey . myvalue)
(otherkey . othervalue)))
Logic / Conditionals
Complementing Predicates
- If you have a predicate and you want the opposite of it or complement you can use the higher order function
complementto achieve that - For example
(substitute-if 0 (complement #'oddp) '(1 2 3 4 5))should return(1 0 3 0 5)
Shortcut Boolean Evaluation
- When evaluating an
ororandboolean operator lisp will stop when it encounters the first symbol that evaluates to either true or false - For example when evaluating an
orthe first true that is encountered causes lisp to stop - When evaluating an
andthe first false encounted causes lisp to stop - This allows you to build conditionals out of
ororandstatements
- and
(defun pred_a () t) (defun pred_b () t) (defun work () 'work-when) (when (pred_a) (when (pred_b) (work)))
(defun pred_a () t) (defun pred_b () t) (defun work () 'work-and) (and (pred_a) (pred_b) (work))
- or
(defun pred_a () nil) (defun pred_b () nil) (defun work () 'work-unless) (unless (pred_a) (unless (pred_b) (work)))
(defun pred_a () nil) (defun pred_b () nil) (defun work () 'work-or) (or (pred_a) (pred_b) (work))
Using case to switch through multiple options
- The
casefunction can be used to handle multiple conditionals for a single value - A conditional named
otherwise
(defvar x 1234) (case x (123 (princ "x is 123")) (456 (princ "x is 456")) (otherwise (princ "beats me")))
- The above example prints "beats me" since
xdoes not match any of the conditionals
Equality
- Use
eqfor comparing symbolseqreturns true when two symbols point to the sameconseqlwill also return true when characters and numbers are used instead of symbols
- Use
equalfor comparing everything elseequalwill tell you if two things are isomorphic (look the same)equalpwill return true when strings have different capitalization, or numbers are not the same type (floats vs ints)
- The
=comparison is meant primarily for numbers string-equalis specific for stringschar-equalis specific for chars
I/O
Printing and Reading
- Use
printto display a string on stdout- This will automatically add a new line at the end of the string
prin1andprincwill not add thenewlineprintwill print values as they are stored in Lisp, so strings will have quotes and literals are displayed as such(print #\newline)will actually print#\newlineto stdout
- Use
princto not add the quotation marks and use the characters the literals represent(princ #\newline)will just print an empty line
- The goal of
printis to output data in a way that it could be re-read back into its internal representation
- Use
readto read from stdin- This function is called with no arguments and returns after the user has typed something and pressed enter
- You can use this to assign the value into a variable:
(let ((user-input (read))))
- Both
printandreadcan handle any Lisp data type, including symbols - Use
read-lineto read the input as a string only rather than any valid Lisp data - Use the function
fresh-lineto print a new line if the cursor is not at the beginning of a line:(fresh-line) - The
terprifunction will always print a new line even if the cursor is at the beginning of the line - The
read-sequencefunction will fill a sequence with items from a stream
Files
with-open-fileoptionally accepts a steam and file name to open a file- If you do not pass in an existing stream a new one is created
- With the stream variable print functions can send their output to that file
- If the stream is
*standard-output*then the print functions will automatically send the output to the file
(with-open-file (stream "~/tmp/testfile.txt" :direction :output :if-exists :supersede) (princ "Hello World!" stream) (princ #\newline stream))
- The below example shows routing standard output to a file and appending to the file
- The
finish-outputfunction will empty the buffers into the file
(with-open-file (*standard-output* "~/tmp/test.log" :direction :output :if-does-not-exist :create :if-exists :append) (loop repeat 100 for x from 0 do (progn (fresh-line) (format t "loop #~d" x) (finish-output) (sleep 1))))
- There is a global stream
*standard-ouput*that represents stdout of the lisp environment
Arrays
- Any one dimensional array is also known as a vector
make-array
- The
make-arrayfunction is used to create an array - It takes an argument that specifies the size of the array
- It will return an array that size with each element initialized to
nil
aref
- To access a member of an array use
aref - For example, say you have
some-listthat has this value,#(1 2 3)to access array index 1 witharefyou would do this,(aref some-list 1), this will return 2 arefcan be combined withsetfto mutate an array(setf (aref some-list 1) 5)will mutatesome-listinto#(1 5 3)
Hash Tables
make-hash-table
- The
make-hash-tablefunction will create an empty hash table
gethash
- The
gethashfunction is used to access items from a hash table - The first argument of the
gethashfunction is the key, the second is the hash table (gethash 'foo some-hash-table)will access thefookey in the hash tablesome-hash-table- This can also be used with
setfto set values in the hash table,(setf (gethash 'foo some-hash-table) 'bar) - The
gethashfunction returns multiple values, the first is the value stored in the hash-table for the key, the second is whether or not that key was in the table - This is needed since a key could be present in the table but have
nilset as its value
Key Equality
- By default hash tables use
eqto compare equality - This will cause issues when you want to use a symbol as a key
- The equality function in a hash table can be changed using the
:testkey inmake-hash-table (make-hash-table :test #'equal)lets you define a table whose keys are compared with theequalfunction instead ofeq- This is useful when you want to have keys are cons pairs
Removing entries with remhash
- The
remhashfunction mirrors thegethashfunction but it removes the key from the table
(defparameter my-hash (make-hash-table)) (setf (gethash 'foo my-hash) 'bar) (remhash 'foo my-hash)
- The above example adds and removes a key from the
my-hashtable
Profiling
time
- The
timefunction will perform the argument function and return a lot of use profile information
Real time: 0.73878 sec. Run time: 0.738236 sec. Space: 31004976 Bytes GC: 26, GC time: 0.098185 sec.
- The above is an example of some of the profile data that the
timefunction will return
Generics
setf
- In general the code for getting data out of something is the same as code for putting something in
- The
setfcommand is a generic setter that can put data into data structures using the accessor as an argument - The following
setfexample changes the third item of the listfoo,(setf (third foo) 'bar) - The first parameter in
setfis a generalized reference - A generalized reference parameter can be arbitrarily complicated, meaning whatever path needed to access the reference you will still be able to mutate it with
setf
Type Predicates
- Common Lisp has dynamic typing, so a symbol can be any type
- This is a list of all the type predicates:
arraypcharacterpconspfunctionphash-table-plistpstringpsymbolpatom(this is the opposite ofconsp, anything that is not a cons cell matches)nullOnly returns true if input isnil, same as thenotpredicate
- These predicates an be used to implement generic functions
defmethod
- The
defmethodmacro will allow you to define separate functions for each supported argument type - This can help with code readability so you don't have have a long conditional that checks the type of the arguments
- When using
defmethodyou need to explicitly state the type of each argument
(defmethod add ((x number) (y number)) (+ x y)) (defmethod add ((x string) (y string)) (format nil "string add:~%~4tx: ~a~%~4ty: ~a~%" x y)) (add (format nil "num add: ~d" (add 100 -100)) (format nil "num add: ~d" (add 2 2)))
- The above example will produce the following ouput
string add:
x: num add: 0
y: num add: 4
type-of
- The
type-offunction will return the type of the variable - For example,
(type-of 12)will returninteger (type-of 'hello)will returnsymbol
Exceptions / Errors
- To generate an exception or error use the
errorfunction
(error "this will generate an error")
- The above example shows generating a simple error with a custom error message
Custom Error Conditions
- The function
define-conditionallows you to define a custom error condition
(define-condition my-error-condition () () (:report (lambda (condition stream) (princ "custom report message for `my-error-condition'" stream)))) (error 'my-error-condition)
- The above example creates a custom error condition with a customized report
- Use the
errorfunction and the symbol of the condition to trigger it
Handling Errors
- The
handler-casefunction allows you to intercept an error
(define-condition my-error-condition () () (:report (lambda (condition stream) (princ "custom report message for `my-error-condition'" stream)))) (defun going-to-error () (error 'my-error-condition)) (handler-case (going-to-error) (my-error-condition () "Intercepted `my-error-condition'"))
- The above example shows using
handler-caseto intercept a particular error symbol
Generate Random Symbols
- Sometimes its useful to generate a random symbol
- This can be handy for macros that need to lexically bind a value but don't want to accidentally have variables shadowed by lisp forms supplied by the user
- The
gensymfunction will return a random unique symbol
Sleeping
- Common Lisp has a
sleepfunction that takes the number of seconds you want to sleep as an argument
Common Lisp Data Structures
List
- Most fundamental data structure in a lisp
- A series of nested
conspairs terminated with nil - The access time for elements inside lists is not constant
Array
- Arrays are like like lists except the access time for any element is constant
- An array literal is preceded with a
#to distinguish it from a list, for example,#(1 2 3) - Arrays are in general faster than lists when accessing or setting specific elements
Hash Table
- A hash table is very similar to an alist
- A hash table literal is preceded with a
#Sto distinguish it from a list and an array - Similar to arrays hash tables have a constant look up time
- Hash tables are less efficient than alists for really small tables
- Very large hash tables can be paged out to virtual memory which could have poor performance
- Lisp will sometimes need to reallocate the memory for the hash table when inserting a key
- This will cause an occasional slow key insertion
- Both arrays and hash tables are not considered very lispy
- They are best avoided until performance concerns arise
Structures
defstruct
- The
defstructmacro is useful for building structured data out of lists - The
defstructmacro will also create functions for building and accessing the data from the struct
(defstruct rectangle x y width height) (defparameter *my-rect* (make-rectangle :x 10 :y 10 :width 50 :height 25)) (rectangle-height *my-rect*) (setf (rectangle-x *my-rect*) 85) *my-rect*
- The above example shows creating a structure with
defstructand building one with themake-<STRUCT_NAME>function - It also shows using a generated accessor function
<STRUCT_NAME>-<PROPERPTY_NAME> - The above example produces the following struct literal,
#S(RECTANGLE :X 85 :Y 10 :WIDTH 50 :HEIGHT 25)
Default Values
- It is possible to provide default values to a struct by wrapping the slot with parenthesis and providing a value
(defstruct rectangle (x 10) (y 10) (width 100) (height 100)) (make-rectangle)
- The above example shows defining a struct with default values
- When initializing the struct the slots can be omitted and they will use the default values
- The above example returns the following struct literal,
#S(RECTANGLE :X 10 :Y 10 :WIDTH 100 :HEIGHT 100)
Including Another Structure
- It is possible to declare a struct that includes the slots from another struct
- The syntax for doing this is to wrap the structure name in parenthesis then add a
(:include some-other-struct)inside
(defstruct point (x 5) (y 5)) (defstruct (circle (:include point)) (radius 4)) (make-circle :x 25 :y 30 :radius 10)
- The above example shows creating a
circlestructure that inherits all the slot from thepointstruct - This example returns this struct literal,
#S(CIRCLE :X 5 :Y 5 :RADIUS 4)
Streams
- There are two types of streams
inputandoutput - The predicates
input-stream-pandoutput-stream-pwill indicate which direction a stream is - The standard
printandreadfunctions work with streams - Streams support most of the functions that lists do with the exception of
setf - A string stream is useful for testing code that operates on streams
(with-output-to-string (*standard-output*) (princ "Hello world!"))
- The above example shows a with form that will collect anything that would normally be sent to standard output and instead return it as a string
Quick Lisp
- Quicklisp is a library manager (package manager) for Common Lisp
Installation
- Download
quicklisp.lispform their website,curl -O https://beta.quicklisp.org/quicklisp.lisp - Download the PGP signature from their website,
curl -O https://beta.quicklisp.org/quicklisp.lisp.asc - Download the release signing public key,
curl -O https://beta.quicklisp.org/release-key.txt - Import the release signing key,
gpg --import release-key.txt - Verify the
quicklisp.lispfile,gpg --verify quicklisp.lisp.asc quicklisp.lisp - Start up a Common Lisp environment
- Inside the REPL run,
(load "quicklisp.lisp")(Ensure the REPL was started in the same folder Quicklisp was downloaded to) - Run
(quicklisp-quickstart:install)to install Quicklisp - This will create a
quicklispfolder in your home directory - Normally when starting a new lisp session you would run
(load "~/quicklisp/setup.lisp)to load Quicklisp into the running session - Use
(ql:add-to-init-file)to have this happen automatically - Install the
slime helper,(ql:quickload "quicklisp-slime-helper")- Be sure to follow the instructions
Installing Libraries
- This is an example of installing a package,
(ql:quickload "vecto") - This is an example of uninstalling a package,
(ql:uninstall "vecto") - To search for a library to install use apropos,
(ql:system-apropos "sdl2")
Upgrading Quicklisp
- Use
(ql:update-dist "quicklisp")to update Quicklisp - Run
(ql:update-client)to update the client
Examples
Split String into List of Words
(defun split-by-one-space (string) "Returns a list of substrings of string divided by ONE space each. Note: Two consecutive spaces will be seen as if there were an empty string between them." (loop for i = 0 then (1+ j) as j = (position #\Space string :start i) collect (subseq string i j) while j))