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 >>