I also agree - when an object type "passes through" a method, it's usually best to use an invariant type variable.

On 11/02/2015 06:44 AM, Paul Sandoz wrote:
I agree with Maurizio, the first signature is good enough.

One could argue that it might be better to apply PECS since it would encourage 
more consistent usage when it is actually required as all too often it’s easy 
to forget, and then too late to change. However, i don’t want to encourage the 
use of BaseStream since it was an unfortunate mistake that this made public.

Paul.

On 2 Nov 2015, at 13:26, Maurizio Cimadamore <maurizio.cimadam...@oracle.com> 
wrote:

So, we have three potential signatures here:

<T> T walk(Function<Stream<StackWalker.StackFrame>, T> function) //1

<T> T walk(Function<Stream<StackWalker.StackFrame>, ? extends T> function) //2

<R extends T, T> T walk(Function<Stream<StackWalker.StackFrame>, R> function) 
//3


Under normal conditions (i.e. lambda parameter being passed to 'walk') I think 
all these signatures are fundamentally equivalent; (2) and (3) seem to have 
been designed for something like this:

Number n = walk(s -> new Integer(1));

That is, the function returns something that is more specific w.r.t. what is 
expected in the return type of walk. But - in reality, if 'walk' returns an R 
that is a subtype of T (as in the third signature), then walk also returns a T 
(as R is a subtype of T), so the result value can be passed where a T is 
expected.

The converse example:

Integer n1 = walk(s -> (Number)null);

Similarly fails on all three signatures.


More generally, with all such signatures, T will always receive:

* lower bound(s) (from the return value(s) of the lambda passed as a parameter 
to the 'walk' method)
* one upper bound (from the target-type associated with the 'walk' call.

Under such conditions, the upper bound will always be disregarded in favor of 
the lower bounds - meaning that instantiation of T will always be driven by 
what's inside the lambda. Signature (3) mentions different variables (R and T) 
but the end result is the same - as the bound says R extends T - meaning that 
lower bounds of R are propagated to T - leading to exactly the same situation.


In other words, I don't think there are obvious examples in which the behavior of 
these three signatures will be significantly different - if the return type of 'walk' 
would have been a generic type such as List<T>, the situation would have been 
completely different - with with a 'naked' T, I think the first signature is good 
enough, and the third is, in my opinion, making things harder than what they need to 
be.

I think the second signature is not necessary, from a pure type-system 
perspective; but I guess one could make an argument for it, but purely in terms 
of consistency with other areas (after all, the second type-parameter of a 
Function is in a covariant position).

I hope this helps.

Maurizio


--
- DML

Reply via email to