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

Revised draft of object creation proposal

This version differs from the previous one I mailed out only in some
small corrections based on comments received in the mail.  The terminology
has been changed back from "named argument" to "keyword argument."
It may be about time to start converting this to the text that will go
into the specification document.  Any more comments first?

Draft of, and notes toward, a new object creation proposal based on
CLOS subcommittee discussions July 2, 1987.  Updated based on Sep 17-18
1987 working group meeting.  Updated based on mail received up to Sep 28,
and converted back to CLtL terminology for keyword arguments.

-foo- means the word foo in italics.  FOO means the word foo in boldface.


This proposal assumes keyword arguments to methods work as described in the
accompanying lambda-list congruence proposal.

The proposal assumes CL-Cleanup issue KEYWORD-ARGUMENT-NAME-PACKAGE, which
stated that the names of &key arguments do not have to be keyword symbols.
The terminology of CLtL is used for discussing keyword arguments, but it
should be understood that keyword names are not necessarily symbols in
the keyword package; that's just a default convention.


-initarg-.  An initarg (initialization argument) is a keyword argument that
can be used to control object creation and initialization.  The &key arguments
to MAKE-INSTANCE are initargs.  It is often convenient to use keyword symbols
to name initargs, but the name of an initarg can be any symbol, including NIL.

-initarg list-.  An initarg list (initialization argument list) is a list of
alternating initarg names and values.  Its structure is identical to a
property list and also identical to the portion of an argument list processed
for &key parameters.  As in those lists, if an initarg name appears more than
once in an initarg list, the leftmost occurrence supplies the value and the
remaining occurrences are ignored.  The arguments to MAKE-INSTANCE, after the
first, are an initarg list.  As in an &key argument list, :ALLOW-OTHER-KEYS
can appear in an initarg list, and if its value is non-NIL, error-checking of
initarg names is disabled.

-slot-filling initarg-.  An initarg associated with a slot.  If the initarg
has a value in the initarg list, the value is stored into the slot of the
newly-created object, overriding any initform associated with the slot.  A
single initarg can fill more than one slot.  A slot-filling initarg that
fills a shared slot stores its value into the shared slot, replacing any
previous value.

-method-implemented initarg-.  An initarg associated with a method.  When
an object is created, the method is called with the initarg's value as an
argument and the method uses the value in any way it likes.  If the initarg
has no value in the initarg list, the method's lambda-list supplies a
default value.  A single initarg can be implemented by more than one
method.  An initarg can be both slot-filling and method-implemented.


DEFCLASS gets a new :INITARG slot option, which is followed by a symbol.
The symbol becomes the name of a slot-filling initarg for this class.
This slot option can appear any number of times.

DEFCLASS gets a new :DEFAULT-INITARGS option, which is followed by a list of
alternating initarg names and default-initarg forms.  If one of these initargs
does not appear in the initarg list supplied to MAKE-INSTANCE, the
corresponding default-initarg form is evaluated, then the initarg name and the
form's value are added to the end of the initarg list.  The form is evaluated
every time it is used.  The lexical environment in which this form is
evaluated is the lexical environment in which DEFCLASS was evaluated.  The
dynamic environment is the dynamic environment in which MAKE-INSTANCE was
called.  The appearance of a symbol as an initarg name in a :DEFAULT-INITARGS
option does not make that symbol a valid initarg name for the class.
The :DEFAULT-INITARGS option may be specified more than once.

  [Should the name of this option be :DEFAULT-INITARGS or :DEFAULT-INITARG ?]

Method-implemented initargs are defined simply by defining a method for
INITIALIZE-INSTANCE or ALLOCATE-INSTANCE.  The keyword name of each keyword
parameter specifier in the method's lambda-list becomes a method-implemented
initarg for all classes for which this method is applicable.

Initarg inheritance: The effective set of slot-filling initargs for a class C
is the union of the slot-filling initargs defined by C and its superclasses.
The effective set of method-implemented initargs for a class C is determined
by method inheritance.

Default-initargs inheritance: The set of initargs of C that have default value
forms is determined by the union of the :DEFAULT-INITARGS options of C and its
superclasses.  When more than one class in the CPL specifies a default value
form for a given initarg, only the form specified by the most specific such
class in the CPL is used.

