[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Issue DEFCONSTANT-VALUE (V2)



I have updated the "proposal" and "current practice" sections of this in
response to comments received.  A companion proposal to address the
issue of constants computed at load-time will follow in a separate
message.
 

Issue:		DEFCONSTANT-VALUE
References:	Proposal COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY
		  section (2)
Category:	CHANGE to previous proposal; CLARIFICATION to CLtL
Edit History:	29 Sept 1988, V1 by David Gray
		 6 Oct  1988, V2 by David Gray - Update proposal to remove
			prohibition against re-evaluating at load time, and
			specify null environment.  Add Lucid to current
			practice.

Status:		For internal discussion 
 
Problem Description:

  CLtL does not specify when the value form of a DEFCONSTANT is evaluated;
  this is clarified in COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY
  but in an unacceptable way.
  
  The DEFCONSTANT macro does two things beyond what DEFPARAMETER does:

    1. It makes it possible for the compiler to replaces references with
       the value at compile time instead of at run time. 

    2. It causes the compiler to complain if the user attempts to SETQ
       or re-bind the constant name.

  These two things give rise to two different views of what the purpose
  of DEFCONSTANT is:

    1. The purpose of DEFCONSTANT is to facilitate compile-time
       substitution of the values of named constants.  A consequence of
       this is that alteration by SETQ or binding should not be
       permitted.
  or 
    2. The purpose of DEFCONSTANT is to declare that the value must not
       be changed at run time.  A side-effect of this is that, under
       certain circumstances, the compiler may be able to substitute the
       value for the reference at compile time.

  The specification of DEFCONSTANT in proposal
  COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY seems to assume the
  second purpose above, which has the effect of severely limiting its
  usefulness for the first purpose.  I contend that this is a mistaken
  viewpoint and that the definition of DEFCONSTANT should be guided by
  the first purpose above.  

  The concept of named constants was introduced into programming
  languages largely to avoid having "magic numbers" scattered throughout
  the code.  By giving the value a meaningful name and defining it in
  just one place, the code is easier to understand and it is much easier
  to change the value if necessary.  In order to encourage this
  programming style of using named constants, it has always been assumed
  that there should be no loss of efficiency from "doing it the right
  way".  Thus, the compiler should substitute the value for each
  reference and the resulting object code will be just as efficient as
  if the number had been used directly.

  For example, Pascal has constant declarations that look like this:

    const MaxIndex = 100;
	  MaxCount = 101;

  Since there are often cases where the value of one constant depends on
  another, many Pascal implementations extend this to permit the value
  to be an expression that depends on previous constants and is
  evaluated at compile time:

    const MaxIndex = 100;
	  MaxCount = MaxIndex+1;

  This extension has been carried over into Modula and Ada, and even
  FORTRAN 77 has this functionality in its PARAMETER statement.  (On the
  other hand I don't know of any language that has functionality that
  says to compute a name's value at load time but don't permit it to be
  changed at run time.)  Now the obvious equivalent in Common Lisp would
  be:

    (defconstant max-index 100)
    (defconstant max-count (1+ max-index))

  But, according to the specification of DEFCONSTANT in proposal
  COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY, in order to get the
  same effect (i.e. ensuring compile-time substitution of the value),
  the user would have to write:

    (eval-when (eval compile load)
      (defconstant max-index 100))
    (defconstant max-count #.(1+ max-index))

  The #. is needed to cause the value to be evaluated at compile time
  and the EVAL-WHEN is needed to ensure that the first constant will be
  defined for use by #. .  However, if the second constant were going
  to be referenced by another constant definition later, then it would
  need to be:

    (eval-when (eval compile load)
      (defconstant max-index 100))
    (eval-when (eval compile load)
      (defconstant max-count #.(1+ max-index)))

  Note that separate EVAL-WHEN forms are required in order for the #. to
  work right.  I contend that this is getting much too complicated for
  something that started out as a simple and very typical example.

  It is true that a smart compiler would be able to optimize the simpler
  example (without EVAL-WHEN and #.) in certain limited cases, but if
  the user can't rely on this, then the effect would be to either
  discourage the use of named constants or to encourage frequent use of
  EVAL-WHEN and #. .

Proposal DEFCONSTANT-VALUE:COMPILE-TIME:
 
  Replace the paragraph describing DEFCONSTANT in proposal
  COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY with the following.
  
  DEFCONSTANT: The compiler must recognize the symbol as being constant
  (for example, to suppress warnings about references to the symbolic
  constant as an unbound variable or to enable warnings about binding or
  SETQ'ing the constant in the code being compiled).  The value form may
  be evaluated at compile-time in the null lexical environment in order to
  be be available for the compiler to substitute in when compiling
  references to the constant name (as described on pages 68-69 of CLtL)
  and when evaluating the value forms of subsequent DEFCONSTANTs.  (The
  value may also be useful to guide optimization even in cases where value
  substitution is not permitted.)  It is implementation dependent whether
  or not the symbol value is actually set at compile time or whether
  COMPILE-FILE temporarily remembers the value in some other way.  It is
  an error for the value expression to have a different value at load time
  than it had at compile-time, so it is implementation-dependent whether
  the expression is re-evaluated at load time or loads the compile-time
  value.

 Rationale:
 
  This makes the semantics of DEFCONSTANT consistent with constant
  declarations in other languages; thus this is a tried and proven
  approach and will reduce confusion for programmers moving from one
  language to another.  

  Run-time efficiency of compiled code seems a more important
  consideration than the ability to protect a load-time value from
  re-assignment at run-time.
 
 Current Practice:

  The Explorer has done it this way since release 3.0 and we have not
  received any complaints saying that this was a mistake.  Before then,
  the heuristic used to decide whether it was safe to compute and
  propagate the value at compile time led to inconsistent and confusing
  behavior.  In order to minimize incompatibility, if there is an error
  during evaluation of the value form, the error is reported as a
  compiler warning and the compiler causes the evaluation to be tried
  again at load time, so missing information at compile time is not a
  fatal error.

  Lucid also conforms to this proposal.
 
 Cost to implementors:

  The simple approach of just having DEFCONSTANT expand to (EVAL-WHEN
  (EVAL COMPILE LOAD) ...) would be trivial to implement.  A little more
  work in the compiler would be needed if the value were to be remembered
  temporarily during COMPILE-FILE, but this would be less effort than it
  would take to implement the optimizations permitted by the previous
  proposal.
 
 Cost to users:

  Some changes to user's code are likely to be required, such as wrapping
  an EVAL-WHEN around a DEFUN that is used later in the same file to
  compute the value of a DEFCONSTANT.  For the most part these changes
  will be obvious enough from the errors reported from attempting to
  evaluate the value form.  In some cases, the user may decide that using
  DEFPARAMETER is more appropriate.  There might be more subtle
  differences in behavior in cases where the compile-time and load-time
  values of the constant differ, but they are likely to be a problem
  regardless of which direction the issue is clarified.
  
 Benefits:

  The use of DEFCONSTANT will be simplified since efficient code can be
  generated without requiring the user to explicitly specify what needs
  to be done when.

 Costs of Non-Adoption: 

  Letting COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY stand will mean
  that it will be difficult for users to understand how to use DEFCONSTANT
  to get it to do what they want.

 Aesthetics:
 
  Are improved since the need to use EVAL-WHEN and #. is minimized.
 
 Discussion: