Cleanup Issue FUNCTION-NAME

Status
Proposal LARGE, with sections 7, 8, 9 removed, passed Mar 89 X3J13
Category
ADDITION
References
SETF rules for what -place- can be (pp.94-7) FBOUNDP function (p.90) FMAKUNBOUND function (p.92) FUNCTION special form (p.87) SYMBOL-FUNCTION and setf of symbol-function (p.90) 88-002R pages 1-21, 2-21, 2-26, 2-39, 2-44, 2-46, 2-51, and 2-55 (There are additional references for the MEDIUM and LARGE proposals, but they are not listed here. They're obvious.)
Related issues
SETF-FUNCTION-VS-MACRO, SETF-PLACES, (both, subsumed, by, this)

Problem Description

The Common Lisp Object System needs a well-defined way to relate the name and arguments of a writer function to those of a reader function, because both functions can be generic and can have user-defined methods. The way that was adopted into Common Lisp when X3J13 voted to accept document 88-002R was to use a list (SETF reader) as the name of the writer function.

Some changes to the non-object-oriented portion of Common Lisp are required in order to support this.

This issue has three proposals.

Proposal (SMALL)

Add a new concept "function-name" (called "function-specifier" in 88-002R). A function-name is either a symbol or a 2-element list whose first element is the symbol SETF and whose second element is a symbol. Implementations are free to extend the syntax of function-names to include lists beginning with additional symbols other than SETF.

Add a new function (FDEFINITION function-name), which returns the current global function definition named by function-name, or signals an error if there is no global function definition. This follows all the same rules listed for SYMBOL-FUNCTION in CLtL p.90.

Add SETF of FDEFINITION to change the current global function definition named by a function-name. This follows all the same rules listed for SETF of SYMBOL-FUNCTION in CLtL p.90.

Change the FBOUNDP and FMAKUNBOUND functions, and the FUNCTION special form, to accept function-names in place of symbols. Implementation defined extensions to the syntax of function-names cannot use the symbol LAMBDA, since FUNCTION already uses that symbol.

Change the rules for SETF places (CLtL pp.94-7) by adding the following clause after all the existing clauses:

- Any other list whose first element is a symbol, call it reader. In this case, SETF expands into a call to the function named by the list (SETF reader). The first argument is the new value and the remaining arguments are the values of the remaining elements of -place-. This expansion occurs regardless of whether reader or (SETF reader) is defined as a function locally, globally, or not at all. For example, (SETF (reader arg1 arg2...) new-value) expands into a form with the same effect and value as (LET ((#:temp-1 arg1) ;force correct order of evaluation (#:temp-2 arg2) ... (#:temp-0 new-value)) (FUNCALL (FUNCTION (SETF reader)) #:temp-0 #:temp-1 #:temp-2...)).

Change the functions GET-SETF-METHOD and GET-SETF-METHOD-MULTIPLE-VALUE to implement the above change to the rules.

Document that a function named (SETF reader) should return its first argument as its only value, in order to preserve the semantics of SETF.

Change the macro DEFGENERIC and the function ENSURE-GENERIC-FUNCTION to refer to the function FDEFINITION where they now refer to the function SYMBOL-FUNCTION.

Change the macros DEFCLASS, DEFGENERIC, and DEFMETHOD, the special forms GENERIC-FLET and GENERIC-LABELS, and the functions DOCUMENTATION and ENSURE-GENERIC-FUNCTION to use the term "function-name" where they now use the term "function-specifier" or "function specifier".

Rationale for FUNCTION-NAME:SMALL:

This is the minimum change to Common Lisp needed to do what 88-002R says about (SETF reader). Giving implementations freedom to extend the syntax of function-names allows for current practice. Changing the name from "function-specifier" to "function-name" avoids confusion and improves consistency with the rest of the language, at the cost of a few small changes to 88-002R.

Proposal (MEDIUM)

Everything in FUNCTION-NAME:SMALL, and in addition:

Change the DEFUN macro to accept a function-name for its name argument, instead of only accepting a symbol. If function-name is (SETF sym), the body is surrounded by an implicit block named sym.

Rationale for FUNCTION-NAME:MEDIUM:

Keeping DEFUN consistent with DEFMETHOD is a good idea. Also 88-002R says "The name of a generic function, like the name of an ordinary function, can be either a symbol or a two-element list whose...", which implies this change to DEFUN.

Proposal (LARGE)

Everything in FUNCTION-NAME:MEDIUM, and in addition the following numbered points, each of which could be adopted independently, except where explicitly noted:

1. Change the function COMPILE to accept a function-name as its name argument.

2. Change the function DISASSEMBLE to accept a function-name as its name argument.

3. Change the FTYPE, INLINE, and NOTINLINE declarations and proclamations to accept function-names, not just symbols, as function names.

4. Change the FLET and LABELS special forms to accept a function-name in the name position, not just a symbol.

5. Change the TRACE and UNTRACE macros to accept function-names, not just symbols, in the function name positions.

6. Change the ED function to accept (ED function-name) in place of (ED symbol).

7. Change the syntax of a function call to allow a function-name as the first element of the list, rather than allowing only a symbol.

8. Change the DEFMACRO macro and the MACROLET special form to accept a function-name in the name position, not just a symbol. Change the MACRO-FUNCTION function to accept function-names, not just symbols. Change the last rule for SETF places to use ((SETF reader) #:temp-0 #:temp-1 #:temp-2...) in place of (FUNCALL (FUNCTION (SETF reader)) #:temp-0 #:temp-1 #:temp-2...) so that (SETF reader) can be defined as a macro. This depends on item 7. If item 4 is rejected, MACROLET should be stricken from this item.

9. Add an optional environment argument to FDEFINITION, SETF of FDEFINITION, FBOUNDP, and FMAKUNBOUND. This is the same as the &environment argument to a macroexpander. This argument can be used to access local function definitions, to access function definitions in the compile-time remote environment, and to modify function definitions in the compile-time remote environment.

10. Change the second, third, fourth, fifth, seventh, and ninth rules for SETF places so that they only apply when the function-name refers to the global function definition, rather than a locally defined function or macro. (The ninth rule is the one that refers to DEFSETF and DEFINE-SETF-METHOD; the other rules listed are the ones that list specific built-in functions). The effect of this change is that SETF methods defined for global functions are ignored when there is a local function binding; instead, the function named (SETF reader), which may have a local function binding, is called. This change is most useful in connection with item 4, but does not actually depend on it.

11. Clarify that the eighth rule for SETF places (the one for macros) uses MACROEXPAND-1, not MACROEXPAND.

Rationale for FUNCTION-NAME:LARGE:

This extends the new feature throughout the language, in order to make things generally more consistent and powerful. Point by point:

1,2,3 - one should be able to compile, examine, and make declarations about functions regardless of whether they are named with symbols or with lists.

4 - locally defined non-generic SETF functions are a logical companion to locally defined generic SETF functions, which can be defined with GENERIC-FLET or GENERIC-LABELS. They make sense on their own, since one might define a local reader function and want a local writer function to go with it.

5,6 - one should be able to apply development tools to functions regardless of how they are named. The function DOCUMENTATION was already updated to work for function-names by 88-002R. There might be some difficulty with implementation-dependent syntax extensions to TRACE and UNTRACE conflicting with this new syntax.

7 - this restores consistency between the FUNCTION special form and the first element of a function call form.

8 - it seems more consistent to allow macros to be named the same way that ordinary functions are named. However, this might be considered redundant with DEFSETF.

9 - this is not needed by the "chapter 1 and 2" level of CLOS, but might be used by the metaobject based implementation of ENSURE-GENERIC-FUNCTION.

10 - this change was in SETF-FUNCTION-VS-MACRO and makes item 4 more useful.

11 - this change was in SETF-FUNCTION-VS-MACRO and is a good idea, but actually is independent of everything else being proposed here.

Examples

;This is an example of the sort of syntax 88-002R allows
(defmethod (setf child) (new-value (parent some-class))
  (setf (slot-value 'child parent) new-value)
  (update-dependencies parent)
  new-value)
(setf (child foo) bar)

;If SETF of SUBSEQ was not already built into Common Lisp,
;it could have been defined like this, if the MEDIUM or LARGE
;proposal is adopted.
(defun (setf subseq) (new-value sequence start &optional end)
  (unless end (setq end (length sequence)))
  (setq end (min end (+ start (length new-value))))
  (do ((i start (1+ i))
       (j 0 (1+ j)))
      ((= i end) new-value)
    (setf (elt sequence i) (elt new-value j))))

;The preceding example would have to be defined like this
;if only the SMALL proposal is adopted.  This is a method
;all of whose parameter specializer names are T.
(defmethod (setf subseq) (new-value sequence start &optional end)
  (unless end (setq end (length sequence)))
  (setq end (min end (+ start (length new-value))))
  (do ((i start (1+ i))
       (j 0 (1+ j)))
      ((= i end) new-value)
    (setf (elt sequence i) (elt new-value j))))

;Another example, showing a locally defined setf function
(defun frobulate (mumble)
  (let ((table (mumble-table mumble)))
    (flet ((foo (x)
             (gethash x table))
           ((setf foo) (new x)
             (setf (gethash x table) new)))
      ..
      (foo a)
      ..
      (setf (foo a) b))))

;get-setf-method could implement setf functions by calling
;this function when the earlier rules do not apply
(defun get-setf-method-for-setf-function (form)
  (let ((new-value (gensym))
	(temp-vars (do ((a (cdr form) (cdr a))
			(v nil (cons (gensym) v)))
		       ((null a) v))))
    (values temp-vars
	    (cdr form)
	    (list new-value)
	    `(funcall #'(setf ,(car form)) ,new-value ,@temp-vars)
	    `(,(car form) ,@temp-vars))))

Current Practice

No implementation supports exactly what is proposed. Symbolics Genera and the TI Explorer support something close to the MEDIUM proposal, but differing in a number of details. Symbolics Genera supports items 1, 2, 3, 6, and 11, and modified forms of items 5 and 8, of the LARGE proposal. Moon considers this proposal's variations from Symbolics current practice to be an improvement, although incompatible in some cases.

Many implementations currently support only symbols as function names.

Symbolics Genera and the TI Explorer have some additional function-name syntaxes.

Cost to Implementors

The SMALL and MEDIUM proposals are estimated to be no more than 50 lines of code and require no changes to the "guts" of the interpreter and compiler. Most of the code for this can be written portably and was shown on two slides at the X3J13 meeting.

Some of the changes in the LARGE proposal are trivial, some require the compiler to use EQUAL instead of EQ to compare function names, and items 4, 7, and 8 might require a more substantial implementation effort. Even that effort is estimated to be negligible compared to the effort required to implement CLOS.

Cost to Users

No cost to users, other than program-understanding programs, since this is an upward compatible addition.

As with any language extension, some program-understanding programs may need to be enhanced. A particular issue here is programs that assume that all function names are symbols. They may use GET to access properties of a function name or use EQ or EQL (perhaps via MEMBER or ASSOC) to compare function names for equality. Such programs will need improvement before they can understand programs that use the new feature, but otherwise they will still work.

Cost of Non-Adoption

We would have to make some other language change since the language became inconsistent when 88-002R was adopted.

Performance Impact

This has no effect on performance of compiled code. It might slow down the compiler and interpreter but not by very much.

Benefits

CLOS will work as designed.

Aesthetics

Some people dislike using anything but symbols to name functions. Other people would prefer that if the change is to be made at all, the LARGE proposal be adopted so that the language is uniform in its treatment of the new extended function names. Other proposals for how to deal with SETF in CLOS were considerably less esthetic, especially when package problems are taken into account.

SETF would be more esthetic, but less powerful, if it had only the proposed setf functions and did not have setf macros. Such a major incompatible change is of course out of the question; however, if setf functions are stressed over setf macros, SETF will be much easier to teach.

Discussion

Moon supports at least FUNCTION-NAME:MEDIUM. He does not necessarily approve of all parts of FUNCTION-NAME:LARGE.

! Additional Comments:

On the whole, I like this presentation much better than either of the other two writeups that were circulated previously. I suspect that it might be necessary to vote on each of the items in the LARGE proposal individually, though. I think I would support items 1, 2, and 11, and don't have any particular objections to 3, 5, and 6. For item 4, if consistency with GENERIC-FLET and GENERIC-LABELS is an object, another alternative is to change those two special forms to be like ordinary FLET and LABELS, instead of vice versa.

- - - - - - - I support FUNCTION-NAME:MEDIUM and may support LARGE once I think about it some more.

As I explained in Hawaii, support for either of these is based on the :conc-name bugs being removed from the condition system. Of course, I believe the best way to do that is to CLOSify it. - - - - - - - - I'm still thinking about this, but while I am I wanted point out that MEDIUM is unacceptable to me because I don't think FLET and DEFUN should disagree on what they permit as defined names. If FLET were added to MEDIUM, I suspect I'd think it was an internally consistent position.

LARGE has an appeal to me in general, but I'm still mulling over the specifics. - - - - - - - - - - I favor the FUNCTION-NAME:LARGE proposal, because it defines a single, useful notion of what a function name is. The other proposals have the flaw that there are two kinds of function names: symbols, and extended names, with only some of the Lisp primitives accepting the latter. This may be convenient for some implementations, for the short term, but it fragments the language.

I have two other comments on the proposal.

A. Reducing the Cost to Implementors

One observation you could put in the Cost To Implementors section is that none of the SMALL, MEDIUM, or LARGE proposals require changes to the "guts" of the interpreter and compiler. This is because an implementation is free to use plain symbols internally to name functions, and use a hack like JonL's SETF:|3.FOO.BAR| mapping to convert non-symbol names to symbols. This conversion would be done as a part of parsing the handful of forms which accept function names, and then all other passes of the interpreter and compiler (the "guts") would just see symbols. (By "parsing" I mean ensuring the right number and type of syntactic subforms. You can see that this is a very early and simple stage of processing.) Or, Lisp compilers with an "alphatization" phase could perform function name symbolization at that phase.

B. Finishing the Job of Regularization

I'd like to suggest two additions to your smorgasbord of options in the FUNCTION-NAME:LARGE section of the proposal. One addition would regularize a major special case of functions--lambda expressions. The other addition would reaffirm an unstated regularity in the language, that function names can stand in for functions under FUNCALL and APPLY. Not only can the treatment of symbolic and setf-list function names be regularized, but lambda too can be treated in a consistent manner.

If these two points are added to your proposal, the language as a whole would have a completely uniform treatment of functions and function names. Here they are:

13. Declare that any function name is a suitable argument to FUNCALL and
    APPLY.  In such a case, the function name is passed to FDEFINITION,
    and the result (which may in turn be a function name) is called.
    That is, the following two expressions are equivalent, when fname
    is a function name:
	(FUNCALL fname x y)
	  <==>
	(FUNCALL (FDEFINITION fname) x y)
    Note that the definition is sought in the global environment.
    Compare with the rule which applies to a function name occurs,
    syntactically, as the car of a list in code:
	(fname x y)
	  <==>
	(FUNCALL (FUNCTION fname) x y)
	  <==> (under proposal item 9)
	(FUNCALL (FDEFINITION fname <local-environment>) x y)

12. Declare that any lamba expression (i.e., a list whose car is LAMBDA and whose cdr is a well-formed lambda argument list and body) is a function name. The effects of the function name accessors on lambda expressions are as follows. FDEFINITION returns an implementation-defined value which is the function specified the lambda expression, closed in the global environment. This FDEFINITION value cannot be changed by SETF. FBOUNDP always returns T, and MAKUNBOUND is an error.

Aesthetics

The effect of items 11 and 12 is to complete the regularization of Common Lisp's treatment of functions and function names. The total effect of proposal items 1 through 12 is that Lisp has just two notions for referencing function objects: FUNCTIONS, which are Lisp objects that directly represent executable code, and FUNCTION NAMES, which can denote functions. Symbols, SETF function names, and lambda expressions are all examples of the latter notion. The former notion is highly implementation dependent. Function names can occur as syntactic entities in code. FUNCALL and APPLY work uniformly on both functions and function names, with a consistent semantics.

Lambda expressions are often thought to denote "anonymous" functions, so it may seem paradoxical to treat them as names. The paradox is only apparent, since the expression itself has the properties of a Lisp function name: It is (typically) a cons tree which can be read, printed, and stored in source files, and it denotes a well-defined Lisp function.

Benefit to Users:

Function names are useful for representing objects in remote environments, because they need not be bound at all times to the same function, or to any function, and because they are typically stable in meaning across reads and prints, where plain functions are not. Programs which deal simultaneously with remote and local environments, such as CLOS, can probably be simplified, since function names can be used uniformly, rather than an ad-hoc mixture of functions and function names.

The language as a whole become more uniform from these additions and clarifications, making it easier to learn and use. (See Esthetics.)

Cost to Implementors

Interpreters which currently have a special case check for application of lambda expressions would need to modify this check to call FDEFINITION when a list of any sort is encountered. Note that all Common Lisps already must perform some such check, since lambda expressions can be funcalled (and this is currently a very special case, the only standard case of a list being funcalled). This means that every Lisp already has a place to insert the required call to FDEFINITION.

In some implementations, FDEFINITION of a lambda expression could be that lambda-expression itself. In others featuring a pre-eval codewalk, the walk would be done by FDEFINITION, which would return an appropriate closure.

Cost of Non-Adoption

Rather than two notions for function references (functions and function names), there would be several notions, each corresponding to the valid inputs for particular group of primitives. APPLY and FUNCALL would accept functions, symbolic names, and lambda expressions, but not setf function names. FDEFINITION and its kind would accept symbols and setf function names but not lambda expressions. If the :LARGE proposal is not adopted, this fragmentation would also apply to the various syntaxes involving function names; some names would be acceptable to DEFUN but not to FLET, etc.

- - - - - - - - - - - - - > 13. Declare that any function name is a suitable argument to FUNCALL and > APPLY. In such a case, the function name is passed to FDEFINITION, > and the result (which may in turn be a function name) is called.

I don't think this is such a good idea. The case of automatically coercing a symbol to a function is needed because it provides a portable mechanism for indirect addressing of a function; I haven't seen a reason to need this for non-symbol function specs. But more important is that coercing a symbol to a function is a trivial operation that is reasonable to do at run time on each call without adding a significant amount of overhead. FDEFINITION, on the other hand, is a much more expensive operation -- at best it might use GET to do a property list lookup, or it could be using string-append and INTERN to convert the name to a symbol. In either case, I think this is more work than you want to do on each call.

> 12. Declare that any lamba expression (i.e., a list whose car is LAMBDA and > whose cdr is a well-formed lambda argument list and body) is a function > name. The effects of the function name accessors on lambda expressions > are as follows. FDEFINITION returns an implementation-defined value which > is the function specified the lambda expression, closed in the global > environment. This FDEFINITION value cannot be changed by SETF. > FBOUNDP always returns T, and MAKUNBOUND is an error.

The exceptions for SETF and MAKUNBOUND show that this is not really as consistent as you might like. Furthermore, the FUNCTION special form would have to treat a LAMBDA expression as a function, not a function name, in order for it to be lexically scoped. It seems like this might just cause confusion rather than consistency.

Edit History