Thanks for the feedback, Paul and Maurizio, that’s very helpful.

I slightly opt for consistency with other areas and go with #2.

Mandy

> On Nov 2, 2015, at 4:44 AM, Paul Sandoz <paul.san...@oracle.com> 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
>> 

Reply via email to