@Suspend Java annotations for Kotlin coroutine sugar


#1

This in an extension of proposal 111, discussion here https://github.com/Kotlin/KEEP/issues/110

The goal is to enhance the integration between Kotlin and Java CPS methods.

Annotations

This proposal requires two new annotations: Suspend and ContinuationAdapter.

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class Suspend()

@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ContinuationAdapter(val value: String)

@Suspend annotation

The @Suspend annotation marks a CPS method, this method must have at least one argument and the last one must be of Continuation type. The return value is always ignored.

Note: Continuation interface is in experimental state now, however the final design of the interface does not affect this proposal.

The Java method:

@Suspend
public void compute(Continuation<Result> continuation);

can be invoked like a regular Kotlin’s suspending method

val result : Result = compute()

@ContinuationAdapter annotation

The @ContinuationAdapter annotation allows to specify a different type for last method parameter, the annotation’s value is the full class name of the adapter.

This annotations allows to use any type as CPS parameter. The wrapper must implement the proprietary interface and must have a constructor taking a single argument of Continuation type.

For example the AWS Java API uses the base class AsyncHandler (https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/handlers/AsyncHandler.html), so the adapter is

public AsyncHandlerAdapter <REQUEST, RESULT> : AsyncHandler<REQUEST, RESULT> {

  private final Continuation<RESULT> continuation;

  public AsyncHandlerAdapter(Continuation<RESULT> continuation) {
    this.continuation = continuation;
  }

  public void onSuccess(REQUEST request, RESULT result) {
    continuation.resume(result); // subject to change
  }

  public onError(Exception exception) {
    continuation.resumeWithException(exception); // subject to change
  }
}

So the class AmazonDynamoDBAsync can become

@ContinuationAdapter("mypackage.AsyncHandlerAdapter")
public class AmazonDynamoDBAsync {

  @Suspend
  public Future<GetItemResult> getItemAsync(GetItemRequest getItemRequest, AsyncHandler<GetItemRequest,GetItemResult> asyncHandler)

  // ...

and it can be used by Kotlin

val getItemResult = amazonDynamoDBAsync.getItemAsync(getItemRequest)

This proposal should cover many asynchronous libraries, like Vert.x, Memcached.
I missed some use case?


Is it possible to simplify this code?
#2

You would want a way to specify the ContinuationAdapter annotation on library types. Perhaps annotating the adapter instead might be the solution. It would however require compiler infrastructure as it needs to know to create that adapter. On the other hand it is fairly straightforward to create a simple helper function that wraps the code so you can call it as:

val getItemResult = suspendAsyncHandler { handler -> amazonDynamoDBAsync.getItemAsync(getItemRequest, handler) }

#3

For my experience the “helper function” is annoying and error prone.

I lost some time to found the bug in this line

suspendAsyncHandler { handler -> amazonDynamoDBAsync.getItemAsync(getItemRequest) }

Yes, it compiles but does not work.