Problems interacting with generic Java code


#1

Here is a simplified excerpt from a Java promise API that I’m using from Kotlin. Only signatures matter.

import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import java.util.stream.Collectors;

public class MyFuture<T> {
  private T value;
  private Throwable error;

  private MyFuture(T value, Throwable error) {
    this.value = value;
    this.error = error;
  }

  public static <T> MyFuture<T> value(T value) {
    return new MyFuture<T>(value, null);
  }

  public static <T> MyFuture<T> error(Throwable error) {
    return new MyFuture<T>(null, error);
  }

  public T getValue() {
    return value;
  }

  public Throwable getError() {
    return error;
  }

  <U> MyFuture<U> andThen(Function<? super T, MyFuture<U>> callback) {
    return callback.apply(value);  
  }
  
  @SafeVarargs
  public static <T> MyFuture<Collection<T>> allOf(MyFuture<T>... futures) {
    return MyFuture.value(Arrays.asList(futures).stream().map(MyFuture::getValue).collect(Collectors.toList()));
  }
}

Here is a sample usage from Java code:

public class JavaClient {
  public static void main(String[] args) {
    MyFuture.allOf(MyFuture.value(42), MyFuture.value("other"));

    MyFuture<String> x = MyFuture.value("foo").andThen(str -> MyFuture.error(new RuntimeException("ouch")));
  }
}

Here is the best Kotlin translation I could come up with:

fun main(args: Array<String>) {
  // extra casts required, give bogus warning "this cast can never succeed"
  MyFuture.allOf(
      MyFuture.value(42) as MyFuture<Any>, 
      MyFuture.value("other") as MyFuture<Any>
  )

  val x: MyFuture<String> = MyFuture.value("foo").andThen {
    // extra <String> required (which doesn't make much sense for an error, from an API user's perspective)
    // otherwise "not enough information to infer parameter T"
    MyFuture.error<String>(RuntimeException("ouch")) 
  }
}

Are these known limitations of Java interop?


#2

Hi, both can be simplified:

  1. Move the type to the function:
MyFuture.allOf<Any?>(
        MyFuture.value(42),
        MyFuture.value("other")
)
  1. The problem here is, that the type of the result is unknown in the functions code block. For me, the extra <String> approach looks very good, because it tells me type of the error future. If the type is uninteresting at all, you should change the java code to:
    public static MyFuture<Object> error(Throwable error) {
        return new MyFuture(null, error);
    }

and kotlin to:

    val x: MyFuture<Any> = MyFuture.value("foo").andThen {
        // extra <String> required (which doesn't make much sense for an error, from an API user's perspective)
        // otherwise "not enough information to infer parameter T"
        MyFuture.error(RuntimeException("ouch"))
    }

Then, x has the Any type, but it’s not interesting because the resulting value will never exists anyway.


#3

Unfortunately, my example was too simplistic. However, your answer helped me to trace back the problem to a thorny wildcard related issue that appears to be equally hard to solve in Java and Kotlin. Thanks!

Changing the Java code in this way wouldn’t work. Anyway, my point is that Java can infer the type but Kotlin can’t. For example:

fun foo(x: Int): MyFuture<String> {
  when {
    x < 5 -> MyFuture.value("ok")
    else -> MyFuture.error<String>(RuntimeException("nope"))
  }
}

The additional <String> that Kotlin requires over Java here is a bit annoying (and doesn’t add any clarity), but I can live with it.