A simple project, whether in C++ or in Lisp, is easy enough to implement. In C++, you’d write one .h file and one .cpp file, and when you wanted to test changes, you would compile with something like this
g++ -ggdb example.cpp -o example
However, as the project becomes more complicated, it typically gets broken up into separate files. In C++, you might reserve a pair of .h/.cpp files for one class or one hierarchy of descended classes. Then, as changes continue, it becomes important to figure out what files need to be recompiled when a header file has changed. This is where GNU Make, and its relatives like cmake and qmake, come in.
What about Lisp? Well, in a similar fashion, you tend to break up your project into separate files. It isn’t common to break out your file into declarations and definitions, the way you try to do in C++, but it is possible to do a bit of that, if desired. However, you still have cases where one .lisp file depends on definitions created in another .lisp file. In that case, it’s important to load the dependent file ahead of the depending one, to avoid runtime errors at load time. How is this managed?
In Lisp, the commonly used dependence manager is called ASDF. A particular project is built with an .asd file declaring what files are needed for the project, and what files depend on what other files. Here’s an example of an .asd file from one of my old projects, a sudoku puzzle generator:
(defpackage puzzle-system (:use :common-lisp :asdf #+clisp :screen #-clisp :cl-ncurses )) (in-package :puzzle-system) (defsystem "puzzle" :description "puzzle: An interactive Sudoku game." :version "0.2" :author "Christopher Neufeld <EMAIL-REDACTED>" :licence "GPL" :components ((:file "generate") (:file "printed-puzzles" :depends-on ("generate" "eval-difficulty")) (:file "interactive" :depends-on ("generate" "eval-difficulty")) (:file "eval-difficulty" :depends-on ("generate"))) :depends-on ( #-clisp :cl-ncurses ))
This declares that we’re creating a system called ‘puzzle-system’. If compiled under clisp, it depends on the :screen feature, otherwise it depends on :cl-ncurses (the cl-ncurses package is now deprecated, using the older UFFI where now we would use CFFI and cl-charms). There are four input files, some of which depend on others. By creating this .asd file, the programmer can avoid having to figure out what files need to be recompiled and loaded after a change has been made. One simply invokes this command:
(asdf:oos ‘asdf:load-op ‘<SYSTEM-NAME>)
The asdf system then compiles the appropriate files and loads them. You should ensure that asdf is loaded when your Lisp starts up. For SBCL, I use this ~/.sbclrc file:
(require :asdf) (setf asdf:*central-registry* (list* '*default-pathname-defaults* #p"/home/neufeld/.sbcl/systems/" asdf:*central-registry*))
This tells SBCL that it should load the :asdf system at startup. It also tells it where to look for additional systems. In the directory /home/neufeld/.sbcl/systems, I have placed symbolic links to the .asd files of the different Lisp packages I have downloaded. For instance, there are links to cl-ncurses.asd, cl-ppcre.asd, cffi.asd, and so on. This way, I can build a project that depends on one of those packages, and the ASDF make system will locate the appropriate system wherever it might be, and include it as appropriate.
ASDF is capable of much more than this, I’ve presented little more than the basic mode of operation. As your projects become more sophisticated, you may find yourself learning the more advanced features of ASDF for proper compiler and loader control. This, though, is a beginner’s guide to dependency tracking and compiling using ASDF in Lisp.