Ive been toying with writing a few "introductory" articles on (common) lisp. I should document how I do basic project structuring, web apps and desktop apps (by the way of SDL, as there are no usable bindings to cocoa, swiftui or qt in lisp, and I don't know how to make one). Let's start with the basic projects, though.
Circumstances
I get paid to write clojure. I hack as much as possible of my unpaid stuff in Common Lisp. I use mainly macs these days, but know how to use linux (and in fact this blog gets deployed on one) and explicitly don't know anything about Windows. I use emacs. The things I'll write here will be a way to do lisp, but not the way to do lisp, as lisp is more like perl than python in that regard. If you do stuff differently, I'm interested in hearing or reading about it (and am reachable by my mastodon-handle @feuer@mastodontti.fi.
Basic stuff
There are a lot of lisp runtimes. These are the runtimes I find relevant:
-
SBCL
A good base for free-standing lisp apps. There are others like it, but I like this one.
-
ECL
A lisp that's easy to embedd into any app that has C bindings. I've not done any released work with this, but if I were to write a desktop app, I'd hack a qt-ui with c++, embed ECL into it and try to make its UI live-hackable in the lisp-repl
-
ABCL
If I were to do a JVM-desktop app, I'd research if ABCL could be used the same way as I'd use ECL inside a qt app. I haven't ever really touched ABCL though.
In this document we're using sbcl. Install it from your package manager and install quicklisp following these instructions too. Quicklisp is a dependency manager tool, that's not completely unlike mvn, lein or clj-cli. The largest difference is that quicklisp is usable in REPL and pulling a new dependency doesn't require rebooting the process. I assume that you have also emacs and slime (installable from melpa, according to its github page) installed.
After all of the above are installed, slime probably needs configuring. My .emacs' slime configuration is surprisingly simple:
(setq inferior-lisp-program "sbcl") (slime-setup '(slime-fancy))
Project stuff
asdf -related theory
asdf is a library that comes with your lisp distribution. It provides the module-abstraction on top of lisp's native packages. It calls the fundemental building blocks 'systems'. Quicklisp is a networked extension of that model, where ql finds asdf-systems from its repositories and downloads + loads them when requested. Asdf files contain the project's/system's metadata, a list of systems it depends on (a correctly configured quicklisp can load missing deps automatically when loading your system!), and a list of directories (":module"s) and :files it needs to load to reanimate the system into a living repl.
I had troubles in the past accepting the need to list all the source files in the correct load order, after years of having clojure's namespace loader do a topological, lazy, recursive and automatic load. It's a tradeoff though. Clojure's ns-loader requires the developer adhere its rules on how package-hierarchy is transformed into a file hierarchy. When the loading is explicit, suddenly you can put your code wherever in the repo you like and it doesn't have implications on your package hierarchy.
How to set up an empty project
Create an empty directory wherever. There are some urban legends that if your project is located in ~/common-lisp/projectname, running `(quicklisp:quickload :projectname)` should load it correctly, but I've had tremendeous troubles making that work. That's why I tend to ignore that rule.
Touch yourprojectname.asd in your just-created empty project directory.
feuer@vivacia resource_handler % pwd pwd /Users/feuer/Projects/finrope/resource_handler feuer@vivacia resource_handler % touch resource-handler.asd
Fill your new asd file with a defsystem declaration similar to mine:
(require 'asdf) (in-package :asdf-user) ;; this name doesn't have to be string= to the file's name (defsystem "linnarope-resource-handler" ;; your name :author "Ilpo Lehtinen" :licence "GPLv3" ;; dependency list. Correctly set up quicklisp might be able to load these (if missing) alongside of your system, but I tend to just copy this list into a repl and loop every element into #'quicklisp:quickload :depends-on ("binding-arrows" "hunchentoot" "ironclad" "trivial-utf-8" "cl-ppcre" "com.inuoe.jzon" "easy-routes" "str" "cl-fad" "log4cl" "cl-dbi" "alexandria" "xmls" "cl-css" "cl-mustache" "parse-number" "quri" "cl-hash-util") :description "A resource handler for linnarope game" ;; these :files are loaded in order they're specified here ;; directories map to :module s in this tree and files map to :file s in the :components list ;; files lose their .lisp extension though :components ((:module "src" :components ((:module "local-lib" :components ((:file "lisp-fixup"))) (:file "middleware") (:file "migrations") (:file "tmxreader") (:module "db" :components ((:file "maps") (:file "sprites") (:file "palettes"))) (:module "views" :components ((:file "root") (:file "sprite") (:file "complete-map"))) (:module "routes" :components ((:file "sprite"))) (:file "main"))))) ;; (asdf:make "linnarope-resource-handler") ;; (linnarope.main:start-server)
In a new and empty project :components would probably be initially just `((:module "src" :components ((:file "main"))))`, with src/main.lisp containing just a `(defpackage example (:use :cl)) (in-package :example) (format t "Loading succeeded!~%")`
When the asd-file and the project skeleton are done, run M-x slime-compile-and-load-file (C-c C-k) in the asd-file's buffer. If that succeeds, run the `(asdf:make "$systemname")` in repl. It makes the actual load happen. When that's done, I tend to have a (go)-like function that kicks possible http-server on, connects to the db, runs migrations, etc.
That's it
Now you have a lisp repl ready for hacking. If you need a dependency, call `(quicklisp:quickload "dep")` in *slime-repl* buffer and remember to update asdf's dependency list. Next time we'll probably see what dependencies I find cool and necessary when doing web apps. Happy hacking!