overview
summary of proposed ECMAScript 4.0 features not already in ActionScript 3.0
language still a work in progress (this lecture last updated april 17 2008)
ECMAScript 4 specification release date: December 2008
resources...
structural types
record type
array type
record type
record type: describes the structure of a set of objects
type Point = { x: int, y: int }
any object with fixed int x
and int y
belongs to Point
x
and y
, but may have other members)record type examples
new record type instance, with args for x
and y
new Point( 3, 10 )
new record type instance, type set with type annotation
{ x:3, y:10 } : Point
inline usage:
public function moveTo (p:{ x: int, y: int }) { x = p.x; y = p.y; } moveTo(somePoint); moveTo(someSprite); // Failure because untyped object doesn't have required type. moveTo({x:10, y:20}); // Success because object's type is specified. moveTo({x:10, y:20}:{ x: int, y: int });
array type
array type: type that describes the structure of an array
type intArray = [int]; // All elements must be int
type myArray = [int,string]; // First element must be int, // second must be string, // remaining elements unconstrained
new array type instance, with 7 elements:
new intArray(7)
new array type instance, type set with type annotation
[ 1,2,3 ] : intArray
nested structural types
type Person = { name:{ last:string, first:string }, born:Date, spouse:* }
[[int]] // Array of arrays whose elements are type int
union types
// x can be assigned either an int or a string var x:(int|string)
public function send (msg:(string|XML)):void {...}
Boolean, String, Number replace ES3 primitives
CURRENT BUILT-IN STATUS IS IN FLUX
ECMAScript 4 built-ins:
wrapper classes for boolean, string, double:
wrappers are dynamic, non-final, nullable
wrappers behave like ES3's primitives of the same name
predefined union types
new union types:
type AnyString = (string,String) type AnyBoolean = (boolean,Boolean) type AnyNumber = (byte,int,uint,double,decimal,Number) type FloatNumber = (double,decimal)
handy for writing libraries used by legacy and new code
type constraints on rest arguments
"rest" arguments can have type constraint
function drawPolygon (p1:Point, p2:Point, p3:Point, ...[Point]): void
non-nullable types
// "!" indicates that v cannot be assigned null var v : C!
// "?" indicates that v can be assigned null var v : C?
// vars of type C cannot be assigned null class C! { ... }
rationale: earlier (compile-time) detection of null-pointer errors
instance initialization
assign constructor parameter directly to an instance variable
// Assign x the value of parameter p class C { var x : D! // must be initialized function C (p):x = p { ... } }
the like operator
like
tests whether an object is structurally similar to a given type
// v will accept any of these: // { x:3, y:3 } : Point // { x:3, y:3 } // new MovieClip() var v: like { x: int, y: int }
use case: old ECMAScript 3 code using upgraded ECMAScript 4 library
the wrap operator
THIS FEATURE HAS BEEN RETIRED
wrap
can give an untyped object a type
o
has no type:
var o:* = { x: 10, y: 20 };
give o
type Point
:
type Point { x: int, y: int }; var v: wrap Point = o;
legally assign o
(now with type) to w (whose type is Point
):
var w:Point = v;
parameterized types
datatype used in class code dynamically determined on a per-instance basis
similar to C#'s "generics"
used primarily to implement core-language-level collections
class Hashtable.<K,V> { var store : [ [K,V] ]; function fetch(k:K) : V { var ix : uint = hash_func(k); while (store[ix][0] != k) ix = ... // some probe sequence return store[ix][1]; } } // Make a new Hashtable, with uint keys and string vals var h : Hashtable.<uint,string> = new Hashtable.<uint,string>();
lexical block scoping
use 'let' instead of 'var' to create block-scoped vars
{ let a = 10, b=1 { let a = 20 trace(a+b) // 21 } trace(a) // 10 }
expression closures
single-line functions
function getWidth () this.width;
&&= and ||=
&&= not often used
||= used for assigning default values
// this code... myVar = myVar || "default"; // can be shortened to: myVar ||= "default";
generic functions ("multimethods")
THIS FEATURE HAS BEEN DEFERRED
java-style method overloading
create generic function first:
generic function intersect(s1, s2);
attach ("overloaded") methods to the generic function:
generic function intersect(s1: Shape, s2: Shape) { // general intersection method } generic function intersect(s1: Rect, s2: Rect) { // fast intersection for rectangles }
not currently available in instance-method form
program units
THIS FEATURE HAS BEEN DEFERRED
similar to #include
, but based on semantics, not files
unit URLParser { use unit Splitter "http://www.mycompany.com/lib/splitter" use unit Unicode "http://www.mycompany.com/lib/Unicode" package com.mycompany.urlparser { ... } } use unit URLParser "http://www.mycompany.com/lib/URLParser"
iterators
based on python's iteration protocol
an interable object must have the following structural type:
type IterableType.<T> = { iterator::get: function (boolean=) : IteratorType.<T> };
an iterator object must have the following structural type:
type IteratorType.<T> = { next: function () : T };
for (i in o) ...
updated to perform iteration equivalent to:
let ($it = o.iterator::get(true)) { while (true) { try { i = $it.next(); } catch (e : iterator::StopIterationClass) { break; } ... } }
example showing iteration over Fibonacci sequence:
// The iterable and iterator class: class Fibber { private var a=0, b=1 private var cutoff:int = int.MAX_VALUE; public Fibber (cutoff:int) { this.cutoff = cutoff; } iterator function get(deep:boolean = false): IteratorType.<int> this public function next(): int { if (b >= cutoff) throw iterator::StopIteration [a,b] = [b,a+b] return a } } // Usage: for ( i in new Fibber(1000) ) trace(i)
generators
generator: function that generates a sequence of values for iteration
when called, the generator returns an iterator, but does not run
calling iterator.next() does 3 things:
yield
// A generator that yields 3 values function countdown() { yield 3; yield 2; yield 1; } var counter = countdown(); trace(counter.next()); // 3 trace(counter.next()); // 2 trace(counter.next()); // 1 // None left, so iterator::StopIteration exception is thrown trace(counter.next()); // ERROR!
python inspired
generators: rationale
rationale: elegant way to traverse data structures (esp. trees)
// A generator for finding leaves in a tree structure function fringe (tree) { if (tree is like {left:*, right:*}) { for (let leaf in fringe(tree.left)) yield leaf for (let leaf in fringe(tree.right)) yield leaf } else { yield tree } }
// Make a sample tree var tree = { left: { left: 37, right: 42 }, right: "foo" }
// Iterate over the tree for ( let x in fringe(tree) ) { trace(x); // 37, 42, "foo" }
operator overloading
redefine the behaviour of built-in operators for custom types
cannot redefine operations for built-in types
generic intrinsic function + ( list: UserList, user: User ) { list.push(user); return user; } // Usage: userList + user;
the call stack (proper tail calls intro)
PROPER TAIL CALLS HAVE BEEN DEFERRED
"call stack": list of the functions currently executing
each function call adds a new item ("stack frame") to the stack
a stack frame indicates where to resume execution after its function exits
when a function exits, its stack frame is removed
function submitForm () { validateInput(); sendInputToServer(); } submitForm();
##[diagram] call stack: =========================== Frame: submitForm() =========================== Frame: validateInput() Frame: submitForm() =========================== Frame: sendInputToServer() Frame: submitForm() =========================== Frame: submitForm() ===========================
proper tail calls
THIS FEATURE HAS BEEN DEFERRED
tail call: function calls another function as its last operation
function getArea (w, h) { return multiply(w, h); }
"proper tail call": run the tail-called function without adding to the call stack
i.e., return multiply()
's result directly to getArea()
's caller
prevents stack overflow (out of space) for recursive functions
enables popular functional programming idioms: state machines, recursion
meta-level hooks
override meta::invoke to intercept method calls
other overrides:
meta function set(name, value) { trace("Setting " + name + " to " + value); }
used internally to handle operations for built-in types
reformed with
THIS FEATURE HAS BEEN DEFERRED
specify which properties should be found in the object
var p = new Point(1, 2); p.x = 1; p.y = 2; var x = 10; var y = 20; // Only find x in p with (p):{x: int} { trace(x + y) // 21 }
more numeric types
THIS FEATURE IS IN FLUX; LIKELY REMOVAL
ActionScript 3.0: int, uint, Number
ECMAScript 4: int, uint, byte, double (64-bit, like old Number), decimal (128-bit, 34 digits)
decimal context
THIS FEATURE HAS BEEN REMOVED
set rounding and precision options for a block of code
// 12 digits of precision, round to even ctx = new DecimalContext(12, "half_even") { use decimal ctx // "+" computed to 12 digits of precision, // rounding to even if necessary x = a + b }
'this' preservation
this
preserved for nested function calls
no need to store 'this' in a local variable
function f () { function g() { trace(this.x) } g(); // returns 'x' for f()'s 'this' } var v = { x: 10, f: f } v.f() // Displays: 10
cast operator
ActionScript 3.0: Apple(food)
ECMAScript 4: food cast Apple
'this function'
ActionScript 3.0: arguments.callee
ECMAScript 4: this function
'switch type'
switch type (v) { case (x: XML) { ... } case (s: String) { ... } // The "default" case case (o:*) { ... } }
cases don't "fall through"
array comprehensions
python inspired
convenience syntax for array initialization
assign values from an iterator to an array's elements
[expression loop condition]
function range(begin, end) { for (let i = begin; i < end; ++i) { yield i; } } var tenSquares = [i*i for (i in range(0, 10))]; var tenEvenSquares = [i*i for (i in range(0, 10)) if (i%2 == 0)];
// Create an array of Fibonacci numbers up to 1000 var fibTo1000 = [i for (i in new Fibber(1000))]
destructuring assignment
shortcut syntax for assignment
assign values from an array/object to a set of variables
e.g., assign variable values based on array elements:
var a; var b; // Assign 1 to a, and 2 to b [a, b] = [1, 2];
e.g., return multiple values from a function:
function getDetails():Array { return ["Colin", "Toronto"]; } // Assign "Colin" to name and "Toronto" to city [name, city] = getDetails();
e.g., assign variable values based on an object's variables:
var a; var b; var o = {name:"Colin", city:"Toronto", occupation:"Author"}; // Assign "Colin" to a and "Toronto" to b {name:a, city:b} = o;
numeric suffixes
THIS FEATURE IS IN FLUX
Suffixes on numeric literals denote their type:
-7i, 1u, 37d, 14.5m
i = int, u = uint, d = double, m = decimal
triple-quoted strings
THIS FEATURE HAS BEEN DEFERRED
can contain line breaks and unescaped quote characters
"""this string starts here it has a "blank" line before this one and ends here:"""
string indexing
// ActionScript 3.0, yields "e" "hello".charAt(1);
// ECMAScript 4.0, yields "e" "hello"[1]
slicing syntax
// ActionScript 3.0, yields "el" "hello".slice(1, 3);
// ECMAScript 4.0, yields "el" "hello"[1:3]
Vector class
high-performance, monotyped array
offers: fixed length (optional), no gaps, bounds checking
Map class
standard hash table
new global function: intrinsic::hashcode
item inspection/insertion/removal methods:
size():uint; get(key: K):V?; put(key:K, value:V):void; has(key:K):boolean; remove(key:K):boolean;
item iteration
get(deep:boolean = false):iterator::IteratorType.<K>; getKeys(deep:boolean = false):iterator::IteratorType.<K>; getValues(deep:boolean = false):iterator::IteratorType.<V> getItems(deep:boolean = false):iterator::IteratorType.<[K,V]>
reflection
typeOf(value)
returns a "meta-object" that describes value
example meta-object APIs:
interface ClassType extends NominalType { function construct(typeArgs: TypeIterator, valArgs: ValueIterator): Object; }
interface NominalType extends Type { function name(): String; function namespace(): String; function superTypes(): IteratorType.<ClassType>; function publicMembers(): FieldIterator; function publicStaticMembers(): FieldIterator; }
interface Type { function canConvertTo(t:Type): Boolean; function isSubtypeOf(t:Type): Boolean; }
check if a value
's type inherits from T:
typeOf(value).isSubtypeOf(T)
make a new instance of oldValue
's type:
var newValue = typeOf(oldValue).construct();
fixed variables on generic objects
use var
to make a non-deleteable instance variable on a generic object:
var order = {var quantity:1, var price:5.99, comment:"thanks!"); delete order.quantity // error in strict mode, otherwise no effect delete order.price // error in strict mode, otherwise no effect delete order.comment // ok, deletes the variable 'comment'
make all instance variables non-deleteable:
var order = var{quantity:1, price:5.99, comment:"thanks!");
const
instead of var
makes the variable read-only and non-deleteable
adds structural integrity to generic objects
should improve performance of generic objects