Skip to content

Instantly share code, notes, and snippets.

@gosukiwi
Last active April 16, 2024 13:59
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save gosukiwi/04b7680efa08bb9affd3b0c3087d7126 to your computer and use it in GitHub Desktop.
Save gosukiwi/04b7680efa08bb9affd3b0c3087d7126 to your computer and use it in GitHub Desktop.
Common Lisp Cheatsheet

Common Lisp Cheatsheet

Common Lisp is a general-purpose programming language with functions as first-class citizens. Don't worry about being purely functional, Lisp is Object Oriented too. CLOS is a very powerful object-oriented system!

Useful definitions

The Common Lisp lingo is quite unique:

  • Package: Basically a namespace, a place for symbols to live
  • System: Basically a Library. A bunch of code plus some instructions how it should be treated, for example which other systems it depends on, what should be loaded and/or compiled first, etc. Not in ANSI lisp but widespread. The most common system definition tool is ASDF.
  • Modules: Deprecated and implementation-dependent
  • Quicklisp: Like NPM or Ruby Gems for ASDF Systems.

Variables

You can define, set and get variables as follows:

(defvar *foo* 1)
(setf *foo* 2)
(getf *foo*) ;; => 2

The "earmuffs" are an optional convention, it means "warning, this can change".

setf and getf can be used for structs, object and pretty much everything.

Equality Predicates

  • (eq x y) is true if and only if x and y are the same identical object.
  • (eql x y) is true if its arguments are eq, or if they are numbers of the same type with the same value, or if they are character objects that represent the same character.
  • (equal x y) is true if its arguments are structurally similar (isomorphic) objects. A rough rule of thumb is that two objects are equal if and only if their printed representations are the same.
  • (equalp x y) is true if they are equal; if they are characters and satisfy char-equal, which ignores alphabetic case and certain other attributes of characters; if they are numbers and have the same numerical value, even if they are of different types; or if they have components that are all equalp.

Format

format is like C's printf. It will replace some wildcards in the string with the values passed. The most common use case are either print something to the screen or return the value as a string.

(format nil "~A-COMMAND" "FOO") ;; => "FOO-COMMAND"
(format t "~A-COMMAND" "FOO") ;; => nil (prints "FOO-COMMAND" to the screen)

~A stands for Anything, it works most of the time and is the most common wildcard used. See this chapter of Practical Common Lisp for more on this.

Functions

(defun my-fun ()
  ...)

Objects

More info: Brief Guide to CLOS.

(defclass person ()
  ((name :accessor person-name)
         :initform "default-name"
         :initarg :name)
   (age  :accessor person-age
         :initarg :age)))
         
;; make new person
(make-instance person :name "Boris" :age 89)

;; inheritance
(defclass boss (person)
  ...)
  
;; methods
(defmethod greet ((p person))
  (format t "Hello ~a! You are ~a years old." (person-name p) (person-age p)))

Data Structures

;; lists
(list 'foo 'bar 1)
'(foo bar 1)

;; plists (property lists)
(defvar *foo* '(name "Mike"))
(getf *foo* 'name) ;; => Mike

(getf '(name "Mike") 'name)

Conditions and Restarts

You can use conditions pretty much the same way you would use a Try/Catch:

;; Define condition/error
(define-condition malformed-log-entry-error (error)
  ((text :initarg :text :reader text)))

;; Use the `error' function to trigger it
(defun parse-log-entry (text)
  (if (well-formed-log-entry-p text)
    (make-instance 'log-entry ...)
    (error 'malformed-log-entry-error :text text)))

;; Use `handler-case' to handle conditions
(defun parse-log-file (file)
  (with-open-file (in file :direction :input)
    (loop for text = (read-line in nil nil) while text
       for entry = (handler-case (parse-log-entry text)
                     (malformed-log-entry-error () nil))
       when entry collect it)))

Instead of using handler-case you can use restart-case and specify how that error is restarted, so callers can choose a restart and abstract the implementation away

(defun parse-log-file (file)
  (with-open-file (in file :direction :input)
    (loop for text = (read-line in nil nil) while text
       for entry = (restart-case (parse-log-entry text)
                     (skip-log-entry () nil))
       when entry collect it)))

Callers (either direct or indirect) of a function which provides restarts can pick the restart they want by wrapping code in a handler-bind macro. handler-bind associates a condition with it's restart:

(defun skip-log-entry (c)
  (invoke-restart 'skip-log-entry))
  
(defun log-analyzer ()
  (handler-bind ((malformed-log-entry-error #'skip-log-entry))
    (dolist (log (find-all-logs))
      (analyze-log log))))

If you are not sure the restart will be there you can check by using find-restart:

(defun skip-log-entry (c)
  (let ((restart (find-restart 'skip-log-entry)))
    (when restart (invoke-restart restart))))

You can find more info on Conditions and Restarts in this chapter of the awesome book: Practical Common Lisp.

Making errors play nice with the debugger

Use :report for that:

(define-condition syntax-error-invalid-token (error)
  ((text :initarg :text :reader text))
  (:report (lambda (condition stream) (format stream "~a" (text condition)))))

Development Environment

For each project, you'll need to add it to ASDF's central registry. You can do this by adding something like this to your ~/.sbclrc file (.roswell/init.lisp if you are using Roswell):

(setf asdf:*central-registry*
   ;; Default directories, usually just the ``current directory''
  '(*default-pathname-defaults*

    ;; Additional places where ASDF can find
    ;; system definition files
    #p"/usr/my-user/workspace/some-project"
    #p"/usr/my-user/workspace/some-other"
    #p"/usr/my-user/other/more-projects"
    
    ))

Because it's a hassle to do this everytime you make a new project, if you use a UNIX-y OS you can use a dedicated folder to contain all ASDF system definitions (.asd files)

$ cd /home/me/cl/systems/
$ ln -s ~/src/foo/foo.asd .

Your central repository definition would then look like this:

(setf asdf:*central-registry*
   ;; Default directories, usually just the ``current directory''
  '(*default-pathname-defaults*

    ;; Additional places where ASDF can find
    ;; system definition files
    #p"/home/me/cl/systems/"))

Libraries

  • You can use Quickproject to make new projects
  • You can use rove for testing
  • Alexandria is an extension to Common Lisp to provide some overall goodies, kind of like ActiveSupport for Ruby

Debugging

You can use break and step and trace for debugging. Remember to enable the debug settings in the Lisp initialization file.

;; enable debugging
(declaim (optimize (debug 3)))

Once in the debugging SLIME session, you can step in, step over, and press e for evaluating a Lisp expression. More info here.

(let ((a 1))
  (break))

For break, just put it as a regular breakpoint in code. For step, use it to call a function:

;; in REPL
(step (my-func))

If you want to try some print debugging you can drop a (inspect ...) in the code and the execution will stop there and you will able to inspect whatever you want.

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment