rfc:generics

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:generics [2016/01/07 19:05] mindplayrfc:generics [2018/06/02 17:33] (current) – add github link mindplay
Line 1: Line 1:
 ====== PHP RFC: Generic Types and Functions ====== ====== PHP RFC: Generic Types and Functions ======
  
-  * Version: 0.3+  * Version: 0.4.0
   * Date: 2016-01-06   * Date: 2016-01-06
   * Author: Ben Scholzen 'DASPRiD' <mail@dasprids.de>, Rasmus Schultz <rasmus@mindplay.dk>   * Author: Ben Scholzen 'DASPRiD' <mail@dasprids.de>, Rasmus Schultz <rasmus@mindplay.dk>
-  * Status: Under Discussion+  * Status: Draft
   * First Published at: http://wiki.php.net/rfc/generics   * First Published at: http://wiki.php.net/rfc/generics
 +
 +**NOTE:** a newer version of this RFC may be under development [[https://github.com/mindplay-dk/php-generics-rfc|on GitHub]].
  
 ===== Introduction ===== ===== Introduction =====
  
-This RCF proposes the addition of generic types and functions to PHP.+This RFC proposes the addition of generic types and functions to PHP.
  
-Generics enable developers to create a whole family of declarations using a single generic declaration - for example, a generic collection-type declaration ''List<T>'' induces a declaration for any entity-type ''T'', which negates the need to implement a dedicated collection-type for every entity-type, reducing the need for boilerplate code and duplication.+Generics enable developers to create a whole family of declarations using a single generic declaration - for example, a generic collection-type declaration ''Collection<T>'' induces a declaration for any entity-type ''T'', which negates the need to implement a dedicated collection-type for every entity-type, reducing the need for boilerplate code and duplication.
  
 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. 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.
Line 35: Line 37:
 A type parameter may include a default type-hint, which will be applied in the absence of a given type-hint, indicated by an ''='' sign folled by a type-hint, at the end of the type parameter. A type parameter may include a default type-hint, which will be applied in the absence of a given type-hint, indicated by an ''='' sign folled by a type-hint, at the end of the type parameter.
  
-Within the scope of a given generic type declaration, a type alias may be used in place of an actual type hint, in any instance method declaration or expression body - this includes instance method return-types, and various common expressions in the body of an instance method, including the ''new'' statement and references to static members. (The use of type aliases in a static method declaration or static method body are not permitted, since there is no `$this`, and therefore no way to establish the type arguments at run-time.)+Within the scope of a given generic type declaration, a type alias may be used in place of an actual type hint, in any instance method declaration or expression body - this includes instance method return-types, and various common expressions in the body of an instance method, including the ''new'' statement and references to static members. 
 + 
 +The use of class-level type aliases in a static method declaration (or static method bodyare not permitted, since there is no calling context (''$this''and therefore no way to resolve the type aliases. (Type arguments in static methods are allowed, and function the same as type arguments in functions.)
  
 The following demonstrates the proposed syntax for a generic class: The following demonstrates the proposed syntax for a generic class:
Line 111: Line 115:
  
 write(new Entry<int, string>(1, 'test')); write(new Entry<int, string>(1, 'test'));
-write(new Entry<int, int>(1, 2)); // throws an EngineException+write(new Entry<int, int>(1, 2)); // throws a TypeError
 </code> </code>
  
-In the second example, the ''ValueType'' is incorrect, which results in an ''EngineException''.+In the second example, the ''ValueType'' is incorrect, which results in ''TypeError''
 + 
 +=== Nested Type Arguments === 
 + 
 +Generic classes may be instantiated and generic functions/methods may be called with nested type arguments. 
 + 
 +<code php> 
 +class Container<ContentType> 
 +
 +    private $content; 
 +     
 +    public function getContent(): ContentType 
 +    { 
 +        return $this->content; 
 +    } 
 +     
 +    public function setContent(ContentType $content): void 
 +    { 
 +        $this->content = $content; 
 +    } 
 +
 + 
 +$container = new Container<Entry<int,string>>(); 
 + 
 +$container->setContent(new Entry<int,string>(1, 'test')); 
 +var_dump($container->getContent() instanceof Entry<int,string>); // => (bool) true 
 + 
 +$container->setContent(new Entry<int,int>(1, 1)); // throws a TypeError 
 +</code> 
 + 
 +In this example, the ''ContentType'' has retained the nested type arguments within ''Entry<KeyType, ValueType>'' The responsibility of type checking arguments to ''Entry'' still belong to the ''Entry'' class, yet the type hierarchy is maintained all the way up to the root definition ''Container<Entry<int,string>>''
 + 
 +In the second example, the ''ValueType'' is incorrect just as before, which again results in a ''TypeError''.
  
 === Upper Bounds === === Upper Bounds ===
Line 137: Line 173:
  
 $box = new Box<Hat>(); // ok $box = new Box<Hat>(); // ok
-$box = new Box<string>(); // throws an EngineException+$box = new Box<string>(); // throws 
 </code> </code>
  
-In the second example, an ''EngineException'' is thrown, because ''string'' is not a sub-type of ''Boxable''.+In the second example, ''TypeError'' is thrown, because ''string'' is not a sub-type of ''Boxable''.
  
 Any valid PHP type-hint may be used as an upper bound, including simple types like ''int'', ''float'', ''bool'', ''string'' and ''object''. (Omission of an upper bound effectively means ''mixed'' in general PHP terms, though we are not proposing the ability to explicitly type-hint as ''mixed'', which isn't supported by PHP.) Any valid PHP type-hint may be used as an upper bound, including simple types like ''int'', ''float'', ''bool'', ''string'' and ''object''. (Omission of an upper bound effectively means ''mixed'' in general PHP terms, though we are not proposing the ability to explicitly type-hint as ''mixed'', which isn't supported by PHP.)
  
-Note that the choice of the keyword ''is'' to indicate upper bounds is based on the rejection of perhaps more obvious alternatives - repurposing the ''extends'' or ''implements'' keywords would be misleading, since they would work precisely the same way; worse, permitting both keywords would render consumer code invalid if an upper bound type provided by a library is refactored between class and interface. Repurposing ''instanceof'' would also be misleading, since the upper bound is checking the type-hint, not an instance.+Note that the choice of the keyword ''is'' to indicate upper bounds is based on the rejection of perhaps more obvious alternatives - repurposing the ''extends'' or ''implements'' keywords would be misleading, since they would work precisely the same way; worse, permitting both keywords would render consumer code invalid if an upper bound type provided by a library is refactored between class and interface. Repurposing ''instanceof'' would also be misleading, since the upper bound is checking the type-hint, not an instance. Furthermore, we don't want this to collide with possible future mixed scalar types, such as ''number'' or ''scalar'', neither of which make sense in conjunction with either ''extends'' or ''implements''. (If a reserved ''is'' keyword is undesirable for other reasons, a simple '':'' is likely a better alternative than overloading the meaning of an existing keyword.)
  
 == Bounds Checking == == Bounds Checking ==
Line 219: Line 255:
 class Box<T> class Box<T>
 { {
-    use Box<T>;+    use Container<T>;
 } }
  
Line 250: Line 286:
  
 $box = create_box(new Hat()); $box = create_box(new Hat());
-$box = create_box<string>(new Hat()); // throws EngineException+$box = create_box<string>(new Hat()); // throws TypeError
 </code> </code>
  
 The first example is able to infer the type argument ''T'' as ''Hat'', because the type alias was used to type-hint the argument given for the ''$content'' parameter. The first example is able to infer the type argument ''T'' as ''Hat'', because the type alias was used to type-hint the argument given for the ''$content'' parameter.
  
-The second example results in an ''EngineException'', because the type parameter ''T'' was explicitly defined as ''string''. (Note that, if we had not used ''declare(strict_types=1)'', and if ''Box'' had implemented ''__toString()'', this would have been acceptable, due to the default behavior of weak scalar type-checking.)+The second example results in ''TypeError'', because the type parameter ''T'' was explicitly defined as ''string''. (Note that, if we had not used ''declare(strict_types=1)'', and if ''Box'' had implemented ''<nowiki>__toString()</nowiki>'', this would have been acceptable, due to the default behavior of weak scalar type-checking.)
  
-Note the addition of ''func_type_args()'', which returns a list of type-hints pertaining to the current calling context. This complements ''func_get_args()'' by providing the list of type-arguments as fully-qualified class-names.+Note the addition of ''func_type_args()'', which returns a list of type-hints pertaining to the current generic function call or constructor invocation. This complements ''func_get_args()'' by providing the list of type-arguments as fully-qualified class-names.
  
 === Generic Methods === === Generic Methods ===
Line 293: Line 329:
  
 The same applies when overriding constructors and static methods. The same applies when overriding constructors and static methods.
 +
 +==== Generic Constructors ====
 +
 +Constructors may accept arbitrary type-arguments, just like any other method, e.g.:
 +
 +<code php>
 +class Hello<T1>
 +{
 +    public function __construct<T1,T2>()
 +    {
 +        // ...
 +    }
 +}
 +</code>
 +
 +In other words, the constructor may accept more type-arguments than those affecting the type.
 +
 +==== Generic Closures ====
 +
 +TODO describe ''callable<T, ...>'' type-hints and/or generic ''Closure<T, ...>'' and/or ''Function<T, ...>'' types
  
 ==== Type Checking ==== ==== Type Checking ====
Line 324: Line 380:
 As demonstrated by the last two examples, type-checking also works on abstract types - that is, an instance of ''Box<Cat>'' is an instance of ''Box<Feline>'' because ''Cat'' implements ''Feline''; and likewise, ''Box<Hat>'' is an instance of ''Box<HeadGear>'' because ''Hat'' extends ''HeadGear''. (Consistent with the inability to type-check against traits, trait-names cannot be used as part of a type-check.) As demonstrated by the last two examples, type-checking also works on abstract types - that is, an instance of ''Box<Cat>'' is an instance of ''Box<Feline>'' because ''Cat'' implements ''Feline''; and likewise, ''Box<Hat>'' is an instance of ''Box<HeadGear>'' because ''Hat'' extends ''HeadGear''. (Consistent with the inability to type-check against traits, trait-names cannot be used as part of a type-check.)
  
-==== Autoloading ====+=== Bounded Polymorphism ===
  
-When autoloading is triggered e.gby a ''new'' statement with a generic type, autoloading is triggered as normal, with only the class-name (without type parameters) being supplied.+TODO: decide whether or not [[https://en.wikipedia.org/wiki/Bounded_quantification|bounded polymorphism]] should be supported.
  
-In other words, a statement like ''new Map<int,string>()'' will trigger auto-loading of class ''Map''.+=== Multiple Constraints ===
  
-==== Generic Arrays ==== +TODOdecide whether or not multiple constraints should be supported, e.gwith a Java-like syntax:
- +
-The built-in array type comes with two generic counterparts - one with a value type, and one with key and value types. +
- +
-The ''array'' keyword is overloaded, such that the following will work as expected: +
- +
-<code> +
-$counts = array<string, int>(); // array<TKey,TValue> +
-$counts["kittens"] = 12; +
- +
-$versions = array<float>(); // array<TValue> +
-$versions["php"] = 7.1; +
-</code> +
- +
-Violating the key or value type of a type-checked array will trigger an ''EngineException''+
- +
-The actual type-checks or type-conversions performed will depend on the ''strict_mode'' flag and normal type conversion rules. +
- +
-Note that there is no generic form of the short array syntax - generic arrays can only be created explicitly by using the ''array'' keyword. +
- +
-=== Array Type-casting === +
- +
-Generic arrays can be explicitly [[http://php.net/manual/en/language.types.array.php#language.types.array.casting|type-cast]] using the following syntax:+
  
 <code php> <code php>
-$array = [1,2,3]; +class A<T> where T is T1T is T2 { 
- +    // ... 
-$numbers = (array<int>) $array;+}
 </code> </code>
  
-In this example, the array is fully copied, and every element is type-checkedbecause ''int'' is a less general element type than ''mixed''.+This may relate to the [[https://wiki.php.net/rfc/union_types|union types RFC]] if implementedit may be more natural to expect support for union types as bounds.
  
-Casting to a less general index or element type than that of the source array, results in creation of a new array, and keys/elements being copied. If an index or element in the source array is incompatible with the index or element types of the created generic array, an ''EngineException'' is triggered.+==== Autoloading ====
  
-If an array is cast to more general (or identical) index and element type, a lazy reference to the source array is madeidentical to how PHP arrays normally work internally.+When autoloading is triggered e.g. by ''new'' statement with a generic type, autoloading is triggered as normalwith only the class-name (without type parameters) being supplied.
  
-The following results in an implicit type-cast: +In other words, a statement like ''new Map<int,string>()'' will trigger auto-loading of class ''Map''.
- +
-<code php> +
-function tally(array<int> $numbers) { +
-    // ... +
-+
- +
-tally([7,8,9])+
-</code> +
- +
-In this case, the implicit type-cast is successful. In a case where the conversion fails, an ''EngineException'' is thrown.+
  
 ==== Reflection ==== ==== Reflection ====
Line 384: Line 408:
 This RFC calls for the following changes and additions to the reflection API: This RFC calls for the following changes and additions to the reflection API:
  
-TODO+TODO (some [[https://gist.github.com/mindplay-dk/dc3d24eba8d13a650cc6|notes]] with ideas are available.)
  
 === Reification === === Reification ===
Line 425: Line 449:
  
 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://github.com/orolyn/php-src/tree/generics-tests/Zend/tests/generics|preliminary tests]] have been written for most key concepts and behaviors. Most notably, at this time, tests for reflection API enhancements are still missing.
 +
 +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://wiki.php.net/rfc/generic-arrays|Generic arrays RFC]].
  
 ===== References ===== ===== References =====
  
 https://en.wikipedia.org/wiki/Generic_programming https://en.wikipedia.org/wiki/Generic_programming
rfc/generics.1452193517.txt.gz · Last modified: 2017/09/22 13:28 (external edit)