I want to walk the search space of an asynchronous function. I coded the logic as follows:
/**
* Assuming that a function maps a range of inputs to the same output value, minimizes the input value while
* maintaining the output value.
*
* @param previousInput the last input known to return {@code target}
* @param currentInput the new input value to evaluate
* @param function maps an input to an output value
* @param target the expected output value
* @return the minimum input value that results in the {@code target} output value
* <br>{@code @throws NullPointerException} if any argument is null
* <br>{@code @throws IllegalArgumentException} if {@code stepSize} is zero}
*/
private static CompletionStage<BigDecimal> optimizeInput(BigDecimal previousInput,
BigDecimal currentInput,
BigDecimal stepSize,
Function<BigDecimal, CompletionStage<BigDecimal>> function,
BigDecimal target)
{
return function.apply(currentInput).thenCompose(output ->
{
assertThat("stepSize", stepSize).isNotZero();
int outputMinusTarget = output.compareTo(target);
if (outputMinusTarget != 0)
return CompletableFuture.completedFuture(previousInput);
BigDecimal nextInput = currentInput.add(stepSize);
if (nextInput.compareTo(BigDecimal.ZERO) < 0)
return CompletableFuture.completedFuture(previousInput);
return optimizeInput(currentInput, nextInput, stepSize, function, target);
});
}
Unfortunately, if the function has a large search space this raises a StackoverflowError after some iterations. Is it possible to walk the search space iteratively, with a fixed-size stack?
you have the following recursion structure
You can rewrite it avoiding completable future composition and its stack usage during completion.
if
asyncTask()
is not really asynchronous and just use the current thread, you must replacethenAccept
with one of its asynchronous versions to use the executor task queue instead of the thread stack.dfogni's answer should work fine -- but for completeness, it is possible to avoid doing the executor handoffs in the case where the method is synchronous using a trampolining type technique.
To make it easier, I've introduced a class that capture the state that is changing between iterations and introducing methods that implement your completion checks and generate the next state. I believe this is the same as your original logic, but you can triple check.
Ok, so it's pretty complicated. Also, note that this requires your function will never throw an exception or complete exceptionally which could be problematic. You can generify this so you only have to write it once though (with correct exception handling), which can be found in the asyncutil library (Disclaimer: I am a co-author of this library). There might be other libraries with similar functionality, most likely a mature reactive library like Rx. Using asyncutil,