xp-framework/rfc

Generics optimization

thekid opened this issue · 1 comments

Scope of Change

Generics will be optimized.

Rationale

  • Improve performance
  • Enable reflection for generics
  • Enable reflective creation of generics
  • Support primitives a generic arguments

Functionality

Currently, generics are implemented as follows:

  • A: A __generic instance member is required inside the declaration.
    It will hold an array of the component types.
  • B: The methods working with generics are required to take care of
    the type checks.
  • C: The create() core functionality takes care of instantiating the
    generics, populating the __generic member with the given types'
    names.

Example (abbreviated):

<?php
  class Vector extends Object {
    public $__generic;                                  // A

    public function add($value) {
      if (!$value instanceof $this->__generic[0]) {     // B
        throw new IllegalArgumentException('...');
      }
      // ...
    }
  }

  $v= create('new Vector<lang.types.String>()');        // C
?>

This has the following downsides:

  • The declaration is quite verbose and introduces a bunch of boilerplate
    code for the manual component type verification.
  • At runtime, two generics, e.g. a vector of strings and one integers,
    are "instanceof"-compatible
  • There is no way to type-hint a generic, verifying a vector's component
    type is string would mean manually accessing its __generic member.

Plan

A generic instance should be created at runtime named with unique name
created of the base and component types. A class has two names, one that
is reported by XPClass::getName() (F) and one used literally (L):

<?php
  // Creates a class named: 
  // F: "util.collections.Vector`1[lang.types.String]"
  // L: "Vector··String"
  $ve= create('new Vector<lang.types.String>()');

  // Creates a class named: 
  // F: "util.collections.HashTable`2[lang.types.String,lang.Generic]"
  // L: "HashTable··String¸Generic"
  $ht= create('new HashTable<lang.types.String, lang.Generic>()');

  // Creates a class named: 
  // F: "util.collections.Stack`1[string]"
  // L: "Stack··þstring"
  $st= create('new Stack<string>()');
?>

The same generation process happens for all generic interfaces these
classes implement and the generic base classes they extend.

The middle dot (·, Alt-Gr + "." in Cygwin) is used in the XP Framework
for generated classes and in fully qualified names (see xp-framework/rfc #37). The
cedil sign (¸, Alt-Gr + "´" in Cygwin) is used to separate the components.

Declaration

To declare a generic class using the XP framework, we will resort to
annotations:

<?php
  #[@generic(self= 'K, V', interfaces= array('Map' => 'K, V')))]
  class HashTable extends Object implements Map {

    #[@generic(params= 'K, V')]
    public function put($key, $value) { ... }

    #[@generic(params= 'K')]    
    public function get($key) { ... }

    #[@generic(return= 'V[]')]
    public function values() { ... }

    public function toString() { ... }
  }
?>

In XP language, this needn't be done as it syntactically supports generics:

<?php
  public class HashTable<K, V> implements Map<K, V> {

    public void put(K $key, V $value) { ... }

    public V get(K $key) { ... }

    public V[] values() { ... }

    public string toString() { ... }
  }
?>

Instantiation

To instantiate generics, the create() core functionality needs
to be used. The inner workings are as follows:

  1. Parse string specifying type into class and parameters
  2. Compose unique name
  3. If this class exists, instantiate and return
  4. For all interfaces, perform generation
  5. Generate class extending lang.Object
  6. For all methods generate delegation
  7. Instantiate and return

What we will end up with is the following:

<?php
  interface Map··String¸Object {
    public function put(String $key, Object $value);
    public function get(String $key);
    public function values();
  }

  class HashTable··String¸Object extends Object implements Map··String¸Object {
    private $delegate;

    public function __construct() {
      $this->delegate= new HashTable();
    }

    public function put(String $key, Object $value) {
      $this->delegate->put($key, $value);
    }

    // ...
  }
?>

Reflection

Whether a type is a generic instance should be determinable at runtime:

<?php
  // Will display: 
  //   Arguments: [
  //     0 => lang.XPClass<lang.types.String>
  //   ]
  $class= create('new Vector<String>()')->getClass();
  if ($class->isGeneric()) {
    Console::writeLine('Arguments: ', $class->genericArguments());
  }
?>

Reflection on generic definitions should also be possible:

<?php
  // Will display:
  //   Components: [
  //      0 => "T"
  //   ]
  $class= XPClass::forName('util.collections.Vector');
  if ($class->isGenericDefinition()) {
    Console::writeLine('Components: ', $class->genericComponents());
  }
?>

Also, reflective creation of generic types should be possible:

<?php
  $class= XPClass::forName('util.collections.Vector');
  $generic= $class->newGenericType(array(XPClass::forName('lang.types.String')));

  // ...and then use newInstance() methods
  $vector= $generic->newInstance();
?>

Discovering the definition type:

<?php
  // Will display:
  //   Definition: lang.XPClass<util.collections.Vector>
  $class= create('new Vector<String>()')->getClass();
  if ($class->isGeneric()) {
    Console::writeLine('Definition: ', $class->genericDefinition());
  }
?>

Type restrictions

The generic types can now be used in parameter type hints, although
somewhat ugly:

<?php
  public function dump(List··String $l) { ... }
?>

Security considerations

n/a

Speed impact

Faster.

Dependencies

xp-framework/rfc #197

Related documents

The $__generic-style generics (BC with < 5.8) has been removed in XP 6 (see RFC #172)