tfeb-lisp-toys

TFEB.ORG Lisp toys

This repo contains a collection of small Common Lisp toys I’ve written over the last thirty-odd years. These are explicitly toys: they’re little things which I wrote for amusement and which I decided might one day be useful either to me or someone else.

The documentation is generally rather sparse. Some of the toys may work, some may not. Some may never have worked.

General

Modules

All of these things are independent modules, providing something which looks like org.tfeb.toys.<module>. There is no ASDF or other system definition for all of them. If they need other things either from amongst themselves or from other modules I’ve written they’ll do so using require-module which you’ll need to have installed in that case. In the case that they need things written by other people or other larger systems they’ll do that using Quicklisp. Currently none of them do either.

Portability

All of the toys purport to be portable Common Lisp.

Zero history

The repo from which the toys are published was invented in 2021, but some of them are 20 years older than that. Some of that history exists but not in the publication repo.

Naming conventions

All of the toys use domain-structured names: packages, modules, features and so on have names which start with a reversed DNS domain and then continue to divide further. The prefix for all of the toys is org.tfeb.toys. See the TFEB.ORG tools documentation for a little more on this.


An iteration protocol: for

This lets you define iterators for objects by defining methods on iter which typically returns a function. next (another generic function) should then know how to get the next element from an iterator: for an iterator which is a function it just calls it. for is a macro which will loop over objects, calling iter to make the iterator & next to get the next values. There are also ranges, and finally a gather macro which is a bit like Python’s list comprehensions (or what I thought they were like in 2004).

As an example the method on iter for lists is this:

(defmethod iter ((l list) &key)
  (lambda ()
    (if l
        (multiple-value-prog1
            (values (car l) t)
          (setf l (cdr l)))
      (values nil nil))))

(In fact it’s #'(lambda ...) which tells you how old this code is.) You could then gather the even integers in a list:

(gather (* x x)
  for (x '(1 2 t 3))
  when (numberp x)
  when (oddp x))

Note that for & when are not loop-style keywords: gather turns them into (when ...), so they’re just the normal CL macros:

(gather (* x x)
  for (x '(1 2 t 3))
  when (numberp x)
  when (oddp x))
 -> (let ((#:a 'nil) (#:at 'nil))
      (for (x '(1 2 t 3))
        (when (numberp x)
          (when (oddp x)
            (if #:a
                (setf (cdr #:at) (list (* x x)) #:at (cdr #:at))
              (setf #:a (list (* x x)) #:at #:a)))))
      #:a)

for lives in org.tfeb.toys.for and provides :org.tfeb.toys.for.

Turning case into an explicit jump table: ncase

ncase is just like casebut it can turn case statements into explicit jump tables if they are large enough. From the comments in the file:

This is actually not a really good example because the compiler is likely to be able to do a better job of the decisions involved, and it can also use real jumps rather than function calls which only may turn into jumps.

I can’t see a case where ncase would be useful today, but it’s there.

ncase lives in org.tfeb.toys.ncase and provides :org.tfeb.toys.ncase.

Global lexical variables: glex

CL only has global special variables, which are dynamically scoped. But it’s easy to fake global variables which are lexically scoped using symbol macros. That’s what glex does.

defglex is like defvar: (defglex x) will define x as a global lexical but not give it an initial value, while (defglex x 1) will give it an initial value as well. You can provide documentation strings.

defglex* is like defparameter: you can’t omit the value and it is set each time.

glex lives in org.tfeb.toys.glex and provides :org.tfeb.toys.glex.

Racket-style parameters: parameters

This lets you wrap special variables in functions and provides forms which will bind these named functions.

define-parameter is like defvar. define-parameter* is like defparameter. parameterize is like let, parameterize* is like let*

An example:

(define-parameter hugeness 10 "how huge a thing is")

(print (hugeness))
(parameterize ((hugeness 20))
  (print (hugeness)))

will print 10 and then 20.

parameters lives in org.tfeb.toys.parameters and provides :org.tfeb.toys.parameters.


The TFEB.ORG Lisp toys are copyright 2000-2021 Tim Bradshaw. See LICENSE for the license.