0

I am looking at the following code (someone else's code) and part of the code seems obscure to me:

List<string> result = forkJoinPool.invokeAll(tasks)
            .stream()
            .map(MUtil.rethrowFunction(Future::get))
            .collect(toList());

This part is straightforward, a ForkJoinPool.invokeAll() returning a List of Future objects and further processing returns a List of Strings.

Later, the list.stream().map() uses a static method on Mutil class Mutil.rethrowFunction(Future::get) and passes it an object of Future type. Looking at the Mutil source code :

public class Mutil {
    public static <T, R> Function<T, R> rethrowFunction(WithExceptionsIF<T, R> function) {
        return t -> {
            try { return function.apply(t); }
            catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }
    @FunctionalInterface
      public interface WithExceptionsIF<T, R> {
          R apply(T t) throws Exception;
    }

    @SuppressWarnings ("unchecked")
    private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }
}

Here are my questions, as I am learning Generics, lambda and java.util.function package:
  1. The signature of Mutil.rethrowFunction(), says it returns <T, R> Function<T, R> and uses a parameter of type WithExceptionsIF<T, R> (which is a functional interface). How does Future.get() translates into the Mutil.rethroFunction() signature? Future.get(), returns a computed result and not a Function?

  2. How does Future.get(), which is passed to Mutil.rethrowFunction() translates to WithExceptionIF<T,R> type?

  3. t->{return function.apply(t);} what is going on in this statement? is 't' the Future object, if so then who is the "function"?

  4. The signature of Mutil.throwAsUnchecked(Exception exception) method has <E extends Throwable> defined after the keyword "static". If "exception" is the only parameter passed to the method, where does the E come from and why is it declared before the method's return type (void)?

Thanks for any clarity.

Anthony
  • 135
  • 1
  • 14

2 Answers2

0

Here's a complete runnable example.

package org.example;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.function.Function;

import static java.util.stream.Collectors.toList;

public class SO62737345 {
    public static void main(String... args) {
        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
        List<Callable<String>> tasks = Collections.singletonList(() -> "fish");
        List<String> result = forkJoinPool.invokeAll(tasks)
                .stream()
                .map(rethrowFunction(Future::get))
                .collect(toList());
    }

    public static <T, R> Function<T, R> rethrowFunction(WithExceptionsIF<T, R> function) {
        return t -> {
            try {
                return function.apply(t);
            }
            catch (Exception exception) { throwAsUnchecked(exception);  return null; }
        };
    }
    @FunctionalInterface
    public interface WithExceptionsIF<T, R> {
        R apply(T t) throws Exception;
    }

    @SuppressWarnings ("unchecked")
    private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }
}
  1. The get() method doesn't execute when rethrowFunction is called. The call to rethrowFunction just transforms the function passed in from one which throws a checked exception to one which doesn't (because any exception inwrapped in an unchecked exception). The collect() method actually calls the function, once for each Future in the Stream.

  2. Think about passing Future::get using a lambda rather than a method reference. It would be f -> f.get(). because we are passing a member function it takes the object it belongs to as a parameter. SO we are passing a function, and as the signature of get() is V get() throws InterruptedException, ExecutionException it matches WithExceptionsIF. In fact it is an instance of WithExceptionsIF<Future,String> in this particular case.

  3. If you expand this by substituting the lambda above, you get: return (f -> f.get()).apply(t) so get is being applied to the parameter being passed when the function which rethrowFunction` returns is evaluated.

  4. This is explained here A peculiar feature of exception type inference in Java 8 Java is performing 'type inference'. The compiler has been told that throwAsUnchecked throws an object whose class is a subclass of Throwable. Because the return type of rethrowFunction does not declare a checked exception, the compiler decides that throwAsUnchecked must be throwing a RuntimeException. Another way of looking at it is that the type parameter in this case has its value inferred from the context the function is invoked in, rather than from its parameters. At least I think that's how it works, it is not that easy to understand!

Explaining that generic syntax more generally, often a method is part of a parameterised type, e.g. List<A>, so the type parameter can appear in the signature of member functions, e.g. A get(int i). You can also have a generic function whose signature has type parameters which do not appear in the class (or a static function which is not associated with an instance of the class), and here they are declared before the return type.

tgdavies
  • 10,307
  • 4
  • 35
  • 40
  • Thank you tgdavies, this clarifies part 2 and part 3 of my question, however, part 1 is about the return type, which still remains a mystery. Once the Future.get() executes, the return will be a computed value and how does that translate to the Mutil.rethrowFunction() signatures that declare Function as the return type? Let's sya if the Future.get() returns an Integer, how does that match to Function ? Thanks again – Anthony Jul 05 '20 at 09:19
  • I've updated my answer, let me know whether that's clearer – tgdavies Jul 05 '20 at 09:54
  • I thank you immensely; your added comment in (1) makes this very clear now. I understand the code better, and I can follow the logic, however, I just hit another snag, which is the last one, I promise :-). I am going to update my post with part 4 and if you could please shed some light on that as well. – Anthony Jul 05 '20 at 11:28
  • With all due respect, there's several things in your answer that are incorrect. I've quoted some of them in my answer. – deduper Jul 09 '20 at 23:53
  • I've added the full code to my answer. Put a breakpoint on `Future::get` and you'll see that it's called by `collect()`, long after `rethrowFunction()` has returned. I'll clarify that 'That' refers to `rethrowFunction()`. All member functions implicitly take the object they belong to as a parameter. – tgdavies Jul 10 '20 at 08:01
  • For somebody new to them, generics are confusing enough on their own. Why bring something as subtle as [*receiver parameters*](https://docs.oracle.com/javase/specs/jls/se14/html/jls-8.html#jls-8.4-300) into it to confuse things even more? Try this in your debugger. Get an instance of a *`Future`* in your *`main()`*; totally outside of any *`Stream`* code at all. Name that instance, *`Future> myFuture...`*, say. Then add *`rethrowFunction(myFuture.get())`* in your *`main()`* (*again, outside of the context of a `Stream` pipeline*). What do you think is happening in the call stack? – deduper Jul 10 '20 at 09:58
  • Sure, but that's completely different from the situation the OP was asking about. `myFuture.get()` is not the same as `Future::get`. – tgdavies Jul 11 '20 at 00:38
0

The get() method doesn't execute when rethrowFunction is called…“

That's incorrect. Future::get is too called in rethrowFunction()s call chain; as a side effect of rethrowFunction() being called. The return type of Future::get is a MUtil.WithExceptionsIF. It is that return value that is passed as the input argument to rethrowFunction().

That just transforms the function passed in from one which throws a checked exception to one which doesn't…

The wording of this sentence is confusing. It reads as if the „That“ in the sentence refers to the Future::get. The get() method doesn't do that transform. The rethrowFunction() method is what does the heavy lifting.

Think about passing Future::get…because we are passing a member function it takes the object it belongs to as a parameter

Future.get() doesn't take any parameters.

…as the signature of get() is V get() throws InterruptedException, ExecutionException it matches WithExceptionsIF in this particular case…“

This is not correct either. The signature of Future.get() has nothing to do with anything. It's certainly not true that the signature of Future.get() matches WithExceptionsIF. Again, the return type of Future.get() would be WithExceptionsIF in this particular invocation. But a method's return type is not part of its signature.

Future.get() translates to *WithExceptionsIF<T,R> because forkJoinPool.invokeAll(tasks) returns a List<Future <MUtil.WithExceptionsIF<T,R>>>.

If you expand this by substituting the lambda above, you get: return (f -> f.get()).apply(t) so get is being applied to the parameter being passed when the function which rethrowFunction` returns is evaluated.

Incorrect. Again, Future.get() has nothing to do with it. Future.get()s job was done after it passed the WithExceptionsIF into rethrowFunction. The t -> {...} block defines a lambda which is returned by the call to rethrowFunction(). The t is the input argument to the lambda. Its type will be of whatever type the type parameter T gets instantiated as.

Some concrete implementation of the WithExceptionsIF.apply() method is what would be called when that lambda is actually invoked; at some unknown later point.

The function we see there is an instance of WithExceptionsIF<T, R> passed in to rethrowFunction(WithExceptionsIF<T, R> function).

…If "exception" is the only parameter passed to the method, where does the E come from and why is it declared before the method's return type (void)?

This: static <E extends Throwable> void throwAsUnchecked(Exception exception) is how generic methods are declared. The E type variable declared in the type parameter section is used in the method's throws clause. Also in its body; to cast the Exception that's passed in to the method, to be of another, more specific type that would be specified at the point the generic method is actually invoked.

Last, but not least: the List<string>... assignment is wrong for a couple reasons. The main reason is the stream in your example does not produce Stream<String>. It produces Stream<Function<T, R>>.

deduper
  • 1,944
  • 9
  • 22