Hello,
I've written up an RFC for supporting generic classes and methods in
PHP, and I'd love to hear your thoughts about it.
https://wiki.php.net/rfc/generics
Cheers,
Ben Scholzen 'DASPRiD'
Community Review Team Member | mail@dasprids.de
Zend Framework | http://www.dasprids.de
On Mon, Aug 31, 2015 at 10:31 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:
Hello,
I've written up an RFC for supporting generic classes and methods in PHP,
and I'd love to hear your thoughts about it.https://wiki.php.net/rfc/generics
Cheers,
Ben Scholzen 'DASPRiD'
Community Review Team Member | mail@dasprids.de
Zend Framework | http://www.dasprids.de--
Hello,
I would love to see generics in PHP, but I have a few questions for your RFC.
class Bazz<Foo : \Bar>
Why did you use this syntax for forcing extending/implementation? I
know this is the syntax C# uses, but it doesn't fit PHP. I think it
should be "extends" or "implements", because in PHP : has a different
meaning.
Also, how do you specify multiple constraints?
Do you have any idea how it would work internally and if there was
some performance hit while using generics, and if it would also slow
down existing apps that don't use generics?
I hope this RFC will have positive comments, and someone who is
capable of writing C will help you out with it so there's a patch for
people to review - because I'm not sure if this RFC can get anywhere
without a patch. :(
Regards
Pavel Kouril
On Mon, Aug 31, 2015 at 10:31 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:Hello,
I've written up an RFC for supporting generic classes and methods in PHP,
and I'd love to hear your thoughts about it.https://wiki.php.net/rfc/generics
Cheers,
Ben Scholzen 'DASPRiD'
Community Review Team Member | mail@dasprids.de
Zend Framework | http://www.dasprids.de--
Hello,
I would love to see generics in PHP, but I have a few questions for your
RFC.class Bazz<Foo : \Bar>
Why did you use this syntax for forcing extending/implementation? I
know this is the syntax C# uses, but it doesn't fit PHP. I think it
should be "extends" or "implements", because in PHP : has a different
meaning.Also, how do you specify multiple constraints?
Do you have any idea how it would work internally and if there was
some performance hit while using generics, and if it would also slow
down existing apps that don't use generics?I hope this RFC will have positive comments, and someone who is
capable of writing C will help you out with it so there's a patch for
people to review - because I'm not sure if this RFC can get anywhere
without a patch. :(Regards
Pavel Kouril--
Hi Ben,
Now that I have substantive questions, including the list :)
In the following:
class SomeList<Bar> {
public static function fromArray<Baz>(array $a): SomeList<Baz> {
$list = new SomeList<Baz>();
$list2 = new SomeList<Bar>();
return $list;
}
}
$list = SomeList<int>::fromArray([1, 2]);
When fromArray() executes, what type is $list2? or is that an error? If
it's an error, what advantage is there to having distinct generic types on
the class vs. the method? How do I specify the two generic types I want to
use?
Another thing I'd like to make sure I understand, how does this interact
with strict types (and non-strict)?
new Entry<int, string>(1, 2);//I assume it should type-juggle
declare(strict_types = 1);
new Entry<int, string>(1, 2);//I assume it should throw error
I'm interested to see if PHP can get generics going. I hope hashing out
some more details might garner more interest.
Thanks,
John
Hi,
Hi Ben,
Now that I have substantive questions, including the list :)
In the following:
class SomeList<Bar> {
public static function fromArray<Baz>(array $a): SomeList<Baz> {
$list = new SomeList<Baz>();
$list2 = new SomeList<Bar>();
return $list;
}
}$list = SomeList<int>::fromArray([1, 2]);
When fromArray() executes, what type is $list2? or is that an error? If
it's an error, what advantage is there to having distinct generic types
on the class vs. the method? How do I specify the two generic types I
want to use?
The $list2 line would definitely be an error, since the <Bar> hint is
not known in the static context. The advantage is simply that you can
pass a type through the a static method to create an object with a
specific type for you.
Another thing I'd like to make sure I understand, how does this interact
with strict types (and non-strict)?new Entry<int, string>(1, 2);//I assume it should type-juggle
declare(strict_types = 1);
new Entry<int, string>(1, 2);//I assume it should throw error
I didn't think too much about this, but I guess you are right that for
scalar types, the strict_type declaration should apply in the user context.
I'm interested to see if PHP can get generics going. I hope hashing out
some more details might garner more interest.Thanks,
John
--
Ben Scholzen
http://www.dasprids.de
Hi,
I would love to see generics in PHP, but I have a few questions for your RFC.
class Bazz<Foo : \Bar>
Why did you use this syntax for forcing extending/implementation? I
know this is the syntax C# uses, but it doesn't fit PHP. I think it
should be "extends" or "implements", because in PHP : has a different
meaning.
I've used a different syntax before (namely "extends" and "implements"),
but someone pointed out, that a keyword-less version would make more
sense, so I updated it appropriately. If someone has a better
suggestion, I'm happy to include it in the RFC.
Also, how do you specify multiple constraints?
I didn't plan on covering multiple constraints here, as we don't handle
them at all in PHP yet. Although I've read that someone wanted to have
Union-types in PHP, which could be used here as well I suppose.
Do you have any idea how it would work internally and if there was
some performance hit while using generics, and if it would also slow
down existing apps that don't use generics?
I don't have enough insights in the PHP guts to make a good suggestion
about this, but I'd assume that it should not have a negative
performance effect on code which doesn't use generics.
I hope this RFC will have positive comments, and someone who is
capable of writing C will help you out with it so there's a patch for
people to review - because I'm not sure if this RFC can get anywhere
without a patch. :(Regards
Pavel Kouril
--
Ben Scholzen
http://www.dasprids.de
Hi Ben!
Ben Scholzen 'DASPRiD' wrote:
Hello,
I've written up an RFC for supporting generic classes and methods in
PHP, and I'd love to hear your thoughts about it.https://wiki.php.net/rfc/generics
Cheers,
Generics are a feature I'd love to see in PHP. I think this RFC could do
with a little elaboration, though.
Something that's particularly important to me is if it be possible to
have default values for type parameters? For example, could I make a
class like this?
<?php
// note the =, a default value (like function parameters)
class List<T=mixed>
{
private $array = [];
// ...
public function append(T $value) {
$this->array[] = $value;
}
}
$integerList = new List<int>; // only accepts integers
$list = new List; // accepts any value
Here, 'mixed' means any type, like in the PHP manual or in Hack. We
don't currently have a 'mixed' typehint since we don't need it (you can
simply omit a typehint for a parameter or return type), but it would be
useful here.
I'd like it if this was possible, because then you could make existing
classes use generics without breaking compatibility. It's also more
beginner-friendly: people who don't know about generics don't have to
use them, if the class author has made this possible. This is in keeping
with the "PHP way" where we don't require people to use type hints if
they don't want them.
On a different note, I'm not sure I like the class Baz<Foo : \Bar>
syntax. For functions parameters, we put the type name before the
variable name, e.g. function foo(int $bar)
. So, could we do the same
here, i.e. class <\Bar Foo>
, for consistency?
Though that looks a little strange to me. It's two bare identifiers next
to eachother (\Bar Foo
), unlike function parameters where the second
one has a dollar prefix (\Bar $foo
). Perhaps type variables, like
regular variables, should have some sort of sigil in PHP? This would
make it more obvious that something is a type variable and not a class
name. To take an example from your RFC, consider the following code snippet:
class Entry<KeyType, ValueType>
{
// ...
public function __construct(KeyType $key, ValueType $value)
{
// ...
}
// ...
}
If we hadn't seen the class declaration at the top which makes KeyType
and ValueType
be type parameters, we might not realise they weren't
ordinary classes or interfaces when we looked at __construct
. The
-Type suffix you've used helps a little, but that's a convention, and
isn't guaranteed to be used. Also, in some existing projects, there
might be classes with that suffix.
What if we use some sort of sigil here to make it clear? Say we used %:
class Entry<%KeyType, %ValueType>
{
// ...
public function __construct(%KeyType $key, %ValueType $value)
{
// ...
}
// ...
}
Now, without having to look at the class declaration, we can tell that
KeyType
isn't a class name, because it has % in front of it.
Anyway, all that being said: the big challenge with generics is really
getting someone to do the nitty-gritty of implementing it. It's great
that you've got an RFC written for this, but unless someone is willing
to implement it, I fear it may go to waste. But you writing an RFC might
inspire someone. There is hope!
Hope my thoughts are somewhat helpful.
--
Andrea Faulds
http://ajf.me/
Andrea Faulds wrote on 06/09/2015 17:20:
If we hadn't seen the class declaration at the top which makes
KeyType
andValueType
be type parameters, we might not realise
they weren't ordinary classes or interfaces when we looked at
__construct
. The -Type suffix you've used helps a little, but that's
a convention, and isn't guaranteed to be used. Also, in some existing
projects, there might be classes with that suffix.
As I understand it, the convention in a lot of languages is that type
parameters are a single upper-case letter. I'm not sure if any enforce
this restriction, though, and a sigil does seem more flexible and readable.
Regards,
Rowan Collins
[IMSoP]
Hi Ben,
Am 31.08.2015 um 22:31 schrieb Ben Scholzen 'DASPRiD':
I've written up an RFC for supporting generic classes and methods in
PHP, and I'd love to hear your thoughts about it.
I have some points where I think some clarification is required before
someone can start to implement it:
- static methods:
class Foo<T> {
public static function test() {
}
}
how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()
in case b), would the generic methods not be a duplication?
- Generic methods
a) Can generic methods be inside classes that themselves are not generic?
class Foo<T1> {
public static function test<T2>() {
$a = new self<T1>();
}
}
b) Is the generic parameter type of T1 set inside the static method or
only T2? And if T1 is available, why is T2 required? What is the
advantage of having T1 and T2?
- Class names
a) Is it possible to have two classes with the same name (in the same
namespace) but one that requires type parameters and one that does not?
b) Is it possible to allow for a generic class to be constructed without
type parameters (e.g. with mixed as the default)?
- Reflection
Can you get the type parameters used to construct an object at runtime? How?
- Implementation
How do you propose to implement all this? In Java (at least in older
versions, may have changed), generics where a pure compile time
function. So the compiler checked that your code did not validate the
type but you could not access the type parameter information it at
runtime. That is not possible for PHP. So either we need classes that
can be parameterized inside the PHP core or we need "template" classes
that are compiled into concrete implementations using the type
parameters once they are used first. Have you thought about which
variant you prefer and why (or any other I did forget)? Is this even
possible without digging deep inside the language?
Thanks for your time.
Greets
Dennis
Hi Dennis,
thanks for your feedback, see my answers below:
- static methods:
class Foo<T> {
public static function test() {
}
}how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()in case b), would the generic methods not be a duplication?
Case a) is correct here. A static method is not in the context of an
instance, so it doesn't know about the class boxing by itself.
- Generic methods
a) Can generic methods be inside classes that themselves are not generic?
Sure, that's perfectly valid.
class Foo<T1> {
public static function test<T2>() {
$a = new self<T1>();
}
}b) Is the generic parameter type of T1 set inside the static method or
only T2? And if T1 is available, why is T2 required? What is the
advantage of having T1 and T2?
T1 is not available in the static method, as said before, the static
method is not part of an instance. Furthermore, line 3 should read like
this:
$a = new self<T2>();
- Class names
a) Is it possible to have two classes with the same name (in the same
namespace) but one that requires type parameters and one that does not?
That is in fact an implementation detail, but I'd tend to say no. That
is similar to function overloading, which we don't have in PHP (sadly).
b) Is it possible to allow for a generic class to be constructed without
type parameters (e.g. with mixed as the default)?
As I wrote in an earlier email, that would totally possible. My idea
there was, that, as long as the types don't have restrictions (to extend
another class or implement an interface), it should be allowed to
construct it without a type so that it accepts any parameters.
- Reflection
Can you get the type parameters used to construct an object at runtime? How?
That's in fact an implementation detail I can't say anything about, sorry.
- Implementation
How do you propose to implement all this? In Java (at least in older
versions, may have changed), generics where a pure compile time
function. So the compiler checked that your code did not validate the
type but you could not access the type parameter information it at
runtime. That is not possible for PHP. So either we need classes that
can be parameterized inside the PHP core or we need "template" classes
that are compiled into concrete implementations using the type
parameters once they are used first. Have you thought about which
variant you prefer and why (or any other I did forget)? Is this even
possible without digging deep inside the language?
I haven't thought about a concrete implementation, simply because I'm
not familiar enough with this part of the PHP core, and I'd leave it up
to someone else to suggest the best way to implement it. But the idea of
template classes sounds like a solution I'd probably chose if I didn't
know better… which I don't :)
Again, thanks for your feedback!
Ben Scholzen 'DASPRiD'
Community Review Team Member | mail@dasprids.de
Zend Framework | http://www.dasprids.de
On Thu, Sep 10, 2015 at 5:12 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:
Hi Dennis,
thanks for your feedback, see my answers below:
- static methods:
class Foo<T> {
public static function test() {
}
}how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()in case b), would the generic methods not be a duplication?
Case a) is correct here. A static method is not in the context of an
instance, so it doesn't know about the class boxing by itself.
I think b would be correct. Can you explain this rationale a bit more?
Levi,
It's my understanding that the "type variable" is a member of the instance
and not of the class. In at least java, generics are unavailable in static
context[1] though they can be specifically defined on the static method
itself[2].
[1]
http://stackoverflow.com/questions/936377/static-method-in-a-generic-class
[2] https://docs.oracle.com/javase/tutorial/extra/generics/methods.html
On Thu, Sep 10, 2015 at 5:12 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:Hi Dennis,
thanks for your feedback, see my answers below:
- static methods:
class Foo<T> {
public static function test() {
}
}how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()in case b), would the generic methods not be a duplication?
Case a) is correct here. A static method is not in the context of an
instance, so it doesn't know about the class boxing by itself.I think b would be correct. Can you explain this rationale a bit more?
Levi,
It's my understanding that the "type variable" is a member of the instance
and not of the class. In at least java, generics are unavailable in static
context[1] though they can be specifically defined on the static method
itself[2].[1]
http://stackoverflow.com/questions/936377/static-method-in-a-generic-class
[2] https://docs.oracle.com/javase/tutorial/extra/generics/methods.htmlOn Thu, Sep 10, 2015 at 5:12 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:Hi Dennis,
thanks for your feedback, see my answers below:
- static methods:
class Foo<T> {
public static function test() {
}
}how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()in case b), would the generic methods not be a duplication?
Case a) is correct here. A static method is not in the context of an
instance, so it doesn't know about the class boxing by itself.I think b would be correct. Can you explain this rationale a bit more?
This may be how it works in Java, but this is not Java. All behavior
must be outlined and justified, especially because not all languages
behave the same as Java with regards to generic-like behavior. Notably
C++ does not share this characteristic. If this generic ability is
confined only to classes/interfaces and only to instance methods that
is a large limitation. Even if that is what is decided there must be
rationale for choosing it to be that way.
Well, with the example, there's a good reason to have them separated:
class Factory
{
public static function createFoo<T extends \SomeClass>()
{
return new Foo<T>();
}
public static function createBar<T extends \SomeOtherClass>()
{
return new Bar<T>();
}
}
class Foo<T extends \SomeClass> {}
class Bar<T extends \SomeOtherClass> {}
The factory instance does not know to need about <T>, this is purely a
type for the method to be able to create another class, which requires
that type. You couldn't put this definition on the Factory class.
Levi,
It's my understanding that the "type variable" is a member of the instance
and not of the class. In at least java, generics are unavailable in static
context[1] though they can be specifically defined on the static method
itself[2].[1]
http://stackoverflow.com/questions/936377/static-method-in-a-generic-class
[2] https://docs.oracle.com/javase/tutorial/extra/generics/methods.htmlOn Thu, Sep 10, 2015 at 5:12 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:Hi Dennis,
thanks for your feedback, see my answers below:
- static methods:
class Foo<T> {
public static function test() {
}
}how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()in case b), would the generic methods not be a duplication?
Case a) is correct here. A static method is not in the context of an
instance, so it doesn't know about the class boxing by itself.I think b would be correct. Can you explain this rationale a bit more?
This may be how it works in Java, but this is not Java. All behavior
must be outlined and justified, especially because not all languages
behave the same as Java with regards to generic-like behavior. Notably
C++ does not share this characteristic. If this generic ability is
confined only to classes/interfaces and only to instance methods that
is a large limitation. Even if that is what is decided there must be
rationale for choosing it to be that way.
--
Ben Scholzen
http://www.dasprids.de
(Please stop top-posting!)
Well, with the example, there's a good reason to have them separated:
class Factory
{
public static function createFoo<T extends \SomeClass>()
{
return new Foo<T>();
}public static function createBar<T extends \SomeOtherClass>()
{
return new Bar<T>();
}
}class Foo<T extends \SomeClass> {}
class Bar<T extends \SomeOtherClass> {}The factory instance does not know to need about <T>, this is purely a type
for the method to be able to create another class, which requires that type.
You couldn't put this definition on the Factory class.
What would prohibit this?
class Factory<T> {
public static function makeFoo() {
return new Foo<T>();
}
}
It's perhaps less generic than what you posted, but this demonstrates
that that a Factory could hold the definition on the class and not
the method.
To be honest, I don't know why you would bother doing this anyway. I
am a fan of realistic examples and Factories that make Foos of type T
is pretty unrealistic.
What would prohibit this?
class Factory<T> { public static function makeFoo() { return new Foo<T>(); } }
It's perhaps less generic than what you posted, but this demonstrates
that that a Factory could hold the definition on the class and not
the method.To be honest, I don't know why you would bother doing this anyway. I
am a fan of realistic examples and Factories that make Foos of type T
is pretty unrealistic.
Well, the counter-example would be that without generic methods, thus
automatically applying the class instance types to static method, you
would enforce that the user has to define them for static method calls
which don't require a type at all (helper methods for instance).
--
Ben Scholzen
http://www.dasprids.de
Hey,
Levi Morrison wrote:
On Thu, Sep 10, 2015 at 5:12 PM, Ben Scholzen 'DASPRiD'
mail@dasprids.de wrote:Hi Dennis,
thanks for your feedback, see my answers below:
- static methods:
class Foo<T> {
public static function test() {
}
}how can I call the method:
a) Foo::test()
or
b) Foo<int>::test()in case b), would the generic methods not be a duplication?
Case a) is correct here. A static method is not in the context of an
instance, so it doesn't know about the class boxing by itself.I think b would be correct. Can you explain this rationale a bit more?
I'm with Levi here, I think type parameters should matter for static
methods. An example might be a list class:
class List<T> {
// ...
public static function newFromConcat(List<T> $a, List<T> $b) {
// creates a new list which is two lists concatenated
}
// ...
}
In this context, the static method, which operates on instances of list,
would benefit from type checking.
PHP's dynamic nature means that it wouldn't be the end of the world if
static methods couldn't use the type parameter, though. You'd just lose
proper type checking.
Thanks.
--
Andrea Faulds
http://ajf.me/
I'm with Levi here, I think type parameters should matter for static
methods. An example might be a list class:
Just because you want it, doesn't make it possible ;-)
The class type variables are specified when instantiating the class. Since
you don't do that for static methods, the runtime has no information about
which type was specified at the time the static method is called.
- Stig
Hi together,
- If we require some keyword instead of the colon to limit which types
the type placeholders can have I would choose:
class Foo<T1 instanceof Bla, T2 instanceof Blubb> {}
The C#-like syntax "where T1 extends Bla implements Blubb" (proposed in
another thread) has two downsides:
a) It will not fit nicely with generic methods
b) "extends Bla" implies you can not use Bla itself but you actually
want a behavior that exactly matches "instanceof"
But I am not really happy with listing multiple classes/interfaces after
"instanceof" (the RFC does also not address this): separating it by
space seems odd, by comma will not work (you can not distinguish it from
type placeholders without an "instanceof" requirement) or you have to
use semicolon to separate the type placeholders and list multiple
"instanceof" requirements by comma.
- I think mixing class and method generic specification together is
quite confusing:
class A {}
class B {}
class List<T : A> {
public static createFromElement<T : B>(T $element) {
return new OtherList<T>($element);
}
}
class OtherList<T extends B> { ... }
$list = List<A>::createFromElement(new A());
The example is not very meaningful but I think it sometimes may get very
hard to see why the static method does not accept an A if the class
does. If only non-generic classes could contain generic static methods
that would make it more obvious.
Maybe we should ignore generic methods for the moment and cleanly
specify how generic classes work, including the question if static
methods can be called with class::method() on generic classes and if the
type parameters can be used inside static methods if the call is
class<...>::method(). If someone is willing and able to implement this,
we will see whether generic methods are needed in real live at all.
Greets
Dennis
P.s.: sorry for top-replying again but my last thread got too specific
for this reply IMHO
Am 12.09.2015 um 12:03 schrieb Dennis Birkholz:
Hi together,
- If we require some keyword instead of the colon to limit which types
the type placeholders can have I would choose:
class Foo<T1 instanceof Bla, T2 instanceof Blubb> {}
Just to save you some lookups in the Hack documentation:
-
Hack uses the "as" keyword:
class Foo<T1 as Bla> {} -
It does not allow to require multiple types for T1:
"Currently, Hack only allows one constraint on a type or a method." -
I found no example that Hack allows multiple type parameters like
class Foo<T1, T2> but also nothing that it is not possible -
All type parameters must start with a capital T
-
Hack has generic methods
-
Hack has generic traits which we should also consider/specify how
they work exactly.
Greets
Dennis
- All type parameters must start with a capital T
It's important to note that the HHVM runtime does not require this (or
at least it didn't when I last checked it out). It's only the Hack
type-checker that cares.
Hi Dennis,
Dennis Birkholz wrote:
Am 12.09.2015 um 12:03 schrieb Dennis Birkholz:
Hi together,
- If we require some keyword instead of the colon to limit which types
the type placeholders can have I would choose:
class Foo<T1 instanceof Bla, T2 instanceof Blubb> {}Just to save you some lookups in the Hack documentation:
- Hack uses the "as" keyword:
class Foo<T1 as Bla> {}
This seems like a poor syntax choice to me. Usually the word 'as' is
used for aliases and such. If you hadn't told me its function here, I
would probably have been very confused. 'T1' isn't an alias of Bla,
right? It's some subclass.
Thanks.
Andrea Faulds
http://ajf.me/