Await and task syntax; add some extra sugar?


#1

Hey guys,

I’m the head dev at a startup on the JVM --although I’m doing greenfield fortran dev but that’s another story for later-- and one of the things that is consistently irking me right now is the difficulty of creating and dispatching tasks and futures and the like.

I’m wondering if the Kotlin community has any plans to add Future/Task/Co-routine support with some fancy kotlin-oriented sugar.

Before I go any further, for the record:

  • I think the mentality of ‘lets solve all problems’ is currently weakening Scala greatly. I really like Scala, but am so scared of the implicit keyword (among other features) that I haven’t attempted to push it on my company.
  • I am very much using this topic as a way to learn more about kotlin, so my current understanding of the language is not exhaustive. I am reasonably well acquainted with the limitations of the JVM however.

Specifically, looking at C#, you’ve got two really cool language features that I’m wondering if kotlin would ever adopt, yield and await (and its counterpart async)

As a previous C# developer, and current java (javafx desktop app) developer, I miss this feature enormously:

void handleUploadButtonClicked(ClickEvent event){
  Path file = determineIndicatedFile();
  //fast, looks at some text fields or something and creates a java.nio.path object

  Object fileContent = await fileSystem.loadContent(file); 
  //very slow, 'await' = don't hang the UI thread!!

  boolean success = await socket.upload(fileContent, file.meta()); 
  //also very slow, but it has to ~block to indicate success or failure. 
  //Don't hang the UI thread!

  if(success) { logSuccess(); }
  else { fireStingerUIEffect(); }
}

async Object loadContent(Path file){
  //opens channels, acquires file locks, has coffee with the operating system, opens file streams, pulls data off the disk.
  //all very slow operations for large files, and thus they cannot be done on the UI thread without usability issues.
}

async boolean upload(Object content, Metadata fileMeta){
  //similarly, this is C#-ish pseudo code for a co-rotine based progress-bar friendly 
  //web-uploading method. Note 'sendPart' could be a traditional blocking call.
  do try {
    double progress = sendPart(content);
    uiProgressBar.setProgress(progress);
  }
  catch(SessionFailureException e){
    return false;
  }
  while(progress < 1.0);
  return true;
}

double sendPart(Object content){
  int chunkCount = determinePacketCount(content);
  int partNo = 0;
  do{
    session.transmit(content, partNo, chunkCount);
    partNo += 1;
    yield partNo / chunkCount;
  }while(partNo < totalParts);

  yield 1.0;
}

To (very briefly) summarize the purpose of async, it changes the runtime signature of the method to be Task<Object> in C# (effectively Future<Object> in java), with an await (ie getResult()) method that yields the UI job queue, allowing the UI thread to dispatch other jobs but leaving the effective program counter at the call site, giving the appearance (to the programmer) of a traditional imperative execution flow read: devs don’t have to worry too much about blocking or icky Platform.runLater calls. On JavaFX this can be implemented with Toolkit#enterNestedEventLoop. Of course, the corollary is a multi-thread friendly model which we all too often don’t have, but thats a bigger challenge.

I’m wondering if Kotlin can/will adopt anything similar. With existing kotlin syntax (last argument is a lambda) you can get away with:

fun handleUploadButtonClicked(ClickEvent event) {
  Path file = determineIndicatedFile();

  val fileContent = await { fileSystem.loadContent(file) }

  val success = await { socket.upload(fileContent, file.meta()) }
}

which is pretty good, but (afaik) there is no getting around the type-up-conversion from the result type (eg Object, boolean) to its corresponding Task (eg Task<Object> and Task<Boolean>), so you’re still looking at something like

//method calls a static Task factory
fun loadContent(path: Path) : Task<Object> {
  Task.of {
    //implementation 
  }
}

This assumes that there’s useful information to be stored as state on a Task object, if the only thing we’re after is implementation than we can likely implement everything as extension methods. Of course, the obvious state information is things like isCancelled and thrownException or what-have-you.

And regarding Co-routines, I think we’re simply SOL. Again, lamba sugar might get us something, but I dont think it would go very far.

Thoughts? Is there existing cool syntax in kotlin (maybe around infix or operator) that I’m missing to get a really slick implementation of long-running non-blocking imperative UI event handlers and/or co-routines?

Thanks for reading!

-Geoff


#2

ahahhh, but hacker news to the rescue:

Function1Async()
  .flatMap(a -> a, Function2Async(a))
  .map( (a, b) -> nonAsync(a, b))
  .flatMap(c -> AnotherThingAsync(c))
  .error(e -> catch)
  .complete( finally );

I never realized how much a typical functional pipeline looks like a builder and I know Kotlin has some very cool builder syntax…


#3

You can indeed use builder-like (or some people would call that “monad-like”) operations to compose functions in today’s Kotlin.

On the other hand, we are investigating the possible design for coroutines, to cover something similar to what C# has there


#4

Hmm, so if I was to try to use a builder for this…

@FXML fun handleClick(ClickEvent e) { async {
   then { fileSystem.loadContent(file); }
   thenAwait { file -> fileSystem.loadContent(file); }
   thenAwait { content -> socket.upload(content, ???); }
   then { success -> {
     if(success) { logSuccess(); }
     else { fireStingerUIEffect(); }
   }}
}}

with then and thenAwait being map and flatmap respectively.

You do of course lose an enormous amount of context here, you cant reference a variable from 2 lines above (as indicated by the ???)

but more importantly it still seems like a far cry from idiomatic.


But thats exciting about co-routines, is there anywhere I can read up on the discussion you guys have already had about them?


#5

We haven’t published anything yet, but will hopefully do so soon enough.

Meanwhile, I’d recommend looking at Quasar: http://blog.jetbrains.com/kotlin/2015/06/fibers-and-actors-in-kotlin-with-quasar/. It adds functionality similar to async/await through bytecode transformation