Next on our tour of less commonly used Lisp features is load-time-value. This special operator allows the programmer to write a form that is executed when the file is loaded, and to take the primary value returned by that form and insert it into the code as a literal object.
So, why might one typically use this? The obvious use case is to perform expensive operations to initialize a local variable inside a function. While the programmer could choose to do this work in a top-level form assigned to the value field of an interned symbol, that violates encapsulation principles by making the variable visible outside the scope of its intended user.
To demonstrate, I’ll start with the alternative solution. For purposes of this demonstration imagine that expensive-function does something difficult that takes a long time to run. Adding one to a number is not commonly a difficult operation, but it’s my stand-in. So, here’s the solution without load-time-value:
(defparameter *lookup-table* (labels ((expensive-function (x) (format t "WORKING-BAD~%") (+ 1 x))) (mapcar #'expensive-function '(1 2 3 4 5 6 7)))) (defun demonstrate-bad () (format t "lookup-table is ~A~%" *lookup-table*))
Loading and executing is as follows:
CL-USER> (load "load-time-value") WORKING-BAD WORKING-BAD WORKING-BAD WORKING-BAD WORKING-BAD WORKING-BAD WORKING-BAD T CL-USER> (demonstrate-bad) lookup-table is (2 3 4 5 6 7 8) NIL
So, when the file is compiled, our lookup table is assembled, and the expensive function is executed. When the demonstrate-bad function is called, the lookup table is not recomputed, so it doesn’t have to make that expensive calculation every time it’s invoked. The disadvantage here is that I’ve had to intern a symbol called *lookup-table*. Other functions can locate that symbol, and can modify its contents, and if I have to do this many times I’ll have to be careful that my lookup table names don’t collide. Also, by separating the definition of the table from the function that uses it, it becomes less obvious to the maintainer what *lookup-table* is for, and who might be using or modifying it.
Now, here’s the solution with load-time-value:
(defun demonstrate () (let ((lookup-table (load-time-value (labels ((expensive-function (x) (format t "WORKING~%") (+ 1 x))) (mapcar #'expensive-function '(1 2 3 4 5 6 7)))))) (format t "lookup-table is: ~A~%" lookup-table)))
The output of load and invoke is:
CL-USER> (load "load-time-value") WORKING WORKING WORKING WORKING WORKING WORKING WORKING T CL-USER> (demonstrate) lookup-table is: (2 3 4 5 6 7 8) NIL
The behaviour is the same as in the other example, but now the lookup table is a local variable within demonstrate. It’s not stored in an interned symbol, so other functions can’t locate or modify it. It can’t collide with variable names in other functions, and by putting it in this function it makes it very clear to the maintainer that this table is used exclusively within the demonstrate function.
It is important to note that the load-time-value form may be executed before other top-level forms in the same file have loaded. This means that if the form invokes functions defined in the same file, those functions may or may not be known at the time the load-time-value form executes. The relative order is implementation-defined, so the programmer cannot assume any particular order in portable code. To avoid confusion, such load-time-value forms should only refer to symbols that are known to have been loaded earlier, in other files, or to symbols that are part of the Common Lisp image. This is why I have used labels here, rather than a separate defun-ed function.