rfc:generics
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:generics [2015/08/31 19:58] – 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.2 | + | |
- | * Date: 2015-08-29 | + | * Version: 0.4.0 |
- | * Author: Ben Scholzen ' | + | * Date: 2016-01-06 |
+ | * Author: Ben Scholzen ' | ||
* Status: Draft | * 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 and typehint for specialized collections without the need to implement such a collection for every single entity. This RFC proposes the introduction of generics into PHP. Adding generic support to existing collections, | ||
- | Generics offer a great way of avoiding duplicate code, where many classes only differ in the accepted variable | + | This RFC proposes |
+ | |||
+ | Generics enable developers to create a whole family of declarations using a single generic declaration - for example, | ||
+ | |||
+ | 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 in which PHP is already moving, with the addition of scalar type-hints, and return type-hints, in PHP 7. Generics will provide stronger type-safety, | ||
+ | |||
+ | Note that generic | ||
+ | |||
+ | Also note that, while the syntax proposed by this specification may be similar to that of Hack/HHVM, compatibility with Hack is not a stated objective. | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | A class is considered generic when it has one or more types defined. The | + | |
- | defined types can be used in both method | + | This RFC proposes the addition of generic classes, interfaces and traits - all of which will be referred to as generic " |
- | hinting. | + | |
+ | 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 that looks and feels familiar to those with generics experience from other languages. | ||
+ | |||
+ | ==== 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 | ||
+ | |||
+ | The following demonstrates the proposed syntax for a generic class: | ||
<code php> | <code php> | ||
Line 38: | Line 63: | ||
} | } | ||
} | } | ||
+ | </ | ||
- | $entry = new Entry<int, string>(1, 'test'); // Valid | + | Note the use of type parameters in the class declaration '' |
- | $entry = new Entry(1, ' | + | |
+ | 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< | ||
</ | </ | ||
- | Functions and methods are able to type hint against specific variations of a | + | The type arguments, in this example, may also be inferred from the given arguments, rather than explicitly given: |
- | given class: | + | |
+ | <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> | <code php> | ||
- | function | + | function |
{ | { | ||
- | // … | + | // ... |
} | } | ||
- | foo(new Entry< | + | write(new Entry< |
- | foo(new Entry< | + | write(new Entry< |
</ | </ | ||
- | A generic class can also force a type to extend a specific class or implement | + | In the second example, the '' |
- | a specific interface: | + | |
+ | === Nested Type Arguments === | ||
+ | |||
+ | Generic classes may be instantiated and generic functions/ | ||
<code php> | <code php> | ||
- | class ClassA<Foo : \Bar> | + | class Container<ContentType> |
{ | { | ||
+ | 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 ===== | ||
+ | |||
For this proposal to be accepted, a 2/3 majority is required. | For this proposal to be accepted, a 2/3 majority is required. | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
+ | |||
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. | 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. | ||
+ | |||
+ | ===== Related RFCs ===== | ||
+ | |||
+ | Generic arrays (and related functions) were previously part of this RFC, but have been moved to a dedicated [[https:// | ||
===== References ===== | ===== References ===== | ||
+ | |||
https:// | https:// |
rfc/generics.txt · Last modified: 2018/06/02 17:33 by mindplay