Proposed API for JEP 259: Stack-Walking API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Nov 5 22:34:11 UTC 2015



On 04/11/15 11:19, Timo Kinnunen wrote:
> Hi,
>
> I tested your version of the wildcard counter and it appears to be incompatible with one possible signature of the StackWalker.walk method.
>
> Here’s my code. Please see the line with ERROR comment:
>
> 	static class StackWalker {
> 		static class StackFrame implements CharSequence {
> 			// ...
> 		}
> 		<T> T walk(Function<? super Stream<StackFrame>, ? extends T> function, IntUnaryOperator sizing) {
> 			return function.apply(Stream.of("one " + sizing.applyAsInt(1), "two" + sizing.applyAsInt(2)).map(StackFrame::new));
> 		}
> 		<T> T powerWalk(Function<Stream<StackFrame>, T> function, IntUnaryOperator sizing) {
> 			return function.apply(Stream.of("one " + sizing.applyAsInt(1), "two" + sizing.applyAsInt(2)).map(StackFrame::new));
> 		}
> 	}
>
> 	static Function<Stream<?>, Long> wildCounter() {
> 		return Stream::count;
> 	}
> 	static <T> Function<Stream<T>, Long> tameCounter() {
> 		return Stream::count;
> 	}
> 	public static void main(String[] args) {
> 		Function<Stream<StackWalker.StackFrame>, Long> tameCounter = tameCounter();
> 		Function<Stream<?>, Long> wildCounter = wildCounter();
> 		Long counted1 = new StackWalker().walk(tameCounter, i -> i);
> 		Long counted2 = new StackWalker().walk(wildCounter, i -> i);
> 		Long counted3 = new StackWalker().powerWalk(tameCounter, i -> i);
> 		Long counted4 = new StackWalker().powerWalk(wildCounter, i -> i); // ERROR
> 		System.out.println(counted1 + counted2 + counted3 + counted4);
> 	}
>
> This may be a problem with javac, but it doesn’t exactly inspire confidence in me towards wildcards. More like it’s another example in a long-running pattern of bad behavior. The version by Paul Sandoz compiles without issues, no surprises there as usual.
>
> And this is what javac is telling me:
>
> Information:Using javac 1.8.0_65 to compile java sources
> Error:(77, 50) java: method powerWalk in class nonnulls.GenericTest.StackWalker cannot be applied to given types;
>    required: java.util.function.Function<java.util.stream.Stream<nonnulls.GenericTest.StackWalker.StackFrame>,T>,java.util.function.IntUnaryOperator
>    found: java.util.function.Function<java.util.stream.Stream<?>,java.lang.Long>,(i)->i
>    reason: cannot infer type-variable(s) T
>      (argument mismatch; java.util.function.Function<java.util.stream.Stream<?>,java.lang.Long> cannot be converted to java.util.function.Function<java.util.stream.Stream<nonnulls.GenericTest.StackWalker.StackFrame>,T>)
Right - you are passing a Function<Stream<?>, Long> where a 
Function<Stream<StackFrame>, T> is expected - regardless of what happens 
to the inference variable T, for this to be valid, it must be that 
Stream<?> == Stream<StackFrame>, which is obviously not the case. 
There's no doubt that '? super' is needed in the argument position for 
Function - as to the '? extends' in the return type I think it's 
irrelevant - i.e. this:

<T> T powerWalk(Function<? super Stream<StackFrame>, T> function, IntUnaryOperator sizing) {


and

<T> T powerWalk(Function<? super Stream<StackFrame>, ? extends T> function, IntUnaryOperator sizing) {


Should result in having same applicability.

Maurizio
>
>
>
> Sent from Mail for Windows 10
>
>
>
> From: Remi Forax
> Sent: Wednesday, November 4, 2015 10:04
> To: Paul Sandoz
> Cc: core-libs-dev at openjdk.java.net
> Subject: Re: Proposed API for JEP 259: Stack-Walking API
>
>
> Hi Paul,
>
> The use of BaseStream was just an example, here is another one that works only if the function first parameter type is declared as '? super Stream<StackWalker.StackFrame>'.
>
> static Function<Stream<?>, Integer> counter() {
>    return stream::count;
> }
>
> ...
> StackWalker walker = ...
> int count = walker.walk(counter());
>
> regards,
> Rémi
>
> ----- Mail original -----
>> De: "Paul Sandoz" <paul.sandoz at oracle.com>
>> Cc: core-libs-dev at openjdk.java.net
>> Envoyé: Lundi 2 Novembre 2015 13:44:24
>> Objet: Re: Proposed API for JEP 259: Stack-Walking API
>>
>> 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.cimadamore at 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
>>>
>




More information about the core-libs-dev mailing list