This is a library written by François-René Rideau in days when he was the maintainer of the ASDF. This library allows you to move a piece of code, generated by a macro to the top-level of the lisp component.
This transformation happens during macro-expansion step.
As an example of this technique, “asdf-finalizers” contains a system “list-of” which defines a custom type “list-of”:
POFTHEDAY> (asdf:load-system :list-of)
T
POFTHEDAY> (typep '(1 2 3 4 5)
'(list-of:list-of integer))
T
POFTHEDAY> (typep '(1 2 3 4 "Hello Lisp World")
'(list-of:list-of integer))
NIL
POFTHEDAY> (typep '((1 2 3))
'(list-of:list-of integer))
NIL
POFTHEDAY> (typep '((1 2 3)
(4 5 6))
'(list-of:list-of
(list-of:list-of integer)))
T
POFTHEDAY> (typep '((1 2 "Not an integer")
(4 5 6))
'(list-of:list-of
(list-of:list-of integer)))
NIL
It does this by creating a predicate function on the fly first time when you are using the type specifier. Type definition binds a function to the symbol and returns a type specifier (and list (satisfies list-of-integer-p)).
Of cause for different element types it uses their own predicate names.
To reproduce this type definition we could write such code:
POFTHEDAY> (deftype my-list-of (type)
(let* ((name (format nil "LIST-OF-~S-P" type))
(predicate (intern name)))
(format t "Creating predicate ~A for ~A~%" name type)
(setf (symbol-function predicate)
(lambda (x)
(loop for c = x
then (cdr c)
while (consp c)
always (typep (car c)
type)
finally (return (null c)))))
`(and list
(satisfies ,predicate))))
MY-LIST-OF
POFTHEDAY> (typep '(1 2 3)
'(my-list-of string))
Creating predicate LIST-OF-STRING-P for STRING
NIL
POFTHEDAY> (typep '("foo" "bar")
'(my-list-of string))
T
POFTHEDAY> (typep '(1 2 3)
'(my-list-of integer))
Creating predicate LIST-OF-INTEGER-P for INTEGER
T
POFTHEDAY> (typep '(1 2 3)
'(my-list-of integer))
T
But if we’ll put this naive type definition into a library “my-list-of”, then there will be problems.
During the first loading a system, which uses “my-list-of”, everything will be ok. But when you’ll try to load it into a fresh Lisp image, this UNDEFINED-FUNCTION error will be signalled:
The function MYLIST::LIST-OF-INTEGER-P is undefined.
This is because of SBCL processes “(my-list-of integer)” definitions during macro expansion step, before compilation. But does not do this when loading a compiled FASL.
In other words, using “(my-list-of integer)” in the code causes side-effects during macro expansion step. And when you are loading a precompiled code into a fresh Lisp image, these side-effects are not available anymore.
“asdf-finalizer” solves this problem, by placing additional code into the (eval-when (:compile-toplevel :load-toplevel :execute)) block. This code may recreate a side-effect during the :load-toplevel phase.
Here is the original “list-of” type definition:
(deftype list-of (type)
(case type
((t) 'list) ;; a (list-of t) is the same as a regular list.
((nil) 'null) ;; a (list-of nil) can have no elements, it's null.
(otherwise
(let ((predicate (list-of-predicate-for type)))
(eval-at-toplevel ;; now, and amongst final-forms if enabled
`(ensure-list-of-predicate ',type ',predicate)
`(fboundp ',predicate) ;; hush unnecessary eval-at-toplevel warnings
"Defining ~S outside of finalized Lisp code" `(list-of ,type))
`(and list (satisfies ,predicate))))))
It uses a function “eval-at-toplevel”, which helps “asdf-finalizer” to collect “(ensure-list-of-predicate ‘integer ‘list-of-integer-p)” forms into a special dynamic variables.
Later, these collected forms should be injected to the top-level with the call to “asdf-finalizers:final-forms”:
(defpackage :test-list-of
(:use :cl))
(in-package :test-list-of)
(defun check-list-of ()
(typep '(1 2 3)
'(list-of:list-of string)))
(final-forms)
Also, you’ll need to replace :file with :finalized-cl-source-file in the components section of your ASDF system definition. This will turn on “top-level forms” collection mechanism for this file.
This call to final-forms will be expanded into the forms, injected into a special variable during the macro expansion stage:
(eval-when (:compile-toplevel :load-toplevel :execute)
(ensure-list-of-predicate 'integer
'list-of-integer-p)
(ensure-list-of-predicate 'string
'list-of-string-p)
...)
Each call to “ensure-list-of-predicate” will recreate a predicate function during when the compiled code will be loaded into the fresh Lisp image.
You can also use this technic to inject any code from macroses into the top-level. Just call “asdf-finalizers:eval-at-toplevel” or “asdf-finalizers:register-final-form” from the macro’s code and don’t forget to insert “(final-forms)” to the end of files where these macroses will be used.
As a bonus for everybody who is interested to learn how does code processing work in Common Lisp, there is a great @ngnghm’s article about Common Lisp code processing stages and eval-when usage:
https://fare.livejournal.com/146698.html
Fare shared a piece of history explaining why “asdf-finalizers” were created: