Enclosed is the first draft of the description of array initializer
syntax, which has evolved quite a bit in ES4.  The draft feels a little
raw, maybe because we have yet to pin down all aspects of how structural
array types interact with everything; please read critically and ask
difficult questions.  (The draft contains an informative section on
structural array types as I think they ought to behave.)

--lars
Title: Array initializers

Array initializer syntax


NAME:                       "Array initializer syntax"
FILE:                       spec/language/array-literals.html
CATEGORY:                   Expressions (E262-3 chapter 11)
SOURCES:                    ES3; REFERENCES [1]-[7]
SPEC AUTHOR:                Lars
DRAFT STATUS:               DRAFT 1 - 2008-04-08
REVIEWED AGAINST ES3:       NO
REVIEWED AGAINST ERRATA:    YES
REVIEWED AGAINST BASE DOC:  NO
REVIEWED AGAINST PROPOSALS: NO
REVIEWED AGAINST CODE:      NO
REVIEWED AGAINST TICKETS:   YES
IMPLEMENTATION STATUS:      ?
TEST CASE STATUS:           ?


OPEN ISSUES

  * A splicing shorthand has been introduced for ES4 array
    initializers.  In order to be compatible with other uses of the
    same syntax (destructuring, rest arguments, the spread operator)
    only one splice is allowed.  That may be a foolish consistency.


REFERENCES

[1] proposals:structural_types_and_typing_of_initializers
[2] ES4 base document
[3] Ticket #66
[4] Ticket #120
[5] Ticket #249
[6] Ticket #319
[7] Ticket #370

Synopsis

This draft pulls together everything that has been proposed and tentatively agreed about array initializer syntax and semantics.

Primary syntax

In its general form an array initializer is comprised of an optional keyword (const or var), followed by a bracket-delimited comma-separated list of optional expressions with the last _expression_ optionally followed by a triple-period token and a final _expression_, followed by an optional type annotation.

   [ "const" | "var" ] "[" Body "]" [ ":" Type ]
where Body is one of
   AssignmentExpression? ( "," AssignmentExpression? )*
   ( AssignmentExpression? "," )* "..." AssignmentExpression

The "Type" that annotates the initializer must be a structural array type or a nominal class type whose constructor accepts zero arguments.

Creating Array objects

If the annotating type is absent, or if the annotating type is a structural array type, then the following rules apply.

NOTE   Some informative remarks about structural array types are included at the end of the document.

As in ES3, an elided _expression_ leaves a hole in the array (by the same syntactic rules, not repeated here).

NOTE   The value of a hole can be read provided the type of the hole has a default value (as do primitive string, number, and boolean values as well as nullable object types and the "any" type *). The details are defined by another draft spec but there are some informative comments in the later section on structural array types.

The final optional _expression_ of the form ... E evaluates E to yield some object v and splices some properties of v into the new array object. v need not be an instance of Array, but it needs to provide a value for length and to allow elements with names in the range 0 to length-1 (suitably clamped) to be queried for their presence and, if present, to be read. If a property with a name n in that range is missing from v then the property in the new object that v[n] would have initialized will be a hole.

FIXME   The above needs to be formalized properly, obviously.

If the array initializer is prefixed by const or var then the array properties created will be created as fixtures; if the prefix is const those fixtures will additionally be read-only.

The general form of Type is [T0,,Tk,...Tr] where T0 through Tk (the fixed types) and Tr (the rest type) are all optional. The initializer must provide a (possibly elided) value for each of T0 through Tk, and may provide additional values only if Tr is present.

NOTE   If the initializer is prefixed by const or var, or if there are types T0 through Tk present in the type, then array elements can still be elided from the initializer and there can be holes in any spliced-in object. However, the missing properties will be present as uninitialized fixtures on the object, not as missing dynamic properties. So 1 in [1,,3]:[double,double,...double] will return true. A longer discussion is included below, in the section on structural array types.

Creating arbitrary objects with numbered properties

If the annotating type is a class type whose constructor accepts zero arguments then the const and var keywords must not be present.

In this case, the _expression_ evaluates to a new instance of the class type where successive initializer values have been assigned to numbered properties of the new object and the length of the initializer has been assigned to the length property of the new object.

In other words,

    [1,,3,,] : Vector.<double>
is syntactic sugar for
    (let (TMP = new Vector.<double>)
      (TMP[0] = 1,
       TMP[2] = 3,
       TMP.length = 4,
       TMP))
for a fresh variable TMP.

