JDK-8151736 : MethodHandles.countedLoop(MH, MH, MH) small doc issues
  • Type: Sub-task
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2016-03-11
  • Updated: 2016-06-21
  • Resolved: 2016-06-21
Related Reports
Relates :  
Relates :  
Relates :  
Description
>> Constructs a loop that runs a given number of iterations.

It looks a little bit unclear, because there is no 'number' arguments in method definition. I suppose it would be better to mention that number of iteration is specified by given method handle invocation result (first arg). Actually, it is mentioned in 'parameters' section, but it is a little bit unclear in description. This part of description is really clear in the countedLoop(MH, MH, MH, MH)

>> The loop counter is an int initialized from the iterations handle evaluation result.

This sentence differs from pseudocode, because loop counter in pseudcode is initialized to 0. It could lead to misunderstanding. Moreover if we are talking about initialization value it would be good to note that the counter is incremented (or decremented) per 1 after each loop step. This part of description is really clear in the countedLoop(MH, MH, MH, MH)

>> The iterations handle must accept the same parameter types as init but return an int

I suppose it would be good to mention that 'iterations' shouldn't be null, because we can't talk about 'null' return value. According to realization, iterations = null leads to message "java.lang.IllegalArgumentException: found non-effectively identical parameter type lists" which is not so easy to decode in terms of countedLoop.

>> The counter is passed to the body function, so that must accept an initial int argument.

The second part looks unclear. Is there typo?

>> The result of the loop execution is the final value of the additional local state.

'Additional state' is defined in 'init' definition (at the end of documentation). At this point it is hard to understand what does it mean.  And maybe it would be better to change 'result of loop execution' to 'result of created method handle invocation' or something like this. There is small ambiguity (loop method vs loop result structure). This part of description is really clear in the countedLoop(MH, MH, MH, MH)

>> The body handle must accept the same parameter types as well, preceded by an int parameter for the counter, and a parameter of the same type as init's result.

It would be good to add information about body return type. According to MethodHandles.loop(...), pseudocode and realization, there is one more restriction: body handle should have the same return type as 'init'

>> The iterations handle must accept the same parameter types as init but return an int.

According to given 'lambdaman' example, 'iterations' could have no parameters.

+ It is hard to deduce resulting handle parameter list in the case of absent 'init' function. The situation is the same as in JDK-8150964 comment
Comments
With the ongoing work on JDK-8151179, this is fixed internally; the fix will be pushed with the change for JDK-8151179.
21-06-2016

countedLoop(3args) references countedLoop(4args) in the @ImplSpec but there is no references '@see' to countedLoop(4args). Generic loop combinator have no such reference too. It's not a problem, but it would be good to add this reference.
26-04-2016

countedLoop(null, null, null, (int)void) countedLoop(null, null, (int)void) both produce method handle (int)void instead of ()void, so it doesn't treat first arg of body as 'counter'. It doesn't lead to error because start=0 and end=0 + this doesn't contradict any current existing documentation. So now I can't file bug, but it looks like bug (please observe CountedLoopProposal14).
26-04-2016

1. It seems, I'd made a mistake in the comment: >> >> If only body is present, its parameter types (minus the leading int parameter for the counter) determine the resulting loop handle's parameter type list. >>I suppose 'only body' could lead to misunderstanding, because 'only body' could be treated as (null, null, body) instead of (iterations, null, body). It was correct! The body determines parameter list only in the case init=null and iterations=null. So, now we have two parts concerning to parameter list of result handle: In case both {@code init} and {@code body} are {@code null}, the resulting loop handle will have a {@code void} * return type and accept no arguments. If {@code init} is present, its parameter types determine the resulting * loop handle's parameter type list. If {@code init} is {@code null} and {@code body} is present, {@code body}'s * parameter types (minus the leading {@code int} parameter for the counter) determine the resulting loop handle's * parameter type list. if {@code init} is missing or has no parameters, {@code iterations} will determine the resulting * loop handle's parameters. And it seems they contradict each other (case init == null). 2. With regard to 'iterations', they can determine parameter list in the case init is present and have parameters (in the case iterations have more parameters). And it seems we've missed restrictions on 'iterations' parameter list (it shouldn't contradict 'init' parameter list). They present indirectly (via reference to loop initializers documentation), but it's really hard to find necessary restrictions in loop's documentation from the 'first glance'. We can make reference to the particular step or describe them here. 3. I've attached CounterLoopProposal11.java, it contains different cases and comments. Please observe section 'ITERATIONS = NULL, INIT IS NOT NULL' because I've found that every time we pass init!=null and iterations=null, we've got IAE. It is slightly unpredictable (we just assume zero iterations, but it doesn't work!). I can't find reason of failure, maybe that's because of dropArguments(... body)? Iterations=null is really possible in the case init == null too. In the nutshell (as I see, I can make mistake), it seems that in the case init or iterations is present, parameter list of resulting method handle is defined by the longest parameter list of init and iterations and they should be effectively equals (assuming omitting function have no params). Moreover (as mentioned in spec), body should have the same parameter list (it is possible to omit arguments). Exception is the case 'init != null, iterations==null' (see notes above). In the case no init and no iterations, body defines resulting parameter list.
26-04-2016

>> The {@code body} handle, if non-{@code null}, must accept the same parameter types as {@code init}, preceded by * an {@code int} parameter for the counter, and a parameter of the same type as {@code init}'s result, representing * additional loop state. If {@code init} is present, it is possible to omit a suffix of the parameter types as long * as the leading {@code int} for the counter is given. Actually it could have longer list in the case 'iterations' have longer parameter list then 'init' (please, observe CountedLoopProposal12)
26-04-2016

If the result type of {@code init} is {@code void} or the {@code init} handle {@code null}, the loop handle's return type will be {@code void}. If present, the value of this additional loop-local variable after the final iteration will be the result of the loop handle invocation. If init is {@code null}, the return type of {@code body} determines the loop-local state type. According to these 3 assertion, in the case init is void, body isn't void: 1. init is void ( => resulting handle is void) 2. body isn't void => there is non-void loop-locals state => it should be returned after last iteration => resulting handle can't be void Actually resulting handle is void.
26-04-2016

[~asolodkaya], please comment on the updated API doc for countedLoop/3 below. It reflects all of your comments. /** * Constructs a loop that runs a given number of iterations. This is a convenience wrapper for the {@linkplain * MethodHandles#loop(MethodHandle[][]) generic loop combinator}. * <p> * The loop can have additional loop-local state, the type of which is determined by the result type of the {@code * init} handle. This also determines the result type of the resulting loop handle. If the result type of {@code * init} is {@code void} or the {@code init} handle {@code null}, the loop handle's return type will be {@code void}. * If present, the value of this additional loop-local variable after the final iteration will be the result of the * loop handle invocation. * <p> * The number of iterations is determined by the {@code iterations} handle evaluation result. The loop counter is * realized as an extra loop-local {@code int} variable. It is initialized to 0 and incremented by 1 in each * iteration. The counter is passed to the {@code body} function in each iteration as its first argument. The * {@code iterations} handle must return {@code int}. If the {@code iterations} handle is {@code null}, 0 is * assumed. * <p> * The {@code iterations} handle is an initializer in the sense of the {@linkplain #loop(MethodHandle[]...) generic * loop combinator}. This means that the rules for initializers apply to both {@code init} and {@code iterations}. * In particular, if {@code init} is missing or has no parameters, {@code iterations} will determine the resulting * loop handle's parameters. * <p> * The {@code body} handle, if non-{@code null}, must accept the same parameter types as {@code init}, preceded by * an {@code int} parameter for the counter, and a parameter of the same type as {@code init}'s result, representing * additional loop state. If {@code init} is present, it is possible to omit a suffix of the parameter types as long * as the leading {@code int} for the counter is given. * <p> * The {@code body} handle's return type must be the same as that of the loop-local state determined by * {@code init}. If init is {@code null}, the return type of {@code body} determines the loop-local state type. If * both {@code init} and {@code body} are {@code null}, there is no additional loop-local state. If there is * loop-local state, it will be updated in each iteration with the result of invoking the {@code body} handle. * <p> * If {@code body} is {@code null}, a handle will be synthesized that conforms to the signature and result type of * {@code init}. The synthetic {@code body} handle will be an identity function for the loop state initialized by * {@code init}. * <p> * In case both {@code init} and {@code body} are {@code null}, the resulting loop handle will have a {@code void} * return type and accept no arguments. If {@code init} is present, its parameter types determine the resulting * loop handle's parameter type list. If {@code init} is {@code null} and {@code body} is present, {@code body}'s * parameter types (minus the leading {@code int} parameter for the counter) determine the resulting loop handle's * parameter type list. * <p> * These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][]) * generic loop combinator}. * <p> * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument * passed to the loop. * <blockquote><pre>{@code * int iterations(A); * V init(A); * V body(int, V, A); * V countedLoop(A a) { * int end = iterations(a); * V v = init(a); * for (int i = 0; i < end; ++i) { * v = body(i, v, a); * } * return v; * } * }</pre></blockquote> * <p> * @apiNote Example: * <blockquote><pre>{@code * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s; * // => a variation on a well known theme * static String start(String arg) { return arg; } * static String step(int counter, String v, String arg) { return "na " + v; } * // assume MH_start and MH_step are handles to the two methods above * MethodHandle fit13 = MethodHandles.constant(int.class, 13); * MethodHandle loop = MethodHandles.countedLoop(fit13, MH_start, MH_step); * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!")); * }</pre></blockquote> * <p> * @implSpec The implementation of this method is equivalent to (excluding error handling): * <blockquote><pre>{@code * MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) { * return countedLoop(null, iterations, init, body); // null => constant zero * } * }</pre></blockquote> * * @param iterations a handle to return the number of iterations this loop should run. * If it is {@code null}, a constant zero is assumed. * @param init initializer for additional loop state. * @param body the body of the loop. * * @return a method handle representing the loop. * @throws IllegalArgumentException if any argument violates the above requirements. * * @since 9 */
18-04-2016

