Re: [RFC:generics]
| From: | Dominic Grostate | Date: | Fri, 29 Apr 2016 15:45:08 +0000 |
| Subject: | Re: [RFC:generics] | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-92940@lists.php.net to get a copy of this message | ||
It's not insane, it's liskov substitution. But that is an insane example,
typical of Java to create a type that extends it's parent by adding value
constraints.. directly violating the principle.
On 29 Apr 2016 4:22 p.m., "Rowan Collins" <rowan.collins@gmail.com> wrote:
guilhermeblanco@gmail.com wrote on 29/04/2016 15:26:
> You can add subtypes of A to a List<A> in Java. What List<? extends A>
>> >means is that the list itself may be a list of any type, provided that
>> type
>> >is compatible with A. So if B extends A, List<B> is compatible with
>> List<?
>> >extends A>, and when reading items you can assume they will be compatible
>> >with A (since B extends A) but you can't add an A (because it's actually
>> a
>> >list of Bs).
>> >
>> >
>>
> Wrong. This is documented here
>
> https://docs.oracle.com/javase/tutorial/java/generics/upperBounded.html
> and
> specifically states:
>
> To write the method that works on lists of Number and the subtypes of
> Number,
>
>> >such as Integer, Double, and Float, you would specify List<? extends
>> >Number>. The term List<Number> is more restrictive than List<? extends
>> >Number> because the former matches a list of type Number only, whereas
>> >the latter matches a list of type Number or any of its subclasses.
>>
>
Forgive me for butting in if I'm completely wrong, but is the confusion
here between *declaring an instance* and *declaring a parameter constraint*?
I think the point is that you can declare variables as follows:
List<Integer> li; // only accepts Integer
List<Double> ld; // only accepts Double
List<Number> ln; // accepts any Number, even if it is in fact an Integer or
Double instance
But if you then define a function accepting a parameter:
public static double sumOfList(List<Number> list)
Now your argument has to be of type List<Number>; you can pass "ln" above,
but not "li" or "ld". To relax the function's contract, you can write this
instead:
public static double sumOfList(List<? extends Number> list)
Now the argument can be a List<Integer> or List<Double>, so "li" and
"ld"
are legal arguments.
The reason the default case is to be invariant, rather than covariant, is
explained on the blog post Jesse linked to:
http://hhvm.com/blog/9215/covariance-contravariance-and-super-type-constraints
In a nutshell, consider this function:
function addPiToList(List<Number> $list): void
{
$list->add(3.1415); // for the sake of example, assume 3.1415 instanceOf
Double
}
If we allow covariance, then we can run addPiToList(new List<Integer>) and
the type conditions are met; clearly this is not sane, and is going to
result in an error somewhere.
So we need to be stricter:
function addPiToList(List<Double> $list): void
But the actual contract we want here is "any list we can legally add a
Double to", and if we get a List<Number> or a List<*>, it would work fine,
so our check is too strict. What's needed is a way to declare our function
as contravariant:
function addPiToList(List<? super Double> $list): void
Now we can pass in a List<Double> or a List<Number>, but not a
List<Integer>, and our contract holds nicely.
It took me a while to get my head around, but I think the above makes
sense...
Regards,
--
Rowan Collins
[IMSoP]