nim-chronos icon indicating copy to clipboard operation
nim-chronos copied to clipboard

RFC: inject `Future[T]` automatically

Open arnetheduck opened this issue 4 years ago • 7 comments

Currently, an async proc looks like so:

# Return int
proc a(): Future[int] {.async.}
# Return void
proc b(): Future[void] {.async.}
# Return void, take 2
proc c()] {.async.}

There is an asymmetry in void which is confusing because there are two ways to do void return, one of which implicitly injects the future and the other that doesn't.

The second confusion comes from the similarity of async and non-async implemenations - ie it's possible to create a function proc d(): Future[int] = ... without using the async transformation and still fit it within the sync/async framework.

As such, I'd suggest that the async macro be changed such that it always automatically injects the future:

proc a(): int {.async.} = ...
proc sameAsA(): Future[int] = ...

Regarding backwards compat, the macro can detect the future and give a deprecation warning for a transitionary period of time.

arnetheduck avatar Mar 26 '20 15:03 arnetheduck

This is very bad idea, because all the implicit conversions of target procedure arguments or return value can create confusion when reading compiler's errors.

Compiler will show you types which was converted by "macro code", while you do not see this conversion in your source.

For example: proc a(): int {.async.} got transformed implicitly to proc a(): Future[int] and now all compiler errors will be about Future[int] value, but not about int.

So current small inconvenience will become much more annoying. As an alternative we can remove silent conversion of procedures without return values to Future[void] and show an error.

cheatfate avatar Mar 27 '20 01:03 cheatfate

compiler errors

isn't this something that the macro could help with? basically, could it output did you forget "await"? in the right places?

if the objective of async/await is to make async code look like sync code (which most async frameworks seem to want to do), then decorating things with Future works against that goal - specially if you want to introduce other types like tuple and Option, it quickly gets very noisy - things that you would "naturally" do in sync code become very verbose.

arnetheduck avatar Mar 27 '20 10:03 arnetheduck

I support this change. The same question was discussed in a Nim RFC and someone correctly pointed out that you cannot avoid the type discrepancy - it will be either present in eyes of the caller or in the eyes of the implementer:

proc foo(): Future[int] {.async.} =
  result = 10 # How come the result is not a `Future`? That's what the signature says!

In the end, Nim has code generation features and we must learn to accept that. What this means it that when you start using a library, your first step should be to read the published documentation. You have to be aware that certain features such as custom pragmas may affect the generated code and you must develop the Nim survival skills to deal with this. I can promise you that the tools at your disposal will become better in the future.

There is a particular objective measure that gets improved with this change. It reduces the erroneous "degrees of freedom" of the API. It's no longer possible to produce certain compilation errors (oh, I forgot to add the Future type).

zah avatar Mar 27 '20 11:03 zah

Its very easy to remove all this result replacements and force people to use complete and fail explicitly. And i really like such change, but i really dislike proposal to perform implicit conversions and produce many nonsense compile time errors, because compiler will start mention Future[T] but you will not see Future[T] in your procedures and so for you this errors messages will have no sense.

cheatfate avatar Mar 27 '20 17:03 cheatfate

@arnetheduck because async macro is untyped we can't get type information, and becasue we do not have type information its very hard to show meaningful error. And we are not talking here about await in right places we are talking about modification of procedure's return value.

cheatfate avatar Mar 27 '20 18:03 cheatfate

how hard would it be to introduce an experimental macro that does this (async2 or whatever)? ie to play around with the syntax without committing to it..

arnetheduck avatar Apr 01 '20 06:04 arnetheduck

@arnetheduck all the logic of result value happens here in asyncmacro2.asyncSingleProc.

This part is responsible for verification of return value: https://github.com/status-im/nim-chronos/blob/master/chronos/asyncmacro2.nim#L164-L181 This part is responsible for creation of internal result Future[T] object. https://github.com/status-im/nim-chronos/blob/master/chronos/asyncmacro2.nim#L187-L193 And last transformation which transforms proc foo() {.async.} => proc foo(): Future[void]. https://github.com/status-im/nim-chronos/blob/master/chronos/asyncmacro2.nim#L255-L259

cheatfate avatar Apr 09 '20 06:04 cheatfate