Common Lisp: Third party libraries

This post is less about just third party libraries, and more about getting a Common Lisp project running in the way I expect (or as close to it as I can get).

I've only dabbled in Lisp over the years (until Clojure in 2009), so I'm trying to make a serious run at Common Lisp. To warm back up I'm running through Land of Lisp.

I ran into the following code:

(defun dot->png (fname thunk) (with-open-file (*standard-output*
		   fname
		   :direction :output
		   :if-exists :supersede)
(funcall thunk))
(ext:shell (concatenate 'string "dot -Tpng -O " fname)))

This is the first function that is intended for Clisp and won't run as written in SBCL (which is the CL implementation I'm using currently. Specifically the ext:shell command isn't available. Fortunately a package called trivial-shell does what I need.

Downloading packages manually isn't something I wanted to do, but CL has Quicklisp. After installing Quicklisp it was simple enough to install and load trivial-shell, from my repl:

(ql:quickload "trivial-shell")

Replacing `ext-shell` with the trivial-shell equivalent and evaluating it resulted in running code.

I figured I could just put the quickload command into my buffer and it would just work when I evaluated it. Not the case. For reasons I still don't understand, if I manually evaluate that quickload in a repl it works, but trying to evaluate it from a buffer does not… particularly from C-c C-k which is the first thing I normally do when I sit down to work on some Lisp.

Some digging around led me to the idea that I need a project definition, CL has one that is the de facto standard in ASDF and it's baked right into SBCL.

You're also going to want the slime-asdf contrib, so add that to your emacs init, mine looks like this:

(use-package slime
  :ensure t
  :config
  (setq inferior-lisp-program "/usr/local/bin/sbcl")
  (setq slime-contribs '(slime-fancy slime-asdf)))

That's going to let you evaluate ASDF files a little later on.

Modern build tools all usually have new project templating built in, Quicklisp is no exception. For example to create my Grand Theft Wumpus project (from Land of Lisp), this is all it took from the repl:

(ql:quickload "cl-project")
(cl-project:make-project #P"~/Development/wumpus")

By default the ASDF QuickLisp combo expects your systems to be defined in ~/quicklisp, but I like to keep my projects under ~/Development (I don't want to have to jump around to different places for different languages).

In order to accomplish that I needed to add a line to ~/.sbclrc. Again here's what mine looks like (the Quicklisp install adds the first bit):

;;; The following lines added by ql:add-to-init-file:
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
				       (user-homedir-pathname))))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

;; search /Users/jgerman/Development for lisp systems
(push #P"/Users/jgerman/Development/" ql:*local-project-directories*)

If you go that route and want that config picked up, make sure you start a new inferior-lisp session. Or when in doubt, restart emacs… I did a fair share of that to make sure I had a clean environment when going through this process.

Now you should be able to use your project, and any third party libraries you need. For the Wumpus game from Land of Lisp, my project file currently looks like this:

(defsystem "wumpus"
  :version "0.1.0"
  :author "Jeremy German"
  :license ""
  :depends-on ("trivial-shell")
  :components ((:module "src"
		:components
		((:file "main"))))
  :description "Wumpus game from Land of Lisp"
  :in-order-to ((test-op (test-op "wumpus/tests"))))

(defsystem "wumpus/tests"
  :author ""
  :license ""
  :depends-on ("wumpus"
	       "rove")
  :components ((:module "tests"
		:components
		((:file "main"))))
  :description "Test system for wumpus"
  :perform (test-op (op c) (symbol-call :rove :run c)))

In order to get my environment for working on this project set up I just follow a few steps:

(asdf:load-system "wumpus"

And I'm up and running.

There are a few quirks I haven't worked out yet. The main issue is that for some reason I have to use two colons to refer to my functions, but to refer to trivial shell I only have to use one:

(wumpus:foo) ;; doesn't work
(wumpus::foo) ;; does work

(trivial-shell:execute-shell-command "ls") ;; does work

There are bound to be more bugs but this is enough to get up and developing in a reasonable workflow.