The less-familiar parts of Lisp for beginners — load-time-value

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.

2 thoughts on “The less-familiar parts of Lisp for beginners — load-time-value

  1. Does this have any advantage over defining the function within a let binding? Perhaps putting the labels form within the defun makes it clear that that’s the only place it is to be used, but if I saw this:

    (let ((lookup-table
    (labels
    ((expensive-function (x)
    (format t "WORKING~%")
    (+ 1 x)))
    (mapcar #'expensive-function '(1 2 3 4 5 6 7)))))
    (defun demonstrate ()
    (format t "lookup-table is: ~A~%" lookup-table)))

    I still wouldn’t assume I’m welcome to add more definitions within the scope of lookup-table, unless I need to add auxiliary functions to demonstrate that use the same binding.

    However, it seems load-time-value can be useful when you need a special value to test identity against: http://coding.derkeiler.com/Archive/Lisp/comp.lang.lisp/2009-06/msg00968.html

    1. Kyle,
      Yes, your code will achieve the same result for the problem I’ve set, but without using load-time-value. It moves the table out of the defun and into an enclosing let. I don’t disagree with that style, it’s a valid way to handle the considerations I outlined.
      So, if you prefer a slightly more subtle use case, there’s one in the CL-PPCRE package. There, the code uses define-compiler-macro to search for regexp objects with constant string regular expressions, and substitutes a form containing load-time-value, essentially moving the cost of building the regular expression parser out of the function-invocation time and into the file load time. This would be significantly more complicated to do in the alternative form you presented, as it would require moving local variables out of the defun and into a new enclosing let.
      For those interested, the exact place where I see that use is in CL-PPCRE v1.2.12, api.lisp line 546.

Leave a Reply to Christopher Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

反垃圾邮件 / Anti-spam question * Time limit is exhausted. Please reload CAPTCHA.