A Handful Of Useful Lisp Programming TricksPosted: 2010/08/13
Here are a few useful Lisp tricks, some are applicable to other languages.
This is lisp 101, but if you have a parameter that many functions need and they access it at will and often, then it’s probably a good candidate for a dynamic variable.
A dynamic variable is like a global variable, but you can shadow its definition by using let:
(defvar *request*) (defun serve (request) (let ((*request* request)) (declare (special *request*)) ... ;; many operations work together handling requests, and they refer to the request often. ...)))
Fixed Point Math
Fixed point math is easy: you just imagine that some range of your integer is dedicated to holding values less than one unit. For example, you just decide that the value 1.0 (one unit) is the unsigned integer 10,000,000 thus any unsigned integer less than 10 million is less than one unit. This choice of unit gives about 7 decimals of accuracy but reduces the range of whole numbers to between 0 units and 429 units. You can perform calculations with scaled numbers, and print them using a scaling-aware print function.
(defun scaleup (n) (* 10000000 n)) (defun scaledown (n) (truncate n 10000000)) (defun scale-print (n) (multiple-value-bind (n r) (truncate n 10000000) (format t "~d.~7,'0d" n r)))
(scale-print (scaleup 1)) ==> 1.0000000
(defun *pi (n) (truncate (* n 355) 113)) (defun *e (n) (truncate (* n 2721) 1001))
(scale-print (*pi (scaleup 1))) ==> 3.1415929
You can use these just like regular numbers with one caveat: Multiplication and division need one scaled number and one unscaled number or you must downscale after multiplication and upscale after division. The order (downscale then multiply, or multiply then downscale) may affect the accuracy of the result.
Insurance against infinite loops
While developing, put this line into something you think will infinite-loop, this’ll stop it (at the cost of some false positives):
(and (zerop (random 1000)) (error "Infinite loop protect!"))
Normally distributed random integers
Sometimes you write tests that use random numbers, and sometimes the test is improved if those numbers tend to congest in an area. You can make a number generator that has a binomial pattern by adding two randoms togther. Don’t do
(random 10) do
(+ (random 5) (random 6)).
(defun bin-rand (n) (+ (random (ash (+ n 1) -1)) (random (ash (+ n 2) -1)))) ;; Test It! ;; I got: #(354 676 967 1317 1686 1642 1357 1002 680 319) (loop repeat 10000 with vector = (make-array 10) for i = (bin-rand 10) do (incf (elt vector i)) finally (return vector))
Compile Time Evaluation (compile-time-value)
(load-time-value), which evaluates the form at load time, but it doesn’t have
(compile-time-value) to do the same at compile time. That’s a shame because I wanted to load my CSS file and images into Lisp at compile time, so my saved image could serve requests for these frequently accessed files from memory without my having to put them on the server box.
Luckily, (compile-time-value) is very easy to write:
(defmacro compile-time-value (form) (eval form))
Using it, I used a slurp command I found on the internet, and was able to write my css handler.
(eval-when (:compile-toplevel :load-toplevel) (defun slurp (path) (with-open-file (stream (pathname path) :if-does-not-exist nil) (let ((seq (make-array (file-length stream) :element-type 'character :fill-pointer t))) (setf (fill-pointer seq) (read-sequence seq stream)) seq)))) (defun handle-css (request) (http-send request (compile-time-value (slurp "mycss.css"))))