Array access / iteration / type boxing / generics

  Status: implemented,  Sun Apr 29 19:14:56 2007
  • Created: 2007-01-08 18:35:00+0100
  • Categories: lang.types, util.collections
  • Author: friebe

Scope of Change

The lang.types.ArrayList class and the relevant classes from util.collections will be changed to support array access (read/write) and foreach() iteration. The classes from the collections API will furthermore support type boxing and provide generic variants of themselves. The text.String class will be deprecated and replaced by lang.types.String and lang.types.Character.


Rationale

Easier / intuitive interface to "array" types.

Functionality



Class overview

* lang.types.ArrayList is an immutable, zero-indexed list of values of any 
  type and most closely resembles an array in Java.
 
* util.collections.Vector is a resizable array of objects.
 
* util.collections.HashTable is a map where both keys and values are 
  objects.

Array access

The ArrayAccess interface requires implementing the following methods: offsetGet(), offsetSet(), offsetExists() and offsetUnset(). These overload getting ($value= $object[$key]), setting ($object[$key]= $value)), testing (isset($object[$key])) and removing (unset($object[$key])).

  with ($a= new ArrayList(1, 2, 3));  {

// Element access
$one= $a[0]; // $one contains "1"
$a[1]+= 1; // The second list entry (2) will be increased
$a[2]= 4; // The third list entry (3) will be set to 4

// ArrayList is immutable in size!
$a[-1]= 1; // Throws an IndexOutOfBoundsException
$a[3]= 1; // - " -
$err= $a[4]; // - " -
$a[]= 1; // - " -
unset($a[0]); // Throws an IllegalArgumentException

// Keys are integers only!
$a['foo']= 1; // Throws an IllegalArgumentException
$f= $a['foo']; // - " -

// Testing
isset($a[0]); // TRUE
isset($a[-1]); // FALSE
isset($a[3]); // FALSE
}

with
($v= new Vector()); {

// The add() method and its overloaded equivalent
$v->add(new String('hello'));
$v[]= new String('hello');

// The get() method and its overloaded equivalent
$hello= $v->get(0);
$hello= $v[0];

// The set() method and its overloaded equivalent
$v->set(0, new String('world'));
$v[0]= new String('world');

// The remove() method and its overloaded equivalent
$v->remove(0);
unset
($v[0]);
}

with
($h= new HashTable()); {

// The put() method and its overloaded equivalent
$h->put(new String('stage-url'), new URL('xp://localhost:6448'));
$h[new String('stage-url')]= new URL('xp://localhost:6448');

// Primitives in keys are "boxed"
$h->put('stage-url', new URL('xp://localhost:6448'));
$h['stage-url']= new URL('xp://localhost:6448');

// The get() method and its overloaded equivalent
$url= $h->get(new String('stage-url'));
$url= $h[new String('stage-url')];

// Primitives in keys are "boxed"
$url= $h->get('stage-url');
$url= $h['stage-url'];
}

with
($s= new HashSet()); {

// The add() method and its overloaded equivalent
$s->add(new String('X'));
$s[]= new String('X');

// The contains() method and its overloaded equivalent
$s->contains(new String('X'));
isset
($s[new String('X')]);

// The remove() method and its overloaded equivalent
$s->remove(new String('X'));
unset
($s[new String('X')]);
}



Iteration

The IteratorAggregate interface requires implementing a method called getIterator() which must return an Iterator instance. This makes a class usable inside the foreach statement.

  // ArrayList foreach-iteration, prints "1 2 3 "
foreach (new ArrayList(1, 2, 3) as $value) {
echo
$value, ' ';
}

// Vector foreach-iteration, prints "1 2 "
foreach (new Vector(array(new String('1'), new String('2'))) as $value) {
echo
$value, ' ';
}

