💾 Archived View for thrig.me › tech › lisp › cons-job.gmi captured on 2024-05-26 at 15:57:17. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

LISP is a giant CONS job!

    (list 1 2 3)
    (cons 1 (cons 2 (cons 3 nil)))

The name LISP is derived from LISt Processing, but what is a list? A list is a particular form of CONS cells (that have a head, and a tail); in particular both lines of the above code yield the same structure:

    SBCL> (LIST 1 2 3)
    (1 2 3)
    SBCL> (CONS 1 (CONS 2 (CONS 3 NIL)))
    (1 2 3)

There are also accessors to get at a list of cons cells, though some of the old names (car, cdr, ...) have modern and perhaps more readable replacements (first, rest, ...).

    SBCL> (DEFPARAMETER BLAH '(1 2 3))
    BLAH
    SBCL> (CAR BLAH)
    1
    SBCL> (CDR BLAH)
    (2 3)
    SBCL> (CADR BLAH)
    2
    SBCL> (FIRST BLAH)
    1
    SBCL> (REST BLAH)
    (2 3)
    SBCL> (SECOND BLAH)
    2

CONS cells need not have a link (or a nil, to terminate the list) in the tail or second slot:

    (cons 'cat 42)
    (car (cons 'cat 42))
    (cdr (cons 'cat 42))

Or both the head and the tail of the CONS can be a link, in which case you get a tree, though the following may not be useful for anything. Usually such trees will instead be constructed and recursed through by suitable code.

    (defparameter tree
      (cons
        (cons
          (cons
            1
            nil)
          (cons
            nil
            (cons
              2
              nil)))
        (cons nil
              (cons
                (cons
                  3
                  (cons
                    nil
                    4))
                nil))))

Lists are everywhere in LISP. The following builds a subroutine with DEFUN, which is the typical way, and builds another from a LIST. Homoiconicity is a fancy word some folks will trot out in this context, but a more important point is that you probably shouldn't be using EVAL unless you know what you are doing.

    SBCL> (DEFUN ANSWER () 42)
    ANSWER
    SBCL> (LIST 'DEFUN 'ENOUGH NIL 640)
    (DEFUN ENOUGH () 640)
    SBCL> (EVAL (LIST 'DEFUN 'ENOUGH NIL 640))
    ENOUGH
    SBCL> (ANSWER)
    42
    SBCL> (ENOUGH)
    640

Practicality

A list is typically used for the arguments to the program, so if we want something that looks a bit like a C program (because I do a lot of C and unix) one might write the skeleton of a "command" and "arguments to that" pattern along the lines of:

    (defun main (argc argv)
      (unless (plusp argc) (error "not enough arguments"))
      (let ((command (pop argv)))
        (format t "~a,~a~&" command argv)))

    (defun setup ()
      (let ((argv (rest sb-ext:*posix-argv*)))
        (main (length argv) argv)))

    (setup)

In a larger script the "command" would probably be looked up in a list or a hash to run the relevant code, or one could hide all these details in an arguments processing library that also supports command line options, etc. The above code is unportable and assumes SBCL.

    $ sbcl --script args.lisp foo bar zot
    foo,(bar zot)

Modern languages will typically use a hash table for lookups in the "command" and "arguments to that" pattern, though LISP also offers an "alist" which is suitable for small numbers of keys, as there is less overhead than involved in a hash table. This is mostly an excuse to show the "(foo . bar)" CONS cell form, and the associated ASSOC function.

    (defun command-echo (args)
      (format t "~{~a~^ ~}~&" args))

    (defun command-world (args)
      (declare (ignore args))
      (princ "hello world")
      (fresh-line))

    (defparameter *commands*
      '(("echo"  . command-echo)
        ("world" . command-world)))

    (defun main (argc argv)
      (unless (plusp argc) (error "not enough arguments"))
      (let* ((command (pop argv))
             (call (cdr (assoc command *commands* :test #'equalp))))
        (if call (funcall call argv)
                 (error "no such command '~a'" command))))

    (defun setup ()
      (let ((argv (rest sb-ext:*posix-argv*)))
        (main (length argv) argv)))

    (setup)

Some usage of the above:

    $ sbcl --script args.lisp world
    hello world
    $ sbcl --script args.lisp echo foo bar
    foo bar

By contrast, a Perl script would probably use a hash lookup. Common LISP could, as well.

    #!/usr/bin/env perl
    use 5.10.0;

    my %commands = (
        echo  => sub { say "@_" },
        world => sub { say "hello world" },
    );

    my $c = shift // die "Usage: $0 command ..\n";
    exists $commands{$c}
      ? $commands{$c}->(@ARGV)
      : die "no such command '$c'\n";

Eventually I may figure out a suitable tree of CONS cells example, but not at this time.