Skip to content

Instantly share code, notes, and snippets.

@wortelstoemp
Created May 13, 2020 13:19
Show Gist options
  • Select an option

  • Save wortelstoemp/6a9339e4505fcfed6ce6178e3a08c538 to your computer and use it in GitHub Desktop.

Select an option

Save wortelstoemp/6a9339e4505fcfed6ce6178e3a08c538 to your computer and use it in GitHub Desktop.
Common Lisp Cheatsheet
;;;; Cheatsheet for Common Lisp
;;; REFERENCES
;;; - Info: https://anticrisis.github.io/2017/09/04/how-i-got-started-with-common-lisp-2017.html
;;; - Learning Common Lisp:
;;; http://www.newthinktank.com/2015/07/learn-lisp-one-video/
;;; https://lispcookbook.github.io/cl-cookbook/
;;; (Advanced) http://www.gigamonkeys.com/book/
;;; - Exercises:
;;; http://www.4clojure.com/problems
;;; - Install sbcl installer on http://www.sbcl.org/platform-table.html
;;; - Download quicklisp.lisp from https://beta.quicklisp.org/quicklisp.lisp
;;; - Install quicklisp:
;;; $ sbcl --load quicklisp.lisp
;;; (quicklisp-quickstart:install :path "./quicklisp")
;;; (ql:add-to-init-file)
;;; (quit)
;;; - Find libraries on http://quickdocs.org/
;;; - Use REPL:
;;; $ sbcl
;;; (format t "Hello, World!")
;;; - Create a project:
;;; $ cd my_projects
;;; $ sbcl
;;; (ql:quickload "cl-project")
;;; (cl-project:make-project #P"./name-of-my-project/")
;;; (quit)
;;; https://stackoverflow.com/questions/48103501/deploying-common-lisp-web-applications
;;; https://github.com/fare/asdf/blob/master/doc/best_practices.md
;;; BEFORE WORKING ON EACH PROJECT
;;; In the quicklisp/local-projects/system-index.txt file you need to
;;; put the path to the .asd file, e.g.: D:/Projects/Startup/kp/cl-photon-framework/cl-photon-framework.asd
;;; Better alternative: Symlink (on Windows/Linux) to appear as if the project is in quicklisp/local-projects
;;; - Using REPL: $ sbcl
;;; - Load lisp file in REPL:
;;; $ sbcl
;;; (load "hello.lisp")
;;; - Better: install Atom and SLIMA!
;;; - Compile the project
;;; $ sbcl
;;; (asdf:load-system "my-app")
;;; (ql:quickload :my-app)
;;; (asdf:make :my-app)
;;; Press 0 to continue if necessary
;;; or in 1 command to put in shell script:
;;; $sbcl --eval "(asdf:load-system :my-app)" ^
;;; --eval "(ql:quickload :my-app)" ^
;;; --eval "(asdf:make :my-app)" ^
;;; --eval "(quit)"
;;; C libraries on Windows: put them in same folder as lisp.exe of sbcl (or maybe in System32 ?)
;;; Debugging:
;; Use print/format in code
;; (inspect x) in REPL
;; Breakpoints: (break "x=~a, y=~a" 10 20)
;; Trace functions:
;; (trace f), use function, (untrace f)
;; Trace methods:
;; (trace foo :methods t)
;;; Remembering function names in Lisp:
;; Partially: (apropos "map" :cl)
;; Describe: (describe 'map)
;;; Performance tuning:
;; Assembly: (disassemble #'my-func)
;; Timing: (time (my-func 1 2))
;; Optimization: inline + more speed (3) and less safety (0) + type hinting + function types + avoid generic functions
;; YOU CAN'T PUT DECLARES IN MACROS!!!
;; (declaim (inline max-optimized))
;; (declaim (ftype (function (integer integer) integer) max-optimized))
;; (defun max-optimized (a b)
;; (declare (optimize (speed 3) (safety 0)))
;; (declare (type integer a b))
;; (the integer (max a b)))
;; or for entire file: (declaim (optimize (speed 3) (safety 0)))
;; Must re-compile callers when inlined function changes.
;; Types for variables:
;; (declaim (type (string) *str*))
;; (defparameter *str* "hello")
;;; ----------------------------------------------------------------------------
;;;
;;; Common Lisp
;;;
;;; ----------------------------------------------------------------------------
;;; ----------------------------------------------------------------------------
;;; Imperative programming
;;; ----------------------------------------------------------------------------
;;; Lisp code is a tree: (+ 5 (- 6 2)) ; => 9
(format t "Hello, World!~%") ; t (true) prints to console
; and returns nil, '~%' is newline, '~T' is tab)
(print "Hello, World!")
(terpri) ; print newline
(format nil "Hello!") ; nil (false) just returns string
;;; Global variables (and read from console)
(defconstant +c+ 20) ; constant
(defvar *var* (read)) ; keeps same begin value unless changed with setf
(defparameter *param* 15) ; can be assigned new value when recompiling
(defparameter *param* 16) ;
;;; Datatypes
;; integer: 10 #b1010 in binary, #o12 in octal, #xA in hexadecimal
;; #2r1010 in binary, #8r12 in octal, #16rA in hexadecimal
;; single-float: 1.0f0 (= default floating-point number 1.0)
;; double-float: 1.0d0
;; boolean: t / nil
;; character: #\A
;;; Change value of variable
(setf *number* 6)
;;; ---------- STRUCTS ---------------------------------------------------------
;;; Structs are always pass by reference as function parameter, so mutable!
(defstruct person
(id 0 :type integer)
(name nil :type string)
age)
;;; make on heap
(defparameter *me* (make-person
:id 9000
:name "Tom Q"
:age 28))
;;; make on stack (needs local scope!)
(let ((you (make-person
:id 69
:name "Sexy Lady"
:age 30)))
(declare (dynamic-extent you))
(format t "~s~%" you))
(person-name *me*) ; get
(setf (person-age *me*) 27) ; set
(format t "~s~%" *me*)
;;; predicate function to check type
(person-p *me*) ; => t
;;; ---------- LOCAL VARIABLES -------------------------------------------------
;;; (let (definitions) (body))
(let ((x 5) (y 10) (z 20))
(format t "~d ~%" (+ x y z))) ;; body
;;; Local variables which are used in other definitions
;;; (let* (definitions) (body))
(let* ((a 1)
(b (* a 2)))
(+ a b)) ;; body
;; rotatef: swap values of 2 variables
(let ((a 1) (b 2))
(print a)
(print b)
(rotatef a b)
(print a)
(print b))
;;; ---------- FUNCTIONS -------------------------------------------------------
(defun hello-world (name)
"Description of function"
(format t "Hello, world to ~a!~%" name))
(hello-world *name*)
;;; Functions returning multiple values (NOT a list!)
(defun test0 (x y)
(values
(+ x 1)
(+ y 1)))
(test0 10 20)
(multiple-value-bind (a b) (test0 10 20)
(format t "~d and ~d~%" a b))
;;; Functions with optional defaulted parameters
(defun test1 (x &optional (y 0) (z 0))
(+ x y z))
(test1 10)
;;; Test if an optional value is set
(defun test2 (&optional (y 0 y-set))
(if y-set
(format t "y is set: ~d~%" y)
(format t "y is not set~%")))
;;; Functions with any number of parameters
;;; DON'T USE &REST AND &OPTIONAL TOGETHER!!!
(defun test3 (&rest params)
(format t "~a~%" params)
(length params))
(test3 10 20 30 40)
;;; Functions with keyword parameters
(defun test4 (&key x y (z 0))
(format t "List: ~a ~%" (list x y z)))
(test4 :y 10 :x 20) ;; order doesn't matter
;;; Functions with parameters passed by reference
(defvar *x* 100)
(defun test-inc (x)
(incf (symbol-value x)))
(test-inc '*x*) ; *x* is now 101
;;; Generic functions (inlined methods) => avoid because slower
(defgeneric test-f (x y)
(:method ((x number) (y number))
(+ x y))
(:method ((x string) (y string))
(concatenate 'string x y)))
;;; Generic functions (use for extension): => avoid because slower
;;; different methods for same function name (methods are not bound to classes in LISP)
(defgeneric test-g (x y))
(defmethod test-g ((x number) (y number))
(+ x y))
(defmethod test-g ((x string) (y string))
(concatenate 'string x y))
;;; Functions as arguments
(defun test (x y)
(+ x y))
(defun test-caller (f x y)
(funcall f x y))
(test-caller #'test 1 2)
;;; Split elements of list as function arguments
(defun test (x y)
(+ x y))
(apply #'test '(1 2))
;;; ---------- CONTROL STRUCTURES ----------------------------------------------
;;; if (is a special form and only evaluates cases that apply to condition)
(if (> 5 4)
(format t "true")
(format t "false")) ; else
(if (and (>= 100 18) (<= 20 67))
(format t "Yes!~%")
(format t "No!~%"))
;;; relational operators: = < <= >= >
;;; logical operators: and, or, not
;;; progn to execute multiple statements in if
(defun add10 (val)
"Description of function"
(if val
(progn
(format t "start!~%")
(incf val 10)
val) ; => return last result
(format t "I need a value!~%"))) ; else
(add10 10) ; => 20
;;; when is used when there is no else so you can execute multiple statements
(when (> 5 4)
(format t "first~%")
(format t "second~%"))
;;; cond (is like switch-case, check value comparable with eql function)
(defun test-cond (x)
(cond
((= x 0) "0")
((= x 1) "1")
((= x 2) "2")
(t nil)))
(test-cond 3) ; => nil
;;; case (check value comparable with eq function => identical in memory)
;;; DON'T USE NUMBERS, ONLY SYMBOLS!!!
(defun test-case (x)
(case x
(:p (format nil ":p"))
(:d (format nil ":d"))
(:n (format nil ":n"))
(otherwise nil))) ; optional
(test-case :a) ; => nil (otherwise case)
;;; typecase (check datatype)
(defun test-typecase (x)
(typecase x
(integer (format nil "integer"))
(string (format nil "string"))
(otherwise (format nil "otherwise"))))
(test-typecase 1.0) ; => otherwise
;;; flet: define local function(s)
(flet ((square (val)
(* val val))
(cube (val)
(* val val val)))
(list (square 10) (cube 10)))
;;; labels: same as flet, but now you can use functions in other functions
;;; order of functions doesn't matter!
(labels ((square (val)
(* val val))
(cube (val)
(* (square val) val)))
(list (square 10)(cube 10)))
;;; ---------- LOOPING ---------------------------------------------------------
(dotimes (i 12)
(print i))
;;; loop for
(loop for i from 0 below 10 do ; [0, 10[
(print i))
(loop for i from 0 to 10 by 2 do ; [0, 10] stepsize = 2
(print i))
(loop for i from 10 above 0 do ; [10, 0[
(print i)
(terpri))
(loop :for c1 :across "Hello" :for c2 :across "abcde" :do
(print c1)
(print c2))
(loop for x from 0 to 50 by 10 collect x) ; => (0 10 20 30 40 50)
(loop for x from 50 downto 0 by 10 collect x) ; => (50 40 30 20 10 0)
; stepsize: #'cdr = 1 (default), #'cddr = 2, #'cdddr = 3,...
(loop for x in '(0 10 20 30 40 50) by #'cdr do
(format t "~d~%" x))
;;; loop while
(let ((i 0))
(loop while (<= i 10) do
(print i)
(incf i)))
;;; loop until
(let ((i 0))
(loop until (> i 10) do
(print i)
(incf i)))
;;; ---------- STRINGS ---------------------------------------------------------
;;; Strings are mutable arrays (type-of *my-string*)
;;; (defvar *arr* (make-array 5 :element-type 'character :initial-element #\_))
(defvar *my-string* "Hello")
(aref *my-string* 0) ; => #\H
(setf (aref *my-string* 0) #\B)
(print *my-string*) ; => "Bello"
;;; compare strings
(equal "hello" "Hello") ; case-sensitive
(string= "hello" "Hello")
(equalp "hello" "Hello") ; case-insensitive
(string-equal "hello" "Hello")
;;; String formatting
(format nil "Hello, ~a!" "Tom")
(format nil "~s" :foo) ; Data as string
(format nil "~:d" 10000000) ; Number with commas
(format nil "~b" 42) ; Number as binary string
(format nil "~o" 42) ; Number as octal string
(format nil "~x" 42) ; Number as hexadecimal string
(format nil "~,2F" 20.1) ; 2 digits after decimal
(format nil "~{~a~^, ~}" '(1 2 3)) ; List and no , after last element: 1, 2, 3
; ("~{ ~}" is a loop)
(format t "PI to 5 characters ~5f" pi)
(format t "PI to 5 decimals ~,5f" pi)
(defparameter *my-str* "abcdefghijklm")
;;; characters from string
(char *my-str* 4)
(setf (char *my-str* 4) #\E)
;;; character as number and vice versa
(char-code #\a) ; => 97
(code-char 97) ; => #\a
;;; string to integer or other numbers
(parse-integer "42") ; => 42
(read-from-string "1.2e2") ; => 120.0
;;; number to string
(write-to-string 42) ; => "42"
(write-to-string 1.2e2) ; => "1.2e2"
;;; substrings
(subseq *my-str* 4) ; substring from 4th char to end
(subseq *my-str* 4 5) ; substring from [4, 5[
(setf (subseq *my-str* 0 4) "iiii") ; change substring
;;; string trimming
(string-trim " " " trim me ") ; => "trim me"
(string-left-trim " " " trim me ") ; => "trim me "
(string-right-trim " " " trim me") ; => " trim me"
;;; remove char from string
(remove #\o "Homo Sapiens") ; => "Hm Sapiens"
(remove #\o "Homo Sapiens" :start 3) ; => "Hom Sapiens"
(remove-if #'upper-case-p "He-Man") ; => "e-an"
;;; substitute char
(substitute #\l #\m "Tommy") ; => "Tolly"
(substitute-if #\_ #'upper-case-p "He-Man") ; => "_e-_an"
;;; concatenate strings
(concatenate 'string "a" "b" "c") ; => "abc"
;;; loop through string
(loop for c across "Hello" do
(print c))
(map 'string #'(lambda (c) (print c)) "Hello")
;;; reverse string
(reverse "Hello") ; => "olleH"
;;; change cases (also can use :start and :end)
(string-upcase "cool") ; => "COOL"
(string-downcase "COOL") ; => "cool"
(string-capitalize "cool") ; => "Cool"
;;; string to char
(coerce "a" 'character)
;;; char array to string
(coerce #(#\h #\e #\y) 'string)
;;; ---------- MATH FUNCTIONS --------------------------------------------------
(defparameter *x* 1)
(incf *x*) ; x++
(decf *x*) ; x--
(null nil) ; = T Is something equal to nil
(+ 5 4)
(- 5 4)
(* 5 4)
(/ 5 4) ; 5/4
(/ 5 4.0) ; 1.25 or (float (/ 5 4))
(mod 5 4) ; 1 (or rem)
(expt 4 2000) ; 4^2000 (get the exact large number)
(sqrt 81) ; 9
(isqrt 10) ; 3
(sqrt -4) ; #C(0.0 2.0) (complex number)
(exp 1) ; e^1
(log 1000 10) ; = 3 = Because 10^3 = 1000
(floor 5.5) ; = 5
(ceiling 5.5) ; = 6
(max 5 1000 200 50) ; = 10
(min 5 1000 200 50) ; = 5
;;; sin, cos, tan, asin, acos, atan
(sin (/ pi 2.0))
(evenp 15) ; Check if even
(oddp 15) ; Check if odd
(numberp 2) ; Check if number
(integerp 2) ; Check if integer
(stringp "a") ; Check if string
(arrayp #(1 2 3)) ; Check if array
(vectorp #(1 2 3)) ; Check if vector (1D arrays are vectors!)
;;; ----------- EQUALITY -------------------------------------------------------
(eq "Hello" "Hello") ; Compare address of objects
(= 1 1) ; Compare numbers
(char= #\a #\A) ; Compare characters
;;; Otherwise use equal
(equal 'car 'truck)
(equal 10 10)
(equal 5.5 5.3)
(equal "string" "String") ; case-sensitive
(equal (list 1 2 3) (list 1 2 3))
;;; eql: must also have same type (===)
(eql 1 1.0) ; => nil
;;; equalp: compare strings case insensitively and integers to floats
(equalp 1.0 1)
(equalp "tom" "Tom")
;;; ----------- ARRAYS ---------------------------------------------------------
;;; Make mutable 1D array of size 3 and all 0
;;; A constant 1D array is: #(1.0 2.0 3.0)
(defparameter num-array (make-array 3 :initial-element 0 :element-type '(integer)))
;;; Get an element
(aref num-array 0)
;; Set an element
(setf (aref num-array 0) 42)
;;; Length
(length num-array) ; => 3
;;; Rank (number of dimensions)
(array-rank num-array) ; => 1
;;; Allocate array on stack (1D only!)
(let ((arr #(1 2 3)))
(declare (dynamic-extent arr))
(format t "~d~%" (aref arr 1)))
;;; Loop through 1D array
(loop for e across #(0 10 20 30 40) do
(print e))
(map 'simple-vector #'(lambda (e) (print e)) #(0 10 20 30 40))
;;; Mutable 2D array (nD arrays possible too)
;;; A constant 2D array is: #2A((0 1 2) (3 4 5) (6 7 8))
(defparameter num-array-2d
(make-array '(3 3) :initial-contents '((0 1 2) (3 4 5) (6 7 8))))
;;; Length of 2D array
(array-total-size num-array-2d) ; => 9
;;; Number of rows
(array-dimension num-array-2d 0) ; => 3
;;; Number of columns
(array-dimension num-array-2d 1) ; => 3
;;; Array dimensions
(array-dimensions num-array-2d) ; => (3 3)
;;; get element in 2D array as if it was a 1D array
(row-major-aref num-array-2d 5) ; => 5 (value)
(array-row-major-index num-array-2d 1 2) ; => 5 (index)
;;; Loop through 2D array
(destructuring-bind (row-count column-count) (array-dimensions num-array-2d)
(loop for i from 0 below row-count do
(loop for j from 0 below column-count do
(print (aref num-array-2d i j)))))
;;; vector/array to list
(coerce #(0 1 2) 'list)
;;; copy a sequence
(copy-seq #(0 1 2))
;;; ---------- ADJUSTABLE ARRAYS -----------------------------------------------
(defparameter num-array (make-array 0 :adjustable t)) ; empty adjustable array
(adjust-array num-array 5)
(setf (aref num-array 1) 10)
(setf (aref num-array 3) 30)
(adjust-array num-array 4)
;;; immitate vector
;;; vector-push, vector-push-extend return an index
(defparameter num-array (make-array 3 :adjustable t :fill-pointer 0)) ; fill starting at index 0
(vector-push 0 num-array) ; #(0)
(vector-push 10 num-array) ; #(0 10)
(vector-push 20 num-array) ; #(0 10 20)
(vector-push 30 num-array) ; #(0 10 20) => returns nil => needs to adjust!!!
(vector-push-extend 30 num-array) ; #(0 10 20 30) (only extends when full => size * 2)
(vector-pop num-array) ; pop element of fill-pointer and decrease it by 1
; => popped element
;;; cleanup vector
(setf (fill-pointer num-array) 0) ; move back fill-pointer to index 0
(adjust-array num-array 0) ; optional if you want no preallocation
;;; size of vector
(length num-array) ; number of elements in use
(array-total-size num-array) ; total number of elements allocated
;;; ---------- HASH TABLES -----------------------------------------------------
(defparameter *ht* (make-hash-table :size 100)) ; Use size to avoid rehashing!
(setf (gethash :foo *ht*) "Tom") ; Set
(gethash :foo *ht*) ; Get
(remhash :foo *ht*) ; Remove
(clrhash *ht*) ; Clear full hash table
;;; Normally use symbols (like :foo) or numbers instead of strings as keys!
;;; If you want strings, define hash table as:
(defparameter *ht* (make-hash-table :size 100 :test #'equal)) ; default test is eql !!!
;;; maphash: do function on each item and returns nil
(maphash
(lambda (k v)
(format t "~a = ~a~%" k v)
*ht*))
;;; ---------- CONS CELLS / LISTS / TREES --------------------------------------
(list 1 2 3 4) ; Create list
'(1 2 3 4)
(listp '(1 2 3 4)) ; Check if list or not (t or nil)
(cons 0 '(1 2 3 4)) ; Add item to front of list
(car '(1 2 3 4)) ; Get 1st element of list
(first '(1 2 3 4))
(nth 0 '(1 2 3 4))
(cdr '(1 2 3 4)) ; Get rest of list
(rest '(1 2 3 4))
(cadr '(1 2 3 4)) ; Get 2nd element of list (1st of rest)
(second '(1 2 3 4))
(nth 1 '(1 2 3 4))
(caddr '(1 2 3 4)) ; Get 3rd element of list (1st of rest of rest)
(third '(1 2 3 4))
(nth 2 '(1 2 3 4))
(cadddr '(1 2 3 4)) ; Get 4th element of list
(fourth '(1 2 3 4))
(nth 3 '(1 2 3 4))
;;; Is 3 a member of the list
(format t "Is 3 in the List = ~a ~%" (if (member 3 '(2 4 6)) 't nil))
;;; Combine lists into 1 list (every list except last list is copied!)
(append '(just) '(some) '(random words))
;;; Push an item on the front of a list
(defparameter *nums* '(2 4 6))
(push 1 *nums*)
;;; Get the nth value from a list
(format t "2nd Item in the List = ~a ~%" (nth 2 *nums*))
;;; Create a plist which uses a symbol to describe the data
(defvar superman (list :name "Superman" :secret-id "Clark Kent"))
;;; This list will hold heroes
(defvar *hero-list* nil)
;;; Adds items to our list
(push superman *hero-list*)
;;; Cycle through all heros in the list and print them out
(dolist (hero *hero-list*)
;; Surround with ~{ and ~} to automatically grab data from list
(format t "~{~a : ~a ~}~%" hero))
;;; Trees
(defvar *tree* '((1 2) (3 4 5) (6 7)))
(cadadr *tree*) ; too cryptic...
(nth 1 (nth 1 *tree*)) ; better!
(nth 2 (nth 1 *tree*))
;;; Set operations
(intersection '(0 1 2 3) '(0 2 4))
(set-difference '(0 1 2 3) '(0 2 4))
(union '(0 1 2 3) '(0 2 4))
(set-exclusive-or '(0 1 2 3) '(0 2 4)) ; remove common elements and merge
;;; ----------------------------------------------------------------------------
;;; CLOS: Common Lisp Object System
;;; ----------------------------------------------------------------------------
(defclass person ()
((name :type string
:initform "Tom"
:initarg :name
:accessor name) ; :reader (only getter) :writer (only setter)
(age :type integer
:initform 28
:initarg :age
:accessor age)))
(defclass child (person) ; put superclasses in (), multiple inheritance allowed!
())
(defun make-person (&key (name "Tommm") (age 20)) ; constructor
(make-instance 'person :name name :age age))
(defmethod print-age ((self person))
(format t "Age: ~d!~%" (age self)))
(defparameter *me* (make-person :name "Tom" :age 28))
(name *me*) ; get
(setf (age *me*) 27) ; set
(class-of *me*)
(type-of *me*)
(defgeneric greet (obj)
(:documentation "say hello"))
(defmethod greet ((obj person))
(format t "Hello I'm a ~a!~&" (type-of obj)))
(defmethod greet ((obj child))
(format t "Yo, I'm a ~a!~&" (type-of obj)))
;;; Whenever you want to change method definition in REPL do this first:
(fmakunbound 'greet)
;;; ----------------------------------------------------------------------------
;;; Functional programming
;;; ----------------------------------------------------------------------------
;;; sort (mutable)
(sort #(1 8 4 5 6 7 2) #'<) ; from small to big
(sort #(1 8 4 5 6 7 2) #'>) ; from big to small
(stable-sort #(1 8 4 5 6 7 2) #'>) ; if same elements need to appear in same order
;;; These work on lists, arrays, vectors,...
(some #'evenp '(1 2 3 4 5)) ; t if it contains even number
(notany #'evenp '(1 2 3 4 5)) ; t if no number is even
(every #'evenp '(1 2 3 4 5)) ; t if every number is even
(notevery #'evenp '(1 2 3 4 5)) ; t if not every number is even
;;; Complement of function
(defvar *my-even* (complement #'oddp))
(funcall *my-even* 0) ; => t
;;; map function on every element of sequence
(map 'vector #'(lambda (x) (* x 2)) #(1 2 3 4 5))
(mapcar #'(lambda (x) (* x 2)) '(1 2 3 4 5))
;;; reduce
(reduce #'+ #(1 2 3 4)) ; => 10
(reduce #'(lambda (x y) (+ x y)) #(1 2 3 4)) ; => 10
;;; filter
(remove-if (complement #'evenp) #(1 2 3 4 5))
(remove-if #'(lambda (x) (<= x 2)) #(1 2 3 4 5))
;;; zip
(map 'vector #'vector #(1 2 3) #(4 5 6) #(7 8 9)) ; => #(#(1 4 7) #(2 5 8) #(3 6 9))
(mapcar #'list '(1 2 3) '(4 5 6) '(7 8 9)) ; => ((1 4 7) (2 5 8) (3 6 9))
;;; search (default :test is eql, can also use :test-not)
;;; also possible on strings
(defparameter *num-array* #(10 20 30 40 50))
(find 40 *num-array* :test #'eql :start 0 :end 3) ; => value or nil when not found
(find 40 *num-array* :from-end t) ; backwards search
(find-if #'evenp *num-array*) ; => first element it finds or nil
(find-if-not 40 *num-array*)
(position 40 *num-array*) ; => index
(position-if #'oddp *num-array*) ; => index of first element it finds or nil
(position-if-not #'oddp *num-array*)
; Scope: let
(let (()) )
; Function: lambda over let
(funcall (lambda (x) (let ((y 5)) (+ x y))) 95)
; Closure (class with 1 method): let over lambda over let
; put defun over it to have a named class instead of anonymous class
(defparameter *counter* (let ((x 0)) (lambda () (incf x))))
; let over lambda over let with values => class with multiple methods
(defparameter *counter*
(let ((x 0))
(defun increase-counter ()
(incf x))
(defun decrease-counter ()
(decf x))))
(increase-counter)
(decrease-counter)
; lambda over let over lambda over let => namespace with classes
; lambda over lambda over let => namespace with functions
;;; ----------------------------------------------------------------------------
;;; MACROS
;;; ----------------------------------------------------------------------------
;;; TODO: Read macros chapter of Common Lisp Cookbook
;;; https://lispcookbook.github.io/cl-cookbook/macros.html#quote-comma-
;;; Expand existing macros (debugging macros)
(macroexpand '(loop for i from 0 below 10 do (print i)))
(defvar my-ast (read-from-string "(defun foo (x y) (+ x y))"))
(nth 0 my-ast)
(nth 1 my-ast)
(nth 0 (nth 2 my-ast))
;;; Turn code into data with ' or (quote (...))
;;; Code is list of elements in cons cells [+] [5] [4] [nil]
;;; nil is end of list
(first '(+ 5 4)) ; => +
;;; Quasiquote ` and , doesn't evaluate list except for , and ,@ removes its ()
`(1 2 ,(loop for i from 3 to 5 collect i)) ; => (1 2 (3 4 5))
`(1 2 ,@(loop for i from 3 to 5 collect i)) ; => (1 2 3 4 5)
;;; Macros
(defclass my-class ()
((resource :type string
:initform "bye"
:initarg :resource
:accessor resource)))
(defmethod my-class-dispose ((self my-class))
(format t "Disposing: ~a!~%" (resource self)))
(defmacro with-my-class (var form &body body)
(let ((tempvar (gensym))) ; make sure this name is never used
`(let* ((,tempvar ,form)
(,var ,tempvar))
(unwind-protect
(progn ,@body)
(my-class-dispose ,var)))))
(with-my-class x (make-instance 'my-class)
(format t "hey!~%")
(format t "cool!~%"))
; Domain Specific Language
(defmacro unit-of-time (value unit)
`(* ,value
,(case unit
((s) 1)
((m) 60)
((h) 3600)
((d) 86400)
((ms) 1/1000)
((us) 1/1000000))))
(unit-of-time 1 d)
;;; ----------------------------------------------------------------------------
;;; FILE I/O
;;; ----------------------------------------------------------------------------
;;; TODO
;;; ----------------------------------------------------------------------------
;;; ERROR HANDLING
;;; ----------------------------------------------------------------------------
;;; unwind-protect: make sure cleanup code is run after error
(let ((x 3))
(unwind-protect
(progn
(print "start")
(print (error "error!"))
(format t "success, ~d~%!" x)) ; doesn't execute due to error!
(print "cleanup")
(print "done with cleanup!")))
;;; ----------------------------------------------------------------------------
;;; Threading: bordeaux-threads
;;; ----------------------------------------------------------------------------
;; Before starting to use threads
(bt:start-multiprocessing)
(bt:all-threads)
(bt:thread-name (bt:current-thread))
(bt:make-thread (lambda () (sleep 100) (format t "Thread!~%")) :name "thread1")
(bt:thead-alive-p *)
;;; Find a thread
(find-if
(lambda (th)
(search "thread1" (bt:thread-name th)))
(bt:all-threads))
(bt:destroy-thread *) ; destroy before completion
(bt:thead-alive-p **)
;;; Join thread: wait in current thread for the given thread to finish
(bt:make-thread (lambda () (sleep 100) (format t "Thread!~%")) :name "thread1")
(bt:join-thread *)
;;; Mutexes
(defparameter *lock* (bt:make-lock "my-lock"))
; try to acquire the lock in each thread and release it. Or use with-macro.
(bt:acquire-lock *lock*)
(bt:release-lock *lock*)
(bt:with-lock-held (*lock*)
(format t "Locking!~%"))
;;; Semaphores: thread safe counter, thread1 can wait on semaphore
;;; while thread2 can signal so thread1 can continue.
;;; Multiple waits can have multiple signals.
;;; A semaphore is a mutex + wait queue
(defparameter *sem* (bt:make-semaphore :name "sem")
(bt:wait-on-semaphore *sem*)
(bt:signal-semaphore *sem* :count 1) ; signal x times
;;; Condition variables:
; make a condition variable together with a lock
; thread1 acquires lock and waits on condition
; thread2 acquires lock but after if-test uses a notify
; thread1 doesn't wait on condition anymore
;;; ----------------------------------------------------------------------------
;;; CFFI: C Foreign Function Interface (talk to C)
;;; ----------------------------------------------------------------------------
(ql:quickload :cffi)
(require :cffi)
;;; Types: :int, :unsigned-int (uint), :long, :long-long, :short,
;;; :int8, uint8, int16, ... , int64, uint64
;;; :float, :double, :char, :bool
;;; :pointer, '(:pointer :int), '(:pointer :char),...
(cffi:foreign-type-size :int) ; int is 4 bytes
(cffi:foreign-type-size '(:struct my-struct))
;;; Pointers
(defvar tmp0 (cffi:foreign-alloc :int)) ; malloc a pointer tmp0 to int
(cffi:mem-aref tmp0 :int) ; get value of pointer
(setf (cffi:mem-aref tmp0 :int) 10) ; set value of pointer
(cffi:foreign-free tmp0) ; free
;;; Null pointer
(cffi:null-pointer)
;;; Arrays
(defvar tmp1 nil)
(setf tmp1 (cffi:foreign-alloc :int :count 10))
(cffi:mem-aref tmp1 :int 0)
(setf (cffi:mem-aref tmp1 :int 0) 10)
(loop for i from 0 below 10 do
(setf (cffi:mem-aref tmp1 :int i) 0))
(cffi:foreign-free tmp1)
;;; Strings
(defvar tmp2 nil)
(setf tmp2 (cffi:foreign-string-alloc "hello!"))
(code-char (cffi:mem-aref tmp2 :char 0)) ; => #\h
(code-char (cffi:mem-aref tmp2 :char 6)) ; => #\Nul
(cffi:foreign-free tmp2)
;; better to use cffi:with-foreign-string macro!
;;; Own types
(cffi:defctype my-int :int)
;;; Structs (can't set default values)
(cffi:defcstruct my-struct
(id :int)
(x :float)
(y :float)
(a :float :offset 4) ; alias for x
(b :float)) ; alias for y
(defvar tmp3 (cffi:foreign-alloc '(:struct my-struct)))
(cffi:foreign-slot-value tmp3 '(:struct my-struct) 'x)
(setf (cffi:foreign-slot-value tmp3 '(:struct my-struct) 'x) 10.0)
(cffi:with-foreign-slots ((id x y) tmp3 (:struct my-struct))
(print id)
(print x)
(print y)
nil)
;;; Unions
(defcunion my-union
(x :int32)
(y :int8 :count 4)))
;;; Enums
(cffi:defcenum (my-enum :uint8)
(:monday 0)
(:tuesday 1)
(:wednesday 2)
(:thursday 3)
(:friday 4)
(:saturday 5)
(:sunday 6)
(:days-in-week 7))
;;; Functions
(cffi:defcfun ("c_name_function" my-function) (:int)
(x (:float))
(y (:float)))
;;; Example 1: From C library to Common Lisp
;;; C backend
(cffi:define-foreign-library soil
(:darwin (:or "libSOIL.dylib"
#+X86-64 "local_x64_libSOIL.dylib"))
(:unix (:or "libSOIL.so"
"libSOIL.so.1"
#+X86-64 "local_x64_libSOIL.so"))
(:windows (:or "libSOIL.dll"
#+X86 "local_x86_libSOIL.dll"
#+X86-64 "local_x64_libSOIL.dll"))
(t (:default "libSOIL.so")))
(cffi:use-foreign-library soil)
;;; Then define cffi structs, functions, enums,... as above
(cffi:defcenum image-format-enum
(:auto 0)
(:l 1)
(:la 2)
(:rgb 3)
(:rgba 4))
(cffi:defcfun ("SOIL_load_image" soil-load-image) (:pointer :unsigned-char)
(filename :pointer)
(width (:pointer :int))
(height (:pointer :int))
(channels (:pointer :int))
(force-channels image-format-enum))
;;; Common Lisp frontend
(defun load-image (filepath &optional (force-channels :rgba))
(cffi:with-foreign-filepath (c-filepath filepath)
(cffi:with-foreign-objects ((c-width :int) (c-height :int) (c-channels :int))
(let ((result-pointer (soil-load-image c-filepath c-width c-height
c-channels force-channels)))
(if (cffi:null-pointer-p result-pointer)
(error "Could not load image ~a~%Recieved NULL pointer" filepath)
;; else
(values result-pointer
(cffi:mem-aref c-width :int)
(cffi:mem-aref c-height :int)
(cffi:mem-aref c-channels :int)))))))
;;; Example 2: From Common Lisp to C
(declaim (ftype (function (single-float single-float)
(simple-array single-float (2)))
vec2)
(inline vec2))
(defun vec2 (x y)
(declare (optimize (speed 3) (safety 0)))
(let ((v (make-array 2 :element-type 'single-float :initial-element 0.0)))
(setf (aref v 0) x)
(setf (aref v 1) y)
v))
(let ((v (vec2 1.0 2.0)))
(declare (dynamic-extent v))
(print v))
(cffi:defcstruct c-vec2-struct
(x :float)
(y :float))
(cffi:define-foreign-type c-vec2-type ()
()
(:actual-type :struct c-vec2-struct)
(:simple-parser vec2))
(defmethod cffi:translate-from-foreign (ptr (type c-vec2-type))
(vec2 (cffi:mem-aref ptr :float 0)
(cffi:mem-aref ptr :float 1)))
(defmethod cffi:translate-into-foreign-memory (value (type c-vec2-type) ptr)
(setf (cffi:mem-aref ptr :float 0) (aref value 0))
(setf (cffi:mem-aref ptr :float 1) (aref value 1)))
(defmethod cffi:expand-from-foreign (ptr (type c-vec2-type))
`(vec2 (cffi:mem-aref ,ptr :float 0)
(cffi:mem-aref ,ptr :float 1)))
(defmethod cffi:expand-into-foreign-memory (value (type c-vec2-type) ptr)
`(progn
(setf (cffi:mem-aref ,ptr :float 0) (aref ,value 0))
(setf (cffi:mem-aref ,ptr :float 1) (aref ,value 1))))
(defvar tmp4 (cffi:foreign-alloc 'vec2))
(cffi:mem-aref tmp4 'vec2)
(setf (cffi:mem-aref tmp4 'vec2) (vec2 2.0 3.0))
(cffi:foreign-free tmp4)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment