Proposed API for JEP 259: Stack-Walking API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Nov 2 12:26:18 UTC 2015


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

On 01/11/15 10:11, Timo Kinnunen wrote:
> I’m not quite sure I follow you, so my apologies if I misunderstood you. What I’m trying to point out is that changing the type from “T” to “? extends T”  doesn’t add any expressiveness in this method. These two signatures are both equally expressive for users of this API:
>
> 	public <T> T walk(Function<Stream<StackWalker.StackFrame>, T> function)
> 	public <T> T walk(Function<Stream<StackWalker.StackFrame>, ? extends T> function)
>
> Maybe this is easier to see if we give a name to our wildcard type:
>
> 	public <T> T walk(Function<Stream<StackWalker.StackFrame>, ? extends T> function)
> 	public <R extends T, T> T walk(Function<Stream<StackWalker.StackFrame>, R> function)
>
> Clearly these two are equivalent, we just now have a real name for “? extends T” is, it is “R”. For a user who is calling this method, what is gained by having it return the potentially less specific type “T” that could be some super type of “R” and not have it just return the type “R”? The implementation of walk doesn’t need them, it can be done without bringing in extra types from R’s type the hierarchy.
>
> I don’t think there is any gain. For all types <A,B,C> where A extends B, B extends C, the method taking A and giving B to the user storing it to C gives a transformation A→B→C. For this case we have two transformation corresponding to the two alternative methods:
>
> In “<R> R walk(Function<Stream<StackWalker.StackFrame>, R> function)”, 	R→R→T is available, because R extends T so R can be stored in T.
> In “<R extends T, T> T walk(Function<Stream<StackWalker.StackFrame>, R> f)”, 	R→T→T is available, because well, T is a T.
>
> Changing a single type parameter in an API from “T” to “? extends T” in a wrong place can have a dramatic effect on the quality of type inference results that the users receive when using the API. The affects functional programming style much, much more than it does a more procedural style. So I want to make it clear now that for most users a single additional wildcard in the API is not a disaster. For people doing more functional programming though, the effects can feel punishing as they have to add more casts, type arguments or extra local variables to make more types explicit --- which type inference would have otherwise inferred --- had the wildcard not been present.
>
> This is why I think it’s prudent that the expected benefits of adding any one new wildcard are stated clearly and in practical terms. These benefits can then be evaluated against the drawbacks, and an informed decision can be made. Which is why I would like to ask:
>
> - What are the expected benefits that arise from using a wildcard type in this method rather than a non-wildcard type, and
> - How will this manifest in practice in the code (i.e. what is one supposed to look for) to verify that the actual results match what we expected?
>
>
> TIA!
>
>
>
>
>
>
>
> Sent from Mail for Windows 10
>
>
>
> From: Mandy Chung
> Sent: Sunday, November 1, 2015 05:36
> To: Timo Kinnunen
> Cc: Remi Forax;core-libs-dev at openjdk.java.net
> Subject: Re: Proposed API for JEP 259: Stack-Walking API
>
>
> In fact, <T> T walk(Function<Stream<StackFrame>, T> function, …) - without ? extends T change,
>
> You can do:
>      Function<Stream<StackWalker.StackFrame>, String> funct;
>      CharSequence chars =  sw.walk(funct, i -> i);
>
> I think it’s right to declare the return type of the function is "? extends T”.
>
> Mandy
>
>
>> On Oct 31, 2015, at 5:01 PM, Timo Kinnunen <timo.kinnunen at gmail.com> wrote:
>>
>> Hi,
>>   
>> Regarding “
>> I was pondering it and that’s why not changed in the last update.  I agree with the upper bounded wildcard "? extends T” for the return type of the function.
>>>>   
>> How does changing the type from “T” to “? extends T” aid the method caller in any way? If the caller has a Function like this:
>>   
>>                  Function<Stream<StackWalker.StackFrame>, String> funct;
>>   
>> but they wished they could have walk use T=CharSequence instead, then they can simply assign the value returned from walk to a CharSequence variable:
>>   
>>                  String result = sw.walk(funct, i -> i);
>>                  CharSequence chars = result;
>>   
>>   
>> Isn’t “? extends T” pointless here?
>>   
>>   
>>   
>>   
>>   
>> Sent from Mail for Windows 10
>>   
>>   
>>
>> From: Mandy Chung
>> Sent: Saturday, October 31, 2015 23:59
>> To: Remi Forax
>> Cc: core-libs-dev at openjdk.java.net
>> Subject: Re: Proposed API for JEP 259: Stack-Walking API
>>   
>>   
>>   
>>> On Oct 31, 2015, at 11:29 AM, Remi Forax <forax at univ-mlv.fr> wrote:
>>>
>>> Hi Mandy,
>>> I've crawled the code and the documentation again.
>>>
>>> In the doc and in the code, a lambda with one parameter doesn't require parenthesis around the parameter,
>>>   (s) -> s.doSomething()
>>> should be
>>>   s -> s.doSomething().
>>>
>>   
>> Oh right.  (It didn’t look right to me but didn’t pay attention to it).
>>   
>>> In the doc of StackWalker,
>>> in the first example, the local variable 'frame' should be named ‘callerClass'
>>>
>>   
>> Fixed
>>> In the doc of getCallerClass(),
>>> the first example do a skip(2) which i believe is not necessary anymore,
>>   
>> It has to skip two frames.  Use the second example, the stack looks like this:
>>   
>> StackWalk::getCallerClass
>> Util::getResourceBundle
>> Foo::init
>> :
>> :
>>   
>>> also instead of Optional.orElse, orElseGet is better because it avoids to evaluate
>>> Thread.currentThread().getClass() if not necessary.
>>>
>>> So the example should be:
>>>    walk(s -> s.map(StackFrame::getDeclaringClass)
>>>               .findFirst()).orElseGet(() -> Thread.currentThread().getClass());
>>>
>>   
>> This would return Util.class instead of Foo.class
>>   
>>> In the second example, the field walker should be declared 'final’.
>>   
>> Sure. Fixed.
>>   
>>> And as i said earlier, the signature of walk() is:
>>>   <T> T walk(Function<? super Stream<StackWalker.StackFrame>, ? extends T> function, IntUnaryOperator batchSizeMapper)
>>>
>>   
>> I was pondering it and that’s why not changed in the last update.  I agree with the upper bounded wildcard "? extends T” for the return type of the function.
>>   
>> But for the function’s input argument, can you help me understand why it should take "? super Stream<StackWalker.StackFrame>”?  Is it useful to have function accepting supertype of Stream<StackFrame> rather than Stream<StackFrame>?  VM should be the only source producing this StackFrame stream.
>>   
>> On the other hand, I wonder if the stream argument should be Stream<? extends StackFrame> that may allow future implementation change.
>>   
>> <T> T walk(Function<Stream<? extends StackWalker.StackFrame>, ? extends T> function, …)
>>   
>> Mandy
>
>




More information about the core-libs-dev mailing list