Secondary syntax

Suppose T is a structural array type:

   type T = [...double]

Then the new operator can be used as follows:

   new T( 10 )
   new T( 5, 1.0 )

The meaning of the above two phrases is exactly:

   [,,,,,,,,,,] : T
   [1.0, 1.0, 1.0, 1.0, 1.0] : T

In other words, used with a structural array type the new operator takes one or two arguments. The first argument is always the length. The second argument, if present, is the initial value to use for the properties at indices below the length.

NOTE   The array type can be of any kind: with or without fixed types or a rest type.

Rationale

This section is informational and will not be part of the finished Standard.

The splicing syntax is symmetric with the prefix ... syntax in in destructuring array patterns:

    var [x,y,...z] = E

To remain compatible with the destructuring syntax, the splicing syntax is only allowed at the end of the literal -- it doesn't make sense to have an open destructuring pattern in the middle of a pattern.

However, that may be taking compatibility too far, as there are obvious uses for having multiple splices (just as there are uses for allowing multiple spread operators in a function call).

The new syntax furthers the evolutionary programming agenda and is yet another point on the continuum between ES3 programs and class-based ES4 programs (the syntax abstracts away from the type T that is the subject of new -- whether it's a class or a structural type). For example,

   type Doubles = [...double]
   new Doubles(10)
evolves easily to
   type Doubles = Vector.<double>
   new Doubles(10)

Nominal type annotations on array initializers further the evolutionary programming agenda, allowing programs that use structural array types as annotations on literals to be changed easily to use class types instead. For example,

   type Doubles = [...double]
   [1,2,3]:Doubles
evolves easily to
   type Doubles = Vector.<double>
   [1,2,3]:Doubles

In addition, [1,2,3]:Vector.<double> is by itself a fairly reasonable way to write down a vector datum.

Structural array types

This section is informational; a separate spec will explain array types in detail.

A structural array type is comprised of some possibly empty sequence of fixed types T0 through Tk followed by an optional rest type Tr. Elements in the type can be elided; elided elements default to the "any" type *.

    "[" ( Type? "," )* ("..." Type)? "]"

Examples:

    [...double]          /* doubles, length >= 0 */
    [string, ...double]  /* one string followed by doubles; length >= 1 */
    [string, double]     /* one string and one double, length = 2 */
    [string, , string]   /* [string, *, string] */
    []                   /* length = 0 */

An object v is said to be of an array type T if v is an instance of (a subtype of) Array and has the fields required by T. That is, if T is [T0,,Tk,...Tr] then v must have fixed type constraints T0 through Tk on properties named 0 through k, and a general constraint Tr on properties named k+1 through 232-2.

The fixed type constraints are handled by means of fixtures: fields named 0 through k are fixtures. The general constraint Tr on the other hand applies to dynamic properties.

Effectively (and making use of some imaginary syntax), we can say that

   [string, double, ...boolean]
is essentially this record type:
   { 0: string, 1: double, 2...: boolean, length: double }
(apart from a lot of methods defined on Array objects).

This split between fixture and dynamic properties looks like it could cause trouble, but as it happens it will not.

The reason is that it could cause trouble is that there is a two-pass name lookup algorithm that gives priority to fixture properties, and that means that in the above object the property 1 and the property 2 would be treated differently though both are logically array properties. That would seem bizarre.

The reason that it does not cause problem in practice is that the difference between fixture and dynamic properties is not observable in an object that has been created by annotating array initializers with a structural array type. Such an object would have Array.prototype as its [[Prototype]] and it's not possible for the program to introduce new fixtures on the prototype object. In other words, the array objects created this way have strictly more fixtures than all the objects in their prototype chains. Thus the two-pass name lookup algorithm will never find a fixture in a prototype that in effect shadows a dynamic, index-named property in the object itself.

The final wrinkle (alluded to earlier) is that fixture properties may be elided from the initializer. For example, consider this object:

   ["foo", ,] : [string, double]

The property "1" can't be missing, because the type requires that it be there: the object "is of that type", so by definition the property "1" must always exist, and the type of that property must always be double. For a property to "have a type" (other than a rest type) is to be a fixed property.

There is a simple interpretation for this (also alluded to earlier): the property is in the object, but it is uninitialized. If the type of the property has a default value, then that value is produced when the property is read. Otherwise, the property cannot be read before it has been written (or a run-time error occurs). That happens for instances of non-nullable user-defined classes.

_______________________________________________
Es4-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es4-discuss

Reply via email to