// HashSet foreach-iteration, prints "1 2 "
$s= new HashSet();
$s->addAll(array(new String('1'), new String('2');
foreach ($s as $value) {
echo
$value, ' ';
}

// HashTable foreach-iteration, prints "development production "
$h= new HashTable();
$h['development']= new URL('xp://localhost:6448');
$h['production']= new URL('xp://jboss.example.com:6448');
foreach ($h->keys() as $key) {
echo
$key, ' ';
}



Generics

To be able to create generics with the "new" keyword, we would need to extend PHP's syntax or change constructor signatures. Both of those have obvious downsides.

Therefore, a new core function "create" is introduced. Here's its prototype:

  lang.Generic create(mixed arg) throws IllegalArgumentException

To create a generic object, the argument passed to create() must be a string of the form ClassType "<" ComponentType [ ", " ComponentType ] ">". Class names referenced by ClassType and ComponentType may be qualified or not and need to refer to loaded XP classes or interfaces.

Examples:

  with ($hash= create('HashTable<String, String>')); {
$hash['key']= new String('value');
$hash['key']= new Integer(1); // Throws an IllegalArgumentException
$hash[1]= new String('value'); // - " -

$value= $hash['key'];
$value= $hash[1]; // Throws an IllegalArgumentException
}

with
($func= create('util.collections.HashTable<String, lang.Runnable>')); {
$func['hello']= newinstance('lang.Runnable', array(), '{
public function run() {
Console::writeLine("Hello");
}
}'
);
$func['hello']->run();
}

with
($vector= create('Vector<lang.types.Number>')); {
$vector[]= new Integer(1);
$vector[]= new Float(1.0);
$vector[]= new Long(6100);
}

with
($routines= create('HashSet<lang.XPClass>')); {
$routines[]= XPClass::forName('lang.reflect.Method');
$routines[]= XPClass::forName('lang.reflect.Constructor');
}



Supporting generics in classes

To add generics support to a class, a public member named "__generic" must be introduced. In all methods, the generic types must be verified manually.

Example:

  class SortedList extends Object {
public
$__generic = array();

public function add($element) {
if ($this->__generic && !$element instanceof $this->__generic[0]) {
throw new IllegalArgumentException(
'Element '.xp::stringOf($element).' must be of '.$this->__generic[0]
);
}

// ...
}
}

This poses a higher burden when programming (compared to one being able to express this syntactically) but does not introduce a great performance penalty like http://experiments.xp-framework.net/?people,friebe,generics does (transforms syntactically defined generic declarations to the notation seen above when a class is loaded).

Additional note: create() overloaded

The create() functionality provides an overloaded version of itself: When passed a lang.Generic instance, the instance is simply returned.

This serves the purpose of chaining after "new", which is not supported by the PHP language:

  echo new Date()->toString();  // Parse error

The following is the workaround:

  echo create(new Date())->toString();

Additional note: ArrayList vs. Vector

The distinction between those two is that ArrayList is a thin, immutable wrapper type without utility functions such as indexOf() or contains(), whereas the Vector class provides a full "list" API.

Additional note: ArrayList constructor

lang.types.ArrayList's constructor will be changed to accept varargs instead of an array. The classes that use lang.types.ArrayList will be adapted to this change.

To be able to create an ArrayList from an array or from a known size, a new method "newInstance" will be added. Usage as follows:

  // Constructing an arraylist from an array
$a= ArrayList::newInstance(array(1, 2, 3, 4));
$four= $a->length;

// Constructing an empty arraylist
$a= ArrayList::newInstance(4);
$four= $a->length;



Security considerations

n/a

Speed impact

Slightly slower (more methods, extra implementation checks)

Dependencies

- A new interface util.collections.IList will be introduced. An implementation will exists in form of the new class util.collections.Vector.

- RFC #0101 - The lang.types.String class will be needed for boxing to work.

Related documents

- http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html Zend-Engine internal interface ArrayAccess

- http://www.php.net/~helly/php/ext/spl/interfaceIteratorAggregate.html Zend-Engine internal interface IteratorAggregate

- http://xp-framework.net/rfc/contrib/rfc0106-remote.diff Patch for remote API (apply in xp/trunk)

- http://experiments.xp-framework.net/?arena,arrayaccess Experiment and unittests

- http://java.sun.com/docs/books/tutorial/java/generics/ Generics lesson

- http://msdn2.microsoft.com/en-us/library/ms379564(VS.80).aspx An Introduction to C# Generics

Comments

- friebe, Mon Jan 8 19:09:55 2007 The ArrayAccess and IteratorAggregate interfaces are part of the Zend Engine, not of SPL!

- friebe, Tue Apr 24 18:06:11 2007 Maybe we can add declarative generic syntax as seen in this experiment: http://experiments.xp-framework.net/?people,friebe,generics later on; it can then be decided whether to live with the performance penalty or to if a compilation step should be introduced (which would be a step into the direction of RFC #0052 and the XP language, see also http://experiments.xp-framework.net/?people,friebe,jay)

- friebe, Tue Apr 24 19:30:20 2007 http://www.php-compiler.net/doku.php?id=core%3aphalanger_for_.net_developers Section 3.3: PHP/CLR language extensions notes Phalanger uses: ClassType "<:" ComponentType [ "," ComponentType ] ":>" for generic syntax (note the extra ":"), for example: $d= new Dictionary<:string, string:>();

<EOF>


Table of contents