- Spice is an experimental evolution of the ECMAScript programming language.
- Designed to be more suitable for
- object-oriented programming
- compilation
- fast and compact run-time requirements e.g. palm-tops
- projects of 20+ Kloc
- Goal to increase range of applications without sacrificing scriptability
Spice is an experimental evolution of the ECMAScript programming language. Spice is designed to be more appropriate for compilation, faster and more compact to interpret, and more suitable for large-scale projects than its antecedent. The objective is to greatly increase the range of applications for which this kind of scripting language is practical without sacrificing its hallmark of interactivity. In particular, for our local requirements Spice must be capable of operating within low memory situations, such as today's palm-top computers.
- Object-oriented programming expressed indirectly. Less friendly for scripting. Less friendly for compilers!
- Compiling ES to native-code would be ineffective. Many idioms depend on relatively costly attribute lookups.
- ES Objects are a very general but heavyweight datastructure.
- Name spaces unconstrained.
- Insuffient abstraction and encapsulation mechanisms.
Although it is possible to write in an object-oriented style in ECMAScript, it is quite indirect manner. This limits the usefulness of this style because it is not readable, efficient or reusable. Spice adds a fairly standard object-oriented class system (prototypes).
As ECMAScript stands, there would be only a modest gain from compiling to native code. There are several language features which demand an interpreted approach - such as attribute lookup, multiple false values, and special syntax such as "with". Furthermore, the only compound data structure is the "object" a general but relatively massive representation. Spice trims away all compilation-unfriendly features and incorporates compact datastructures.
In order to make Spice a practical language for medium-to-large projects it is necessary to add functional abstraction, class encapsulation, and a simple but effective module system to ECMAScript. More controversially perhaps, the identification of methods and instance variables with global object properties has to be undone.
- Lexical binding
- Prototypes (records)
- Methods
- Multiple dispatch
- Optional static typing
- Constants and variables
- Arrays
- Non-full datastructures
- Multiple return values
- Symbols
- Scalars
The above provides a list of the most significant features which have been added to Spice. The biggest set of features, perhaps unsurprisingly, is concerned with the addition of full object-oriented programming support.
One change is the introduction of lexical scoping rules. The dynamic environments of ES have been eliminated in favour of a more structured approach - there is no equivalent to "with" in Spice. Without this change, it is very difficult to generate good code for variable access and update. This structure is also beneficial for people reading and writing scripts as the lexical scoping allows code fragements to be understood in a modular fashion. See example.
Each control construct introduces its own local lexical scopes. This is important for loops, see below.
Prototypes are Spice classes. Within a prototype declaration slots, closely modelled on the existing ES properties, are defined. Methods can be defined both inside and outside of the prototype, by contrast - as befits a scripting language. Accessing and updating slots is no different from running methods. This encapsulation of implementation is very useful in a scripting language as it allows details of implementation to change freely. See example
Prototypes have been designed to be capable of implementing compact record types. Accessing record slots within methods can be compiled into one or two instructions. To achieve this, prototypes, instances, slots and methods are not as general and loose as ES objects. It is not possible to redefine the method of an instance by assigning a function to a slot. Nor is it possible to add slots freely in the way one adds properties.
These restrictions allow the slot and method dispatch tables to be built statically. This in turn eliminates the need for dynamic search and caching solutions - and makes compilation a relatively attractive technology. However, these restrictions also make it undesirable to implement slots and methods directly as properties.
As mentioned before, methods can appear outside of prototype definitions. This is because Spice supports multiple dispatch directly. This is useful in an object-oriented scripting language as it eliminates complex and cumbersome design patterns such as the visitor pattern. In fact this makes methods and functions almost interchangeable - the only difference being whether they can be distributed across module boundaries. See example
To support method definition, we have introduced optional static typing. This is an oddly novel approach in that it seems a natural but largely untried option for scripting languages. (In some BASICs variables can be limited to integer or strings, for example.) When a variable is declared, it can be optionally statically typed. This means that all assignments to the variable are, in principle, checked. See example
Method dispatch is determined by the types of the formal parameters for each method. But the ability to optionally type variables is useful in loop constructs, for example. If the type of the variable is known at compile-time specially efficient loop code can be generated for that collection type. See example
In addition to being optionally typed, variables can be declared as fixed after the first assignment (const instead of var). This is of immediate benefit to the compiler in the case of top-level identifiers. Furthermore, formal parameters declared const by default - which is of benefit to the reader - and makes the job of the compiler easier. This default also applies to loop variables! This will seem paradoxical until you note that loop variables are localised to the loops lexical block and hence redeclared each time round the loop and so, in principle, recreated each time round the loop. Optimising this potential for cost away is well-understood compiler technology. But this means that loop variables cannot be assigned to in the body of the loop and hence loops can be more intensively optimised. See example
Arrays have been given special support in Spice. They need to be compact and fast to access. Any prototype can be declared to be an array - and all instances thereby become arrays. Iteration over arrays is richly supported and should be highly efficient. Our aim here is to make numerical programming practical for all but the most numerically intensive tasks. See example
Arrays can also be specialised to non-pointer datatypes such as characters, integers, and floating point numbers. This has a major impact on garbage collection performance (measured in comparable systems).
Spice also includes a fully worked out multiple-value return semantics. This is not as straightward a gain as, say, prototypes. However, good independent compilation of Spice modules depends on propagating module information correctly without this feature. So including it does not entail new problems.
One benefit of multiple returns is that scripting with arrays and dictionaries is enormously simplified. However, there are many high-level performance benefits, too. In particular, functions returning collections of information can avoid constructing intermediate datastructures. Another is that multi-dimensional hash tables are much more efficient than the equivalent constructed from nested hash tables. See example
At a less rarified level, perhaps, is the inclusion of symbols, scalars and units. Symbols are interned, immutable strings whose primary role is property lookup. The fact they are interned means that equality is typically a single instruction. Scalars provide an efficient and structured way of denoting constants - and simply a degenerate case of units.
Spice provides direct support for units, that is to say numbers with an associated dimensionality. They have designed to be implemented as compactly and efficiently as ordinary integers. Because the Spice run-time system automatically maintains and checks unit consistency, including automatic conversion between different measures (e.g. feet vs metres), this makes scripting much safer too. See example
- Modules
- Open and closed definitions
- Local definitions and lambda expression
- Unified method and slot access/update
Some features of Spice are there to help structure large programs. They are not directly related to the desire to improve performance except that improved performance naturally leads to bigger projects and ambitions.
Spice includes a relatively simple module system. By constrast with C++, the prototype declaration does not provide a natural name space boundary. Instead modules control the visibility of names. Modules must explicitly import the modules they depend on. Imported identifiers can be refered to directly or the programmer can require a particular prefix. Imported identifiers can be re-exported and, indeed, so can entire modules. This allows modules to be carved up according to the programmer's preferences and reassembled in a single module for easy importing. Modules are not include files, though. See example
Functions and methods can be exported from a module. However, the definition of a method can be spread over many modules whereas the definition of a function is limited to a single module. This simple distinction helps both reader and compiler.
To improve the ability to abstract over regions of code, and cut down on the cut-n-paste tendency, Spice allows programmers to write nested functions and in-line functions (lambda expressions). There are no restrictions associated with these. These are very convenient for scripting, particularly when compared against inner classes for example, and their use usually improved compactness. See example
Lastly, we have unified slot and method access. It is possible to write method updaters that allow method calls to appear on the left hand side of assignments. These allow prototypes to provide complete encapsulation of an abstract datatype - and not reveal whether a slot is virtual or actual, for example. This is even more important in a scripting language where the code may be distributed over many pages and very hard to control. See example
Migrating from ECMAScript to Spice is supported in several ways. Firstly, we believe that the learning curve from ECMAScript to Spice is a relatively easy one. Secondly, we expect all scripts to be marked as to the version of ECMAScript they require. When a guess is required as to the version, Spice is sufficiently close that many scripts will run unchanged.
To support this migration we have informally reviewed Javascript programs and tried to ensure that the likely differences lead to controlled and acceptable results. For example, all variables must be declared in Spice. When an undeclared variable is encountered, the action taken by the Spice compiler is compatible with the expectations of the Javascript programmer via defined recovery actions.