assertions plus macro equals easier debuggability
preface⌗
Some parts of this article won’t make sense if you don’t know basic Common Lisp.
what is an assertion?⌗
An assertion is most languages is basically a function that checks if the value is true. If the value isn’t true, it throws an error.
what is a macro?⌗
A macro is basically a function that writes code instead of evaluating code. Macros can call other macros that when they finish they evaluate code.
what’s cool about asserting and macros?⌗
Usually, if you give an assertion a value it takes that value literally. Say for example, you want to assert if 5 = 5 in Common Lisp:
(assert (= 5 5))
;; assert(5 == 5)
What assert sees is
(= 5 5)
;; 5 == 5
It then evaluates it and sees its value
T
;; true
Now, if you had a value in a list like so:
(defparameter *my-assert-argument* (list '= '5 '4))
(print *my-assert-argument*)
;; prints (= 5 5) as a list
If you then asserted that list of code, it would always return true even if the value is not true.
(assert *my-assert-argument*)
;; which is equivalent to
(assert (list '= '5 '4))
And since that value is not nil, it would always return true. The key then becomes in writing a macro that takes replaces the reference of the value in the assertion like so:
(defmacro assert-my-variable (variable)
`(assert ,(eval variable)))
If assert-my-variable
is called with a variable, it would assert it the actual value but not the variable:
(assert-my-variable (list '= '5 '4))
;; throws error since 5 does not equal 4
Cool, eh?
global set of assertions…⌗
I wrote a macro called assert-f
that would assert a value and if it succeeded, it puts in the global list of assertions.
(defvar *assertions* nil)
(defmacro assert-f (expr)
"assert-f adds an expression to *assertions* and asserts it"
(setf *assertions* (append *assertions* (list expr)))
`(handler-case (assert ,expr)
(error (c)
(setf *assertions* (remove-last *assertions*))
(invoke-debugger c))))
I would write:
(assert-f (= (my-sum 5 4) 9))
And if I printed *assertions*
, I’d find my assertion there:
(print *assertions*)
;; ((= (my-sum 5 4) 9))
And then, I wrote a function called assert-everything
to assert everything in the global list of assertions:
(defmacro assert-everything ()
"assert-everything asserts everything in *assertions*"
`(let ()
,@(mapcar (lambda (x) `(assert ,x)) *assertions*)))
So, if I called assert-everything
it’d assert (= (my-sum 5 4) 9)
and every other assertion I called?
why is this useful?⌗
The environment of Common Lisp, the REPL, is not like Go’s environment. The whole process of writing programs is quite different.
With Go, you write a bunch of code that you then test by running go run
. With Common Lisp, you slowly stitch up pieces of code by writing and evaluating.
In a sense, Common Lisp is more correct in its form than Go. So, instead of writing unit tests I wanted to write assertions.
That way, if anything fails I know where it failed and how.
Also, it sure beats having a unit test file and a file containing the actual code.
Last point I want to make is this type of code structure allows me to unit test macros which is tremendously helpful since macros are where the silent errors occur.
Just wanted to share this cool common lisp trick.