Thanks for all the comments, [~asolodkaya]! Before all the details are covered, note that the issue with the loop counter range as passed into body being (0,end] was indeed a bug. This is fixed now to be in [0,end) as you remarked - see JDK-8153637.
13-04-2016

It would be good to add "The implementation of this method is equivalent to (excluding error handling): " to implementation notes the same way it was done for JDK-8151903
31-03-2016

>> init initializer for additional loop state. it is not so important/ It looks ok that initializer for additional state could be null. But it looks a little bit strange that it could be void, so it is a kind of initializer which initializes nothing.
30-03-2016

It would be good to specify 'iterations' parameter list restrictions. Now it sound like it could have any parameter list, but actually some combinations throws IAE. >> If init is present, its parameter types determine the resulting loop handle's parameter type list. Actually, in some cases 'iterations' determines parameter list of resulting handle. Please, observe CountedLoopProposal9.java. In this case init specifies no loop parameters, and body specifies/not specifies loop parameters, and it looks like parameter list is determined from 'iterations'. E.g. init = ()String body = (I,String)String iterations = (List)String leads to (List)String resulting handle >> If only body is present, its parameter types (minus the leading int parameter for the counter) determine the resulting loop handle's parameter type list. CountedLoopProposal10 combinates CountedLoopProposal9 and CountedLoopProposal8 into case then 1) iterations defines resulting handle parameters in the case of init=null (instead of body) 2) body defines local state (body return type=body second parameter) and passes values to it
30-03-2016

>> If only body is present, its parameter types (minus the leading int parameter for the counter) determine the resulting loop handle's parameter type list. I suppose 'only body' could lead to misunderstanding, because 'only body' could be treated as (null, null, body) instead of (iterations, null, body). >> The body handle's return type must be the same as that of the loop-local state determined by init. It is expected that null init => void body is not permtitted. Actually body with different return types are permitted in the case of 'no loop parameters'. However, return type of resulting handle remains void. Please, observe CountedLoopProposal5.java for details.Correct behaviour could be found in CountedLoopProposal6.java, CountedLoopProposal7.java (have loop parameters). However, countedLoop treats second parameter of body as local state variable in the case of body return type = second variable type. E.g. String body(int counter, String s) is permitted. Moreover it could be executed and you can find that result of previous body execution is passed into variable 's'. You can find code in CountedLoopProposal8.java
30-03-2016

