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/02/13 19:14] mindplayrfc:generics [2018/06/02 17:33] (current) – add github link mindplay
Line 4: Line 4:
   * 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 115: Line 119:
  
 In the second example, the ''ValueType'' is incorrect, which results in a ''TypeError''. In the second example, the ''ValueType'' is incorrect, which results in a ''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 144: Line 180:
 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 255: Line 291:
 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 a ''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 ''__toString()'', this would have been acceptable, due to the default behavior of weak scalar type-checking.)+The second example results in a ''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 generic function call or constructor invocation. 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.
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 ==== ==== Generic Closures ====
  
-TODO describe ''callable<T, ...>'' type-hints and generic ''Closure<T, ...>'' types+TODO describe ''callable<T, ...>'' type-hints and/or generic ''Closure<T, ...>'' and/or ''Function<T, ...>'' types
  
 ==== Type Checking ==== ==== Type Checking ====
Line 331: Line 383:
  
 TODO: decide whether or not [[https://en.wikipedia.org/wiki/Bounded_quantification|bounded polymorphism]] should be supported. TODO: decide whether or not [[https://en.wikipedia.org/wiki/Bounded_quantification|bounded polymorphism]] should be supported.
 +
 +=== 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 {
 +    // ...
 +}
 +</code>
 +
 +This may relate to the [[https://wiki.php.net/rfc/union_types|union types RFC]] - if implemented, it may be more natural to expect support for union types as bounds.
  
 ==== Autoloading ==== ==== Autoloading ====
Line 344: 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 367: Line 431:
  
 Consider the introduction of a new pseudo-type ''scalar'' as a super-type of ''int'', ''float'', ''string'', ''bool'' and ''float'', as well as the introduction of a pseudo-type ''number'' as a super-type of ''int'' and ''float''. Consider the introduction of a new pseudo-type ''scalar'' as a super-type of ''int'', ''float'', ''string'', ''bool'' and ''float'', as well as the introduction of a pseudo-type ''number'' as a super-type of ''int'' and ''float''.
- 
-Also consider the introduction of a new pseudo-type ''void'' to explicitly declare functions, and function-types, that do not return any value. Most importantly, this is necessary for generic callable type-hints, e.g. ''callable<int, void>'' which takes an ''int'' argument and returns nothing. Also, explicit void return-types, as in ''function foo() : void'', could be useful e.g. in interfaces. 
  
 Introduction of these types would allow better use of the upper bounds feature, e.g. allowing one type to specify an upper bound of ''scalar'', and a sub-type to specify the kind of scalar 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 ''scalar'', and a sub-type to specify the kind of scalar type.
Line 387: 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 ===== ===== Related RFCs =====
rfc/generics.1455390857.txt.gz · Last modified: 2017/09/22 13:28 (external edit)