I learned something about symbols and packages
I am using Common Lisp for developing a web application. Several days ago a new part of this application didn’t worked as supposed and I spent a considerable large amount of time in finding the bug. It was a very simple problem with symbols where I mixed something up.
In the application the web server somewhen gets some JSON data from the browser. It is then converted to Lisp object using the CL-JSON
package. This package converts JSON objects to a-lists and converts the member keys to symbols (see CL-JSON’s documentation. I then wanted to look something up in that a-list and failed.
I wrote a small test case to show the effect and explain what went wrong.
(ql:quickload '("hunchentoot" "cl-who"))
;; direct loading via ql only for demonstration purposes, normally I
;; would use a asdf:defsystem for that.
(in-package :cl-user)
(defpackage :my-app (:use :cl))
(in-package :my-app)
(defparameter *my-a-list*
'((foo . 100)
(bar . 200))) ;; in the real application this a-list is
;; generated by a JSON-to-lisp conversion by
;; CL-JSON; in CL-JSON the object member keys are
;; converted to symbols.
(defun get-value (key)
"Returns the value with KEY from *MY-A-LIST*."
(cdr (assoc (intern (string-upcase key)) *my-a-list*)))
(hunchentoot:define-easy-handler (web-get-value :uri "/get-value") (id)
(cl-who:with-html-output-to-string (*standard-output* nil :prologue t)
(:p (cl-who:fmt "Value of ~a is: ~a" id (get-value id)))))
(defun start ()
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242)))
So on the REPL everything looks fine: MY-APP> (get-value "foo")
100
MY-APP> (get-value "bar")
200
MY-APP>
But when I used my web browser to give me these results as well I got something strange. For example here are some results when using curl: ~> curl http://localhost:4242/get-value?id=foo
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<p>Value of foo is: NIL</p>
I was puzzled: The value is NIL
?
After some debugging I found out that the easy handler from Hunchentoot runs with *package*
set to COMMON-LISP-USER
(and not to MY-APP
as I implicitly assumed). That means that assoc
looked up COMMON-LISP-USER::FOO
in the a-list where the keys are MY-APP::FOO
and MY-APP::BAR
. And this test fails. Therefore NIL
is returned which is correct.
So I rewrote the get-value
function to: (defun get-value (key)
"Returns the value with KEY from *MY-A-LIST*."
(cdr (assoc (intern (string-upcase key)
(find-package :my-app)) *my-a-list*)))
Now the symbols are interned in the same package and everything went well: ~> curl http://localhost:4242/get-value?id=foo
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<p>Value of foo ist: 100</p>
~> curl http://localhost:4242/get-value?id=bar
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<p>Value of bar ist: 200</p>
Therefore I was reminded to think about packages when interning symbols. A good guide to symbols and packages could be found in this document: The Complete Idiot’s Guide to Common Lisp Packages.