Rules when initargs are duplicated in various ways:

  The :INITARG slot-option may be specified more than once for a given slot.

  A single initarg can initialize more than one slot if the same initarg name
  appears in more than one :INITARG slot-option.

  It is valid for a given initarg name to be defined more than once as a
  slot-filling initarg, as a method-implemented initarg, or both.

  If two initargs that initialize the same slot, with the same or different
  names, are given in the arguments to MAKE-INSTANCE, the leftmost of these
  initargs in the initarg list prevails.

  If two different initargs that initialize the same slot have default values,
  and neither is given explicitly in the arguments to MAKE-INSTANCE,
  the initarg that appears in a :DEFAULT-INITARGS slot-option in the most
  specific class prevails, or if they appeared in the same class, the one whose
  mention in :DEFAULT-INITARGS is leftmost in the DEFCLASS form prevails.
  Defaulted initargs are appended to the end of the initarg list in this order.

  If there are two different initargs that initialize the same slot, and one
  was given explicitly in the arguments to MAKE-INSTANCE while the other was
  defaulted via :DEFAULT-INITARGS, the explicit one prevails.  (This rule is
  implied by the two preceding rules, but it seems worth drawing attention to.)
  If a slot has both an :INITFORM and an :INITARG slot-option, and the
  slot-filling initarg is defaulted via :DEFAULT-INITARGS, the initform is not
  used and is not evaluated.

