Friday, March 4, 2011

"No MAKE-LOAD-FORM" error with OpenMCL Common Lisp

I'm trying to run the ray tracing code form Paul Graham's ANSI Common Lisp on OS X using SLIME with OpenMCL (well, now called CCL). In that code, there's a constant defined whose value is a structure, and when I call either slime-compile-and-load-file or slime-compile-defun on any function that uses the constant, I get an error message:

No MAKE-LOAD-FORM method is defined for #S(POINT :X 0 :Y 0 :Z 200) [Condition of type SIMPLE-ERROR]

I found a post explaining the complication and another one lamenting it, but what needs to be added to the code to negotiate this aspect of OpenMCL?

From stackoverflow
  • When STRUCTURE-OBJECTs (and some other types of objects) appear as literal, constant objects in code being processed by COMPILE-FILE, COMPILE-FILE needs to know how to arrange that, when the resulting binary file is loaded, an "equivalent" object is created. There are many possible definitions of "equivalent" : sometimes, it's important that components of the loaded object share structure with other objects, sometimes it's important that initialization happens in a certain way, and sometimes none of these things are important. To determine how to recreate the constant object, COMPILE-FILE calls the generic function MAKE-LOAD-FORM; this behavior should be described in any CL reference or tutorial. (A reference or tutorial should also note that the implementation can't define default MAKE-LOAD-FORM methods that would be applicable to all instances of STRUCTURE-CLASS or STANDARD-CLASS, and should also note that MAKE-LOAD-FORM-SAVING-SLOTS is a convenient function to use in MAKE-LOAD-FORM methods for objects whose initialization doesn't need to be complicated, e.g.:

    (defmethod make-load-form ((p point) &optional env)
      (declare (ignore env))
      (make-load-form-saving-slots p))
    

    Note that that method has to be defined at compile-time, so that COMPILE-FILE can call it to determine how to save the constant POINT object.

    None of this is CCL-specific. What might be is the question of which things are constant, literal objects and which things aren't.

    In code like:

    (defconstant a-point (make-point :x 0 :y 0 :z 200))
    
    (defun return-a-point () a-point)
    

    the compiler's allowed (but not required) to substitute the value of A-POINT for the reference to it in the function RETURN-A-POINT. (If the compiler does so, that would mean that there's a literal/constant POINT object in the code being compiled, and COMPILE-FILE would need to call MAKE-LOAD-FORM to determine how the object should be saved and loaded; if the compiler doesn't do this substitution, then MAKE-LOAD-FORM doesn't need to be called in this example.)

    Whether an implementation does this kind of substitution or not is up to the implementation. The spec also leaves it unspecified as to whether the value form in a DEFCONSTANT form is evaluated at compile-time, load-time, or both, and notes that care must be exercised (by the user) to ensure that the expression always evaluates to the same value.

    CCL generally tries to evaluate the DEFCONSTANT value form at compile-time, and is fairly aggressive about substituting the values of named constants for references to them; in some cases, this means that MAKE-LOAD-FORM methods on the classes of the constants' values must be defined. Other implementations may be less willing to do this substitution for some types of objects. Both strategies are correct, and portable code can't assume which strategies are being followed (though much purportedly portable code surely does make such assumptions.)

    Different treatment of things defined by DEFCONSTANT seems like the mostly likely cause of this sort of thing (unexpected calls to MAKE-LOAD-FORM which no one's bothered to define). One can avoid some of these issues in a way that should be portable by doing:

    (defconstant a-point (make-point :x 0 :y 0 :z 200))
    
    (defun return-a-point () (load-time-value (symbol-value 'a-point)))
    

    This will have a similar effect to simply allowing an implementation that wants to do so (as CCL does) to do constant-substitution, but the use of LOAD-TIME-VALUE will ensure that the constant value is evaluated only at load time (and that MAKE-LOAD-FORM won't be involved.)

0 comments:

Post a Comment