smashedtoatoms

Sharpsign Plus Reader Macros

WTF does #+(or) mean?

They’re called reader macros. Reader macros in Common Lisp are a read-time feature that lets you do a pre-compile condition check. The #+ reader macro checks for a feature, and if it succeeds, then the S expression following it is evaluated. If not, the code never even gets passed to the compiler. It is as if it never existed. They’re kinda like the ifdef and ifndef macros in C/C++. They’re there to let you define an expression if a feature exists.

Why would I ever do that?

Let’s say you want to do something only if the quicklisp feature is present. Rather than writing a function that looks at *features* to see if :QUICKLISP is in the feature list, and having that check compiled into your code and running every time you run your code, you could do something like this:

1
2
#+quicklisp
(do-something-with-quicklisp)

If quicklisp is in *features* then do-something-with-quicklisp gets handed to the compiler. If not, none of that code ever even makes it to the compiler. It’s filtered out when the file is read, hence the name.

Okay, the question remains, what is the #+(or)?

I use it to get the same functionality that one might get from the ignore syntax: #_ in Clojure. It checks the condition (or), which, with no arguments, will always return NIL. It’s a condition that can never pass, so the expression immediately following it will never make it to the compiled output in real life. It will never exist in your shipped binary, but you can evaluate the expression in your repl all day. It’s nice if you’re doing REPL dev in your editor and add some function to mess with something that should never exist in the final product. I leave them in my code if I need to mess with something from time-to-time (external api call behavior checks, etc.). Here’s an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#+(or)
(defun try-broke-ass-thing ()
  "Runs broke-ass-thing.  This will never exist anywhere but my dev repl."
  (let ((test-arg1 "some random argument")
        (test-arg2 "another random argument"))
    (inspect (broke-ass-thing test-arg1 test-arg2))))

(defun broke-ass-thing (arg1, arg2)
  "This function is broken.  We want to fix it."
  ...
  some-value-that-is-unexpected)

I can load the whole file into my repl and manually evaluate try-broke-ass-thing as I tweak the code. When I finish tweaking, I can leave the definitions. If I ever need to use those again, they’re there, but they never ship with the final binary.

Is it a hack? Probably. Would tests be better? Yes. Will I keep doing this? Absolutely, when I need something quick and dirty.