An illustrative example of the above rules:

  (defclass a () ((x :initarg a)))
  (defclass b (a) ((x :initarg b))
    (:default-initargs a 1 b 2))

  FORM                          INITARG LIST    CONTENTS OF X SLOT
  (make-instance 'b)            (a 1 b 2)               1
  (make-instance 'b 'a 3)       (a 3 b 2)               3
  (make-instance 'b 'b 4)       (b 4 a 1)               4
  (make-instance 'b 'a 1 'a 2)  (a 1 a 2 b 2)           1

Note: nothing is guaranteed about the order of evaluation of default-initarg
forms and initforms.  If there are dependencies among these forms, you should
be using INITIALIZE-INSTANCE methods instead.  In most programs, the initforms
and default-initarg forms are either constants or simple forms that construct
new objects; forms with side-effects are permitted, but are not typically used.


In this section, I have only sketched each function, for the sake of brevity.
Full writeups can be constructed once the overall framework has been agreed
upon.  Functions are in alphabetical order, in three sections at three protocol
levels.  The breakdown between the second and third levels may be changed.  By
coincidence, all functions listed except SLOT-BOUNDP and SLOT-MAKUNBOUND are
generic and expected to specialize on their first argument.

>>> Tools used for simple object-oriented programming:

(INITIALIZE-INSTANCE instance &key &allow-other-keys)

MAKE-INSTANCE calls this with the freshly-created instance, any initargs that
were supplied to MAKE-INSTANCE, and any defaulted initargs.  Users define
methods for this to create method-implemented initargs.  Typically,
user-defined methods are :AFTER methods, however that is not a requirement.

The primary method for INITIALIZE-INSTANCE is system-supplied and takes care
of the slot-filling initargs.  For each slot (whether local or shared):

  - if an initarg was specified or defaulted that fills that slot, its
    value is stored into the slot.  (This is true even if a :BEFORE method
    has modified the slot.)

  - otherwise, if the slot is uninitialized and it has an initform, the
    initform is evaluated and the result is stored into the slot.

  - the duplicate-resolution rules mentioned earlier are obeyed.

An implementation is permitted to optimize initforms that neither produce nor
depend on side-effects, by evaluating them and storing them into slots before
running any INITIALIZE-INSTANCE methods, rather than handling them in the
primary INITIALIZE-INSTANCE method.  This might be implemented by having the
ALLOCATE-INSTANCE method copy a prototype instance.  This means that :BEFORE
and :AROUND methods for INITIALIZE-INSTANCE cannot rely on all the slots being
uninitialized at the beginning.

An implementation is permitted to optimize default value forms for slot-filling
initargs by not actually consing the complete initarg list, when the only
method that would see the complete list is the system-supplied primary method,
e.g. when no other methods use &REST.  In this case default value forms can be
treated like initforms.  This has no visible effects other than a performance

(MAKE-INSTANCE class &key -initargs-...) => instance

Users call this function to create objects.  Class can be either a class or
the name of a class.  Meta-users can define new methods for MAKE-INSTANCE
to replace the object-creation protocol.

(SLOT-BOUNDP instance slot-name) => boolean

Allows writing INITIALIZE-INSTANCE :AFTER methods that only initialize slots if
they haven't been initialized already.

(SLOT-MAKUNBOUND instance slot-name) => instance

Restores a slot to the uninitialized condition.

>>> Functions underlying the tools

It is undefined what happens if you modify the values returned by any
of the functions in this section.  It is permitted, but not required,
for an implementation to return values that share with internal data
structures.  Some of these functions will be SETF'able; which ones
remain to be determined.

(CLASS-ALL-INITARGS class) => list of initarg names, including inherited

(CLASS-DIRECT-INITARGS class) => list of initarg names.  This works by
computing the applicable methods for ALLOCATE-INSTANCE and for
INITIALIZE-INSTANCE and examining their lambda-lists (using
METHOD-KEYWORD-NAMES), then combining that with the class's list of
slot-filling initargs.

   => ((initarg-name default-value-function default-value-form)...)

   => ((initarg-name default-value-function default-value-form)...)
This reflects the :DEFAULT-INITARGS option.  Default-value-form is the form
that was originally specified, and is retained purely for explanatory
purposes.  default-value-function is what gets actually called; its effect is
equivalent to enclosing default-value-form in the appropriate lexical
environment.  Default-value-function takes no arguments.

(CLASS-ALL-SLOT-INITARGS class) => ((initarg-name slot-name...)...)

(CLASS-DIRECT-SLOT-INITARGS class) => ((initarg-name slot-name...)...)
This reflects the :INITARG slot-option.

(COMPUTE-APPLICABLE-METHODS generic argument-list) => list of methods

(METHOD-KEYWORD-NAMES method) => list of symbols or &ALLOW-OTHER-KEYS,
indicating the keyword names of the keyword parameter specifiers in
the method's lambda-list.  The result is the symbol &ALLOW-OTHER-KEYS
instead of a list if the method's lambda-list contains that symbol.

>>> Meta-object functions

(ALLOCATE-INSTANCE class &key &allow-other-keys) => instance

Meta-users can replace the system-supplied, implementation-dependent method
for this.  Any keyword arguments accepted by applicable ALLOCATE-INSTANCE
methods become valid initargs.

(CHECK-INITARGS class initarg-list)

Meta-users could replace the system-supplied method that implements the
normal rules for initarg validity.

(DEFAULT-INITARGS class initarg-list) => initarg-list

The system-supplied method implements the :DEFAULT-INITARGS class option
by appending initargs that do not appear in initarg-list to the end
of the returned list.  The initarg-list supplied as an argument is not
modified.  The order of initargs appended to the list is determined by
the duplicate-initarg rules listed earlier.

(FINALIZE-INHERITANCE class &key slots methods initargs)

This is called by the system at least once before a class is instantiated, and
is called again whenever anything relevant changes.  System-supplied methods
for this conspire with methods for CHECK-INITARGS, etc., to make MAKE-INSTANCE
faster.  Users with their own optimization needs can add methods for this
generic function that will precompute things based on inherited information,
and update the precomputed information whenever anything changes.

The :slots, :methods, and :initargs arguments are booleans that are true
when the specified type of inheritance needs to be recomputed.


MAKE-INSTANCE behaves as if it was defined as follows, except that certain
optimizations are permitted, as detailed below and in the description of

(defmethod make-instance ((class standard-class) &rest initargs)
  (setq initargs (default-initargs class initargs))
  (check-initargs class initargs)  
  (let ((instance (apply #'allocate-instance class initargs)))
    (apply #'initialize-instance instance initargs)

(defmethod make-instance ((class-name symbol) &rest initargs)
  (apply #'make-instance (symbol-class class-name) initargs))

Optimization is possible, including inlining and constant-folding of method
lookup and method bodies, provided that the programming environment either
prohibits redefining these methods or updates everything when they are
redefined.  A possible example implementation would be that MAKE-INSTANCE has
a separate method for every class, which is automatically written and compiled
by the system.

This optimization relies on the FINALIZE-INHERITANCE generic function and some
unpublished slots of STANDARD-CLASS.

Because of optimization, methods for the generic functions listed may not
actually be called on every call to MAKE-INSTANCE, or may not receive
exactly the arguments that would be expected.  For example, CHECK-INITARGS
may actually be called before DEFAULT-INITARGS rather than after, if it has
already been determined that the default initargs will pass CHECK-INITARGS.

Additional explicit details of permissible optimization will need to be set


Lexical proximity of concepts--the declaration of an initarg as valid,
the specification of what it does, and the default if it is not supplied
are all together, in a slot specifier or in a method lambda-list.

Simple ways to do simple things--slot-filling initargs don't require the
user to write any code.  Method-implemented initargs work just like
ordinary function arguments as far as the user is concerned.

Minimal number of new languages--the only addition to Common Lisp is
a mildly complicated rule for how &KEY lambda-lists of methods do
validity checking.

Ability to do everything at some level--the underlying procedural level
is available.  Functions to access all the direct and inherited
information are documented.

Underlying mechanism is exposed so user can use it for other things, rather
than abusing instance creation as the only way to access the mechanism--the
FINALIZE-INHERITANCE provides everything the user needs.