Skip to content

Array access / iteration / type boxing / generics #106

@thekid

Description

@thekid

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])).

<?php
  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.

<?php
  // 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:

<?php
  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:

<?php
  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:

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

The following is the workaround:

<?php
  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:

<?php
  // 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.
  • xp-framework/rfc Add lang.types.String #101 - The lang.types.String class will be needed for
    boxing to work.

Related documents

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions