Search code examples
packagecommon-lispquicklisp

Common Lisp Package Definition with Dependencies for Exploration at the REPL?


This is another take at the question of packages and systems especially for usage locally in personal projects (maybe similiar to HOWTO definition and usage of Common Lisp packages (libraries)? from 2014). What are recommendations for how to approach this and can links be provided for sources that describe the recommended approach in 2022? CL's long half-life means lots of old blogs and books are still useful, but unfortunately not all of them, and it is hard for the novice to know which are the still viable guides.

Here is the start of a file that I might write for a simple personal exploration project. My basic working mode would be to start slime, load and compile this in emacs, and then change to the slime repl and use in-package to move into my package and start playing with my functions.

(ql:quickload "alexandria")
(ql:quickload "trivia")

(defpackage computing-with-cognitive-states
  (:use cl)
  (:nicknames :cwcs)
  (:local-nicknames (:alex :alexandria)))

(in-package :cwcs)

(defun sum-denom (dimension dist) ... ; and more defun's

The leading ql:quickload's strike me as ugly, but without them I can't load and compile. Is it recommended that I do this manually in slime repl first? Or is this considered an acceptable practice for small use cases like this? Or is there another recommended way?

If I am thinking of using my package elsewhere someday is it recommended that I make it a system, perhaps with quickproject? Or is it the more traditional approach to just load a fasl file from one directory on my system when another session (or another package definition) needs it?

In summary, the questions are about the writing of code that is for personal use, might be a small file, or might be a few files that depend on each other and a few external systems. What are some of the reasonable approaches and resources that describe those approaches? And which are the ones that would be easiest to scale up if the project grew into something that one wanted to share?


Solution

  • For personal one-off »scripts«, I have a little macro in my .sbclrc:

    (in-package #:cl-user)
    
    (defmacro ql-require (&rest system-names)
      `(eval-when (:compile-toplevel :load-toplevel :execute)
         (ql:quickload ',system-names)))
    

    Then my script file looks like this:

    (in-package cl-user) ; naked symbols, I don't care much about package pollution here
    
    (ql-require "alexandria" "arrows" …)
    
    (defpackage my-script
      (:use cl alexandria arrows))
    
    (in-package my-script)
    
    ;; script away!
    

    I open these in Emacs/SLIME, C-c C-k. This often just has the »main« at toplevel, so it will just print at the REPL. In other cases, I hit C-c ~ afterwards to work from the REPL.

    As soon as it's not just a script, but something I might want to quickload entirely, I create a simple .asd file for it, which just moves the systems I depend on from the ql-require in the file to the :dependencies in the system definition. The package definition can remain in the one .lisp file. All my common lisp projects are under ~/common-lisp/, which is in the ASDF load path by default. A very simple .asd file can look like this:

    (in-package #:asdf-user) ; uninterned symbols as string designators avoid package pollution
    
    (defsystem "my-system"   ; system names are lower-case strings
      :dependencies ("alexandria" "arrows" …)
      :serial t
      :components ((:file "my-file")))
    

    The filename for the system definition should be the same as the system name (my-system.asd here). If you put multiple systems into a single file, their names should share the filename as a prefix, optionally followed by a slash and some qualifying suffix (e. g. "my-system" and "my-system/test"). This ensures that ASDF can quickly find the right file to load without having to load it first.

    As soon as I split the functionality into several files, I will usually also put the defpackage into its own file.

    As soon as I create multiple packages, I will usually create subdirectories for each of them (:modules in :components in the system definition). (ASDF has an option to make a so-called package inferred system, but I prefer the central system definition.)

    If you want to share a system, you'd probably want to add an :author and a :license to the system definition (even if it's just CC0/Public Domain). If you think that it's of general interest, you can submit it to Quicklisp.