[Next] [Previous] [Up] [Top] [Contents] [Index]

2.3 Fields

2.3.4 Field Initialization

Cecil allows a field to be given an initial value when it is declared by suffixing the field declaration with the := symbol and an initializing expression. Additionally, when an object is created, an object-specific initial value may be specified for a non-shared field. The syntax of field initializers for object declarations and object constructor expressions is as follows:

field_inits	::=	"{" field_init { "," field_init } "}"
field_init	::=	name [location] ":=" expr
location	::=	"@" named_object

For example, the following method produces a new list object with particular values for its inherited fields:

method prepend(e, l@list) {
	object isa cons { head := e, tail := l } }

For a field initialization of the form name := expr, the field to be initialized is found by performing a lookup akin to message lookup to find a field declaration named name, starting with the object being created. Method lookup itself cannot be used directly, since the field to be initialized may have been overridden with a method of the same name. Instead, a form of lookup that ignores all methods is used. If this lookup succeeds in finding a single most-specific matching field declaration, then that field is the one given an initial value; the matching field should not be a shared field. If no matching field or more than one matching field is found, then a "field initializer not understood" or an "ambiguous field initializer" error, respectively, is reported. To resolve ambiguities and to initialize fields otherwise overridden by other fields, an extended name for the field of the form name@obj := expr may be used instead. For these kind of initializers, lookup for a matching field begins with the object named obj rather than the object being created. The obj object must be an ancestor of the object being created. Extended field names are analogous to a similar mechanism related to directed resends, described in section 2.8.

Immutable shared fields must be initialized as part of the field declaration; there is no other way to give them a value. Immutable copy-down fields may be initialized as part of the field declaration, but often they are initialized as part of object constructor expressions for objects that inherit the field, leading to a more functional programming style where data structures are (largely) immutable.

To avoid pesky problems with uninitialized variables, all fields must be initialized before being accessed, either by providing an initial value as part of the field declaration, by providing an object-specific value as part of the object declaration or object constructor expression, or by assigning to the field before reading from it. The static type checker warns when it cannot prove that at least one of the first two options is taken for each field inherited by an object, as described in section 3.7.

In Cecil, the initializing expression for a field declaration is not evaluated until the field is first read. If the field is a shared field, then the initializer is evaluated and the contents of the field is updated to refer to the initial value; subsequent reads of the shared field will simply return the initial value. This supports functionality similar to once functions in Eiffel and other languages. If the field is a copy-down field, then the initializing expression will be evaluated separately for each object accessed, and the result cached for that object. The initializing expression may name the formal parameter of the field declaration, allowing the initial value of the field to reference the object of which the field is a part. The default initializer is not evaluated if it is not needed, i.e., if the field has already been given a contents as part of object creation or via invocation of the set accessor.

By evaluating field initializers on demand rather than at declaration time, we avoid the need to specify some arbitrary ordering over field declarations or to resort to an unhelpful "unspecified" or "implementation-dependent" rule. It is illegal to try to read the value of a field during execution of the field's initializer; no cyclic dependencies among field initializers are allowed.

Evaluating a copy-down field's initializer expression repeatedly for each inheriting object seems to support common Cecil programming style. This corresponds to CLOS's :initform specifier. An earlier version of Cecil specified caching of the results of field initializer evaluation so that other objects evaluating the same initializer expression would end up sharing the initial value. The initializing expression was viewed as a shared part of the field declaration, not as a separate part copied down to each inheriting object. This earlier semantics corresponded more to CLOS's :default-initargs specifier. The difference in the semantics is exposed if the initializing expression evaluates to a new mutable object. In practice, it seems that each object wants its own mutable object rather than sharing the mutable object among all inheriting objects. Moreover, the old semantics can be simulated with a combination of a copy-down field that accesses a shared field to get the field's initial value.