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

Issue: STANDARD-VALUE (Version 1)



 From my list of "pending issues" distributed at the last X3J13 meeting...

-----
Issue:        STANDARD-VALUE
References:   None
Category:     ADDITION
Edit history: 21-Oct-88, Version 1 by Pitman
Status:	      For Internal Discussion

Problem Description:

  Sometimes a program binds option variables to implausible settings for a
  very temporary purpose. For example, *PRINT-BASE* might be bound temporarily
  to 3 or *PACKAGE* might be bound to the KEYWORD package.

  Entry to the debugger or some other form of recursive read-eval-print loop
  may happen either asynchronously (in some implementations) or synchronously
  due to a call to ERROR or BREAK. When that happens, if these option variables
  are bound to invalid values, it is most useful to bind them back to something
  useful.

  Unfortunately, Common Lisp does not provide a way of distinguishing 
   ``I'm binding/setting this variable for a temporary purpose which
     should not affect recursive read-eval-print loops.''
  from
   ``I'm binding/setting this variable for the express purpose of
     affecting recursive read-eval-print loops.''

  So, for example,

   (DEFUN PRINT-IN-KEYWORD-PACKAGE (X)
     (LET ((*PRINT-BASE* (FIND-PACKAGE "KEYWORD"))) (PRINT XX)))
   (PRINT-IN-KEYWORD-PACKAGE 'ZAP)
   >>Error: The variable XX is unbound.
   Debug> (HELP)
   >>Error: The function :HELP is undefined.

  It might be useful for *PACKAGE* to have been bound back to something
  other than keyword here.

   (DEFUN BASE-3-REPL ()
     (LET ((*PRINT-BASE* 3) (*READ-BASE* 3) (*PRINT-RADIX* NIL))
       (MY-READ-EVAL-PRINT-LOOP :PROMPT "Base 3> ")))
   Base 3> (- 10 1)
   2
   Base 3> (+ 'FOO 1)
   Error: FOO is not a valid argument to the + function.
   Debug> :Use-Value 10
   2

  Here it was useful that *PRINT-BASE* was not bound back to something.

Proposal (STANDARD-VALUE:NEW-FUNCTIONS):

  Introduce these new macros:

  DEFVAR-STANDARD name value					[Macro]

   Like DEFVAR, but the initial value is recorded as the `standard value.'
  
  LET-STANDARD let-bindings &body forms				[Macro]

   Like LET, but binds both the values and the standard values of the
   indicated variables (which must have been previously defined by
   DEFVAR-STANDARD).

  PROGV-STANDARD names values &body forms			[Macro]

   Like PROGV, but binds both the values and the standard values of the
   indicated variables (which must have been previously defined by
   DEFVAR-STANDARD).

  STANDARD-VALUE name						[Function]

   Returns the standard value of the indicated variable.
   Note that this may not be the same as (SYMBOL-VALUE name).

   SETF may be used with this function. Note that setting the
   standard value of NAME does not set (SYMBOL-VALUE name).

  When the debugger is entered, variables which have been declared standard
  but which do not currently have their standard values (as judged by EQL)
  will be rebound to their standard values (and a mechanism will be provided
  for viewing the values to which they were bound at debugger entry time).

  NON-STANDARD-VALUES						[Function]

   Returns a list of variables which are not currently bound to their
   standard values.

  The following variables in the LISP package are defined as if by
  DEFVAR-STANDARD:

    *PRINT-ARRAY*    *PRINT-GENSYM*   *READ-BASE*
    *PRINT-BASE*     *PRINT-LENGTH*   *READ-DEFAULT-FLOAT-FORMAT*
    *PRINT-CASE*     *PRINT-LEVEL*    *READ-SUPPRESS*
    *PRINT-CIRCLE*   *PRINT-PRETTY*   *READTABLE*
    *PRINT-ESCAPE*   *PRINT-RADIX*

  Define that LOAD and COMPILE-FILE bind *PACKAGE* using LET-STANDARD.

  Define that IN-PACKAGE sets both the SYMBOL-VALUE and the
  STANDARD-VALUE of the variable in question.

Test Case:

  Contrast these with the analogous cases in the problem description:

   (DEFUN PRINT-IN-KEYWORD-PACKAGE (X)
     (LET ((*PRINT-BASE* (FIND-PACKAGE "KEYWORD"))) (PRINT XX)))
   (PRINT-IN-KEYWORD-PACKAGE 'ZAP)
   >>Error: The variable XX is unbound.
   Rebinding *PACKAGE* from #<PACKAGE "KEYWORD"> to #<PACKAGE "USER">.
   Debug> (HELP)
   ...This is presumably typeout from some function called HELP...

   (DEFUN BASE-3-REPL ()
     (LET-STANDARD ((*PRINT-BASE* 3) (*READ-BASE* 3) (*PRINT-RADIX* NIL))
       (MY-READ-EVAL-PRINT-LOOP :PROMPT "Base 3> ")))
   Base 3> (- 10 1)
   2
   Base 3> (+ 'FOO 1)
   Error: FOO is not a valid argument to the + function.
   Debug> :Use-Value 10
   2

Rationale:

  This would allow users to say when, when binding a variable, whether they
  intended to bind it only `temporarily' or if they had some deeper purpose
  in mind.

  Also, users of Genera (where standard values already exist) have
  complained that IN-PACKAGE does not set the standard value of *PACKAGE*
  in Genera. The same is true for the binding of *PACKAGE* by LOAD. Other
  users would complain if standard values were affected. The confusion is
  not really due to the fact one meaning is preferrable over the other, but
  rather due to the fact that Common Lisp cannot document such a meaning
  without first admitting the concept of standard values. If standard
  values existed in Common Lisp and the documentation was clear on how
  IN-PACKAGE and LOAD related to that concept, users would no longer be
  forced to guess what these functions did with respect to such information,
  and so those users who are now surprised would no longer have reason to
  be surprised.

Current Practice:

  Symbolics Genera implements the proposed functionality (with slightly
  more options and slightly different names).

Cost to Implementors:

  Relatively small. A set of fairly straightforward definitions, looking
  something like:

  (DEFVAR *STANDARD-VALUES* '())

  (DEFUN STANDARD-VALUE (NAME)
    (LET ((ENTRY (ASSOC NAME *STANDARD-VALUES*)))
      (IF (NOT ENTRY) (ERROR ...) (CADR ENTRY))))

  (DEFUN SET-STANDARD-VALUE (NAME VALUE)
    (LET ((ENTRY (ASSOC NAME *STANDARD-VALUES*)))
      (IF (NOT ENTRY) (ERROR ...) (SETF (CADR ENTRY) VALUE))))

  (DEFSETF STANDARD-VALUE SET-STANDARD-VALUE)

  (DEFMACRO DEFVAR-STANDARD (NAME VALUE)
    `(PROGN (DEFVAR ,NAME ,VALUE)
	    (PUSHNEW (CONS ',NAME ,NAME) *STANDARD-VALUES* :KEY #'CAR)
	    ',NAME))

  (DEFMACRO LET-STANDARD (BINDINGS &BODY FORMS)
    `(LET ,@BINDINGS
       (LET ((*STANDARD-VALUES*
	       (LIST* ,@(MAPCAR #'(LAMBDA (BINDING) 
				    `(CONS ',(CAR BINDING) ,(CAR BINDING)))
			        BINDINGS)
		      *STANDARD-VALUES*)))
	 ,@FORMS)))

  (DEFMACRO PROGV-STANDARD (VARS VALUES &BODY FORMS)
    (LET ((TEMP (GENSYM)))
      `(LET ((,TEMP ,VARS))
         (PROGV ,TEMP ,VALUES
           (LET ((*STANDARD-VALUES* *STANDARD-VALUES*))
	     ;; This puts them on backward from how LET-STANDARD does, but
	     ;; if there are no duplicates, that won't matter... and
	     ;; duplicates are disallowed as per LET.
	     (DOLIST (,TEMP ,TEMP)
	       (PUSH (CONS ,TEMP (SYMBOL-VALUE ,TEMP)) *STANDARD-VALUES*))
	     ,@FORMS)))))

  (DEFUN NON-STANDARD-VALUES ()
    (LET ((RESULT '()))
      (DOLIST (ENTRY *STANDARD-VALUES*)
        (UNLESS (EQL (SYMBOL-VALUE (CAR ENTRY)) (CDR ENTRY))
	  (PUSH (CAR ENTRY) RESULT)))
      RESULT))

  [I wrote these quickly and didn't test them. If anyone else reviewing this
   has time to test them and let me know about bugs, that would be nice. -kmp]

Cost to Users:

  None. This change is upward compatible.

Cost of Non-Adoption:

  There is no way for Common Lisp programs to provide advice to systems
  already supporting this facility.

Benefits:

  The interaction between programs that bind important variables and the
  debugger would become more clear (and hence less frustrating) to users.

Aesthetics:

  Some might argue that this adds a small amount of undue hair.

  However, the hair is easy to overlook the nagging little problems that
  come from not using this facility. The problem is that, without a
  facility like this, those who do care about these issues have no way to
  express their intent.

Discussion:

  Pitman thinks a facility like this would probably improve the quality of
  debugging interaction in Common Lisp implementations.

  The problem with the meaning of IN-PACKAGE was first pointed out to
  Pitman by Rees, who encountered the problem while creating his portable
  Scheme implementation.