rfc:generics
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:generics [2015/04/28 15:12] – created dasprid | rfc:generics [2016/04/25 15:19] – mindplay | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Introduce generics into PHP ====== | + | ====== PHP RFC: Generic Types and Functions |
- | * Version: 0.1 | + | |
- | * Date: 2015-04-28 | + | * Version: 0.4.0 |
- | * Author: Ben Scholzen ' | + | * Date: 2016-01-06 |
- | * Status: Draft (or Under Discussion or Accepted or Declined) | + | * Author: Ben Scholzen ' |
+ | * Status: Draft | ||
* First Published at: http:// | * First Published at: http:// | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | Many languages, including hack by now, support generics, which for instance allow to create | + | |
+ | This RFC proposes the addition of generic types and functions to PHP. | ||
+ | |||
+ | Generics enable developers | ||
+ | |||
+ | Generic type relationships are already possible in PHP, as it is in most dynamic languages - but such type relationships presently cannot be declared or checked, and, at this time, can't even be documented, e.g. using php-doc tags. | ||
+ | |||
+ | Generics are an important step towards PHP maturing as a gradually-typed language - a direction | ||
+ | |||
+ | Note that generic versions of the standard (SPL) collection types in PHP is currently beyond the scope of this RFC. | ||
+ | |||
+ | Also note that, while the syntax proposed by this specification | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | All the features and examples of the proposal. | ||
- | To [[http:// | + | This RFC proposes |
- | for inclusion | + | |
- | Remember | + | The proposed syntax and feature set references various generic features of gradually-typed languages such as Dart and TypeScript, as well as statically-typed languages like C# and Java, in an attempt to create something |
+ | |||
+ | ==== Generic Types ==== | ||
+ | |||
+ | A type (class/ | ||
+ | |||
+ | A type parameter alias may optionally include an upper bound, e.g. a supertype of permitted type arguments for a given type parameter, which may be indicated by the use of the word '' | ||
+ | |||
+ | A type parameter may include a default type-hint, which will be applied in the absence of a given type-hint, indicated by an '' | ||
+ | |||
+ | Within the scope of a given generic type declaration, | ||
+ | |||
+ | The use of class-level type aliases in a static method declaration (or static method body) are not permitted, since there is no calling context ('' | ||
+ | |||
+ | The following demonstrates the proposed syntax for a generic class: | ||
+ | |||
+ | <code php> | ||
+ | class Entry< | ||
+ | { | ||
+ | protected $key; | ||
+ | protected $value; | ||
+ | |||
+ | public function __construct(KeyType $key, ValueType $value) | ||
+ | { | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public function getKey(): KeyType | ||
+ | { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function getValue(): ValueType | ||
+ | { | ||
+ | return $this-> | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note the use of type parameters in the class declaration '' | ||
+ | |||
+ | Also note the use of type aliases used for type-hinting the constructor arguments, and the return-types of the '' | ||
+ | |||
+ | An instance of a generic class can be constructed using explicit type arguments: | ||
+ | |||
+ | <code php> | ||
+ | $entry = new Entry< | ||
+ | </ | ||
+ | |||
+ | The type arguments, in this example, may also be inferred from the given arguments, rather than explicitly given: | ||
+ | |||
+ | <code php> | ||
+ | $entry = new Entry(1, ' | ||
+ | </ | ||
+ | |||
+ | In this case, the type of the given arguments are used to automatically complete the type-arguments - this is only possible because the constructor includes a type-hint for every type parameter, and because both constructor arguments are non-null values, from which a type can be inferred. | ||
+ | |||
+ | In either case, note that neither the type parameters, not the type argument names, are part of the class name: | ||
+ | |||
+ | <code php> | ||
+ | var_dump(get_class($entry)); | ||
+ | </ | ||
+ | |||
+ | Also note that this means you can't have both a class '' | ||
+ | |||
+ | === Extending a Generic Type === | ||
+ | |||
+ | A class that extends a generic class can choose to complete the type arguments: | ||
+ | |||
+ | <code php> | ||
+ | class StringEntry extends Entry< | ||
+ | {} | ||
+ | </ | ||
+ | |||
+ | This class is not itself generic (e.g. doesn' | ||
+ | |||
+ | === Type-hinting and Type-checking Generic Types === | ||
+ | |||
+ | Functions and methods are able to type-hint against specific variations of a given class - for example: | ||
+ | |||
+ | <code php> | ||
+ | function write(Entry< | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | |||
+ | write(new Entry< | ||
+ | write(new Entry< | ||
+ | </ | ||
+ | |||
+ | In the second example, the '' | ||
+ | |||
+ | === Nested Type Arguments === | ||
+ | |||
+ | Generic classes may be instantiated and generic functions/ | ||
+ | |||
+ | <code php> | ||
+ | class Container< | ||
+ | { | ||
+ | private $content; | ||
+ | |||
+ | public function getContent(): | ||
+ | { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function setContent(ContentType $content): void | ||
+ | { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $container = new Container< | ||
+ | |||
+ | $container-> | ||
+ | var_dump($container-> | ||
+ | |||
+ | $container-> | ||
+ | </ | ||
+ | |||
+ | In this example, the '' | ||
+ | |||
+ | In the second example, the '' | ||
+ | |||
+ | === Upper Bounds === | ||
+ | |||
+ | The kind of type argument that may be given for a type parameter, can be constrained by using the '' | ||
+ | |||
+ | <code php> | ||
+ | interface Boxable | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | |||
+ | class Box<T is Boxable> | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | |||
+ | class Hat implements Boxable | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | |||
+ | $box = new Box< | ||
+ | $box = new Box< | ||
+ | </ | ||
+ | |||
+ | In the second example, a '' | ||
+ | |||
+ | Any valid PHP type-hint may be used as an upper bound, including simple types like '' | ||
+ | |||
+ | Note that the choice of the keyword '' | ||
+ | |||
+ | == Bounds Checking == | ||
+ | |||
+ | Checking for upper bounds is consistent with type-checking in PHP - for example: | ||
+ | |||
+ | <code php> | ||
+ | interface Machine {} | ||
+ | |||
+ | class Computer implements Machine {} | ||
+ | |||
+ | class SuperComputer extends Computer {} | ||
+ | |||
+ | class MachineBuilder< | ||
+ | |||
+ | class ComputerBuilder< | ||
+ | |||
+ | class SuperComputerBuilder extends ComputerBuilder< | ||
+ | </ | ||
+ | |||
+ | In example (A) the '' | ||
+ | |||
+ | In example (B) the '' | ||
+ | |||
+ | In other words, bounds are checked according to the same type-checking rules as objects being checked with '' | ||
+ | |||
+ | === Traits === | ||
+ | |||
+ | Generic traits can be declared - for example: | ||
+ | |||
+ | <code php> | ||
+ | trait Factory< | ||
+ | { | ||
+ | public function make(F $flavor) : D { | ||
+ | return new D(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Maker | ||
+ | { | ||
+ | use Factory< | ||
+ | make as makeTea; | ||
+ | } | ||
+ | use Factory< | ||
+ | make as makeCoffee; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $maker = new Maker(); | ||
+ | $maker-> | ||
+ | $maker-> | ||
+ | </ | ||
+ | |||
+ | Type arguments must be supplied via the '' | ||
+ | |||
+ | Note that the type argument passed to a trait could itself be a type alias declared by the parent class - for example: | ||
+ | |||
+ | <code php> | ||
+ | trait Container< | ||
+ | { | ||
+ | private $value; | ||
+ | |||
+ | public function get() : T | ||
+ | { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function set(T $value) | ||
+ | { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Box< | ||
+ | { | ||
+ | use Container< | ||
+ | } | ||
+ | |||
+ | $box = new Box< | ||
+ | $box-> | ||
+ | var_dump($box-> | ||
+ | </ | ||
+ | |||
+ | ==== Generic Functions and Methods ==== | ||
+ | |||
+ | Generic functions require a type-argument for the invokation itself - for example: | ||
+ | |||
+ | <code php> | ||
+ | declare(strict_types=1) | ||
+ | |||
+ | class Box< | ||
+ | { | ||
+ | public function __construct(T $content) | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function create_box< | ||
+ | { | ||
+ | var_dump(func_type_args()); | ||
+ | |||
+ | return new Box< | ||
+ | } | ||
+ | |||
+ | $box = create_box(new Hat()); | ||
+ | $box = create_box< | ||
+ | </ | ||
+ | |||
+ | The first example is able to infer the type argument '' | ||
+ | |||
+ | The second example results in a '' | ||
+ | |||
+ | Note the addition of '' | ||
+ | |||
+ | === Generic Methods === | ||
+ | |||
+ | Generic methods are subject to the same rules and behavior as generic functions, see above. | ||
+ | |||
+ | When overridden, generic methods must have the same number of type parameters as that of their superclass, with the same upper bounds - for example: | ||
+ | |||
+ | <code php> | ||
+ | interface Greetable | ||
+ | { | ||
+ | public function getName(); | ||
+ | } | ||
+ | |||
+ | class Greeter | ||
+ | { | ||
+ | public function greet<T is Greetable> | ||
+ | { | ||
+ | $subject = $object-> | ||
+ | |||
+ | return " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class AdvancedGreeter extends Greeter | ||
+ | { | ||
+ | public function greet<T is Greetable> | ||
+ | { | ||
+ | return parent:: | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note that the type parameter alias '' | ||
+ | |||
+ | The same applies when overriding constructors and static methods. | ||
+ | |||
+ | ==== Generic Constructors ==== | ||
+ | |||
+ | Constructors may accept arbitrary type-arguments, | ||
+ | |||
+ | <code php> | ||
+ | class Hello< | ||
+ | { | ||
+ | public function __construct< | ||
+ | { | ||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | In other words, the constructor may accept more type-arguments than those affecting the type. | ||
+ | |||
+ | ==== Generic Closures ==== | ||
+ | |||
+ | TODO describe '' | ||
+ | |||
+ | ==== Type Checking ==== | ||
+ | |||
+ | Run-time type checks can be performed with the '' | ||
+ | |||
+ | <code php> | ||
+ | class Box< | ||
+ | |||
+ | class HeadGear {} | ||
+ | |||
+ | class Hat extends HeadGear {} | ||
+ | |||
+ | interface Feline {} | ||
+ | |||
+ | class Cat implements Feline {} | ||
+ | |||
+ | $hat_box = new Box< | ||
+ | $cat_box = new Box< | ||
+ | |||
+ | var_dump($hat_box instanceof Box); // => (bool) true | ||
+ | var_dump($cat_box instanceof Box); // => (bool) true | ||
+ | var_dump($cat_box instanceof Box< | ||
+ | var_dump($cat_box instanceof Box< | ||
+ | var_dump($cat_box instanceof Box< | ||
+ | var_dump($hat_box instanceof Box< | ||
+ | </ | ||
+ | |||
+ | Note that using an unbounded generic type-check works as expected - a type check against a generic type, without specifying type arguments, checks the base type but ignores the type arguments. | ||
+ | |||
+ | As demonstrated by the last two examples, type-checking also works on abstract types - that is, an instance of '' | ||
+ | |||
+ | === Bounded Polymorphism === | ||
+ | |||
+ | TODO: decide whether or not [[https:// | ||
+ | |||
+ | === Multiple Constraints === | ||
+ | |||
+ | TODO: decide whether or not multiple constraints should be supported, e.g. with a Java-like syntax: | ||
+ | |||
+ | <code php> | ||
+ | class A<T> where T is T1, T is T2 { | ||
+ | // ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This may relate to the [[https:// | ||
+ | |||
+ | ==== Autoloading ==== | ||
+ | |||
+ | When autoloading is triggered e.g. by a '' | ||
+ | |||
+ | In other words, a statement like '' | ||
+ | |||
+ | ==== Reflection ==== | ||
+ | |||
+ | Type parameters, as well as type arguments given for type parameters, are made available via reflection. | ||
+ | |||
+ | This RFC calls for the following changes and additions to the reflection API: | ||
+ | |||
+ | TODO (some [[https:// | ||
+ | |||
+ | === Reification === | ||
+ | |||
+ | Note that, because PHP is a reflective language, type arguments are fully [[https:// | ||
+ | |||
+ | This differs from generics in Hack, where type-hints are [[https:// | ||
+ | |||
+ | ==== Related Enhancements ==== | ||
+ | |||
+ | The following enhancements are somewhat beyond the scope of generics, but should be considered as part of this RFC. | ||
+ | |||
+ | == Static Type-checking With '' | ||
+ | |||
+ | Consider enhancing the '' | ||
+ | |||
+ | Also consider the addition of '' | ||
+ | |||
+ | These enhancements would seem natural and consistent with the addition of scalar type-hints in PHP 7, and implementation of the actual type-checks, | ||
+ | |||
+ | == New Pseudo-types == | ||
+ | |||
+ | Consider the introduction of a new pseudo-type '' | ||
+ | |||
+ | Introduction of these types would allow better use of the upper bounds feature, e.g. allowing one type to specify an upper bound of '' | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
+ | |||
No BC breaks are expected from this proposal. | No BC breaks are expected from this proposal. | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
+ | |||
This proposal aims for PHP 7.1. | This proposal aims for PHP 7.1. | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | Include these so readers know where you are heading and can discuss the proposed voting options. | ||
- | State whether | + | For this proposal to be accepted, |
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | Links to any external patches and tests go here. | ||
- | If there is no patch, | + | No patch has been written for this yet. As I'm not a C-coder myself, I encourage others to write a patch based on this proposal. |
+ | |||
+ | Some [[https:// | ||
+ | |||
+ | The same fork also contains some experimental parser enhancements written by Dominic Grostate. | ||
- | Make it clear if the patch is intended to be the final patch, or is just a prototype. | + | ===== Related RFCs ===== |
- | ===== Implementation ===== | + | Generic arrays |
- | After the project is implemented, | + | |
- | - the version(s) it was merged | + | |
- | - a link to the git commit(s) | + | |
- | | + | |
===== References ===== | ===== References ===== | ||
- | Links to external references, discussions or RFCs | ||
- | ===== Rejected Features ===== | + | https://en.wikipedia.org/ |
- | Keep this updated with features that were discussed on the mail lists. | + |
rfc/generics.txt · Last modified: 2018/06/02 17:33 by mindplay