>> The body handle, if non-null, must accept the same parameter types as well, preceded by an int parameter for the counter, and a parameter of the same type as init's result. I assume that 'same parameter' = 'same parameter as init'. Please, observe CountedLoopProposal2.java. In the case init=()String (local state=String, loop parameters=<empty>) it is expected that body=(I)String(omitted local-state parameter) is rejected (throw IAE). Actually, it is permitted. The same situation could be found in CountedLoopProposal3.java and CountedLoopProposal4.java. It seems that 1. Local state parameter could be omitted in body in following cases: 1.1. There is no loop parameters 1.2. Loop parameters omitted too 2. Loop parameters could be omitted
29-03-2016

According to pseudocode, counter should be in [0, end) during loop execution. Current implementation follows rule >> The number of iterations is determined by the iterations handle evaluation result. But start value of initial counter passed to body is 1, so body receives counters in the range (0, end]. Is it correct? Please, observe CountedLoopProposal1.java for details.
29-03-2016

>> The loop counter is realized as an extra loop-local int variable. It is incremented by 1 after each iteration. It would be good to provide initial value of counter >> In case both init and body are null... >> body - the body of the loop, which must not be null. It seems parameter description contradicts main description >> The body handle, if non-null, must accept the same parameter types as well Does it mean 'the same as init'? It is unclear now.
29-03-2016

[~asolodkaya], please comment on this proposal. /** * Constructs a loop that runs a given number of iterations. This is a convenience wrapper for the {@linkplain * MethodHandles#loop(MethodHandle[][]) generic loop combinator}. * <p> * The loop can have additional loop-local state, the type of which is determined by the result type of the {@code * init} handle. This also determines the result type of the resulting loop handle. If the result type of {@code * init} is {@code void} or the {@code init} handle {@code null}, the loop handle's return type will be {@code void}. * If present, the value of this additional loop-local variable after the final iteration will be the result of the * loop handle invocation. * <p> * The number of iterations is determined by the {@code iterations} handle evaluation result. The loop counter is * realized as an extra loop-local {@code int} variable. It is incremented by 1 after each iteration. The counter is * passed to the {@code body} function in each iteration as its first argument. The {@code iterations} handle must * return {@code int}. * <p> * The {@code body} handle, if non-{@code null}, must accept the same parameter types as well, preceded by an {@code * int} parameter for the counter, and a parameter of the same type as {@code init}'s result. The {@code body} * handle's return type must be the same as that of the loop-local state determined by {@code init}. In each * iteration, the loop-local state will be updated with the result of invoking the {@code body} handle. * <p> * In case both {@code init} and {@code body} are {@code null}, the resulting loop handle will have a {@code void} * return type and accept no arguments. If {@code init} is present, its parameter types determine the resulting * loop handle's parameter type list. If only {@code body} is present, its parameter types (minus the leading * {@code int} parameter for the counter) determine the resulting loop handle's parameter type list. * <p> * These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][]) * generic loop combinator}. * <p> * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument * passed to the loop. * <blockquote><pre>{@code * int iterations(A); * V init(A); * V body(int, V, A); * V countedLoop(A a) { * int end = iterations(a); * V v = init(a); * for (int i = 0; i < end; ++i) { * v = body(i, v, a); * } * return v; * } * }</pre></blockquote> * <p> * @apiNote Example: * <blockquote><pre>{@code * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s; * // => a variation on a well known theme * static String start(String arg) { return arg; } * static String step(int counter, String v, String arg) { return "na " + v; } * // assume MH_start and MH_step are handles to the two methods above * MethodHandle fit13 = MethodHandles.constant(int.class, 13); * MethodHandle loop = MethodHandles.countedLoop(fit13, MH_start, MH_step); * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!")); * }</pre></blockquote> * <p> * @implSpec The implementation of this method is equivalent to: * <blockquote><pre>{@code * MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) { * return countedLoop(null, iterations, init, body); // null => constant zero * } * }</pre></blockquote> * * @param iterations a non-{@code null} handle to return the number of iterations this loop should run. * @param init initializer for additional loop state. This determines the loop's result type. * Passing {@code null} or a {@code void} init function will make the loop's result type * {@code void}. * @param body the body of the loop, which must not be {@code null}. * It must accept an initial {@code int} parameter (for the counter), and then any * additional loop-local variable plus loop parameters. * * @return a method handle representing the loop. * @throws IllegalArgumentException if any argument violates the above requirements. * * @since 9 */
22-03-2016