I'm playing around a bit with writing a reactive programming lib for nim. Part of that means implementing Observers, which contain 3 callbacks: next, error and complete.
All 3 of these callbacks must be async procs because they might contain async work done by the user. To keep that ability open I need to store them that way thusly and the type looks like this:
type
Observer*[T] = ref object
next*: proc(value: T): Future[void] {.async.}
error*: proc(error: ref CatchableError): Future[void] {.async.}
complete*: proc(): Future[void] {.async.}
Now to keep the syntax convenient though, I want people to be able to just pass me synchronous callback functions and I convert them to asynchronous ones under the hood (well, really only for the type system because without await they'll be executing synchronously anyway and just return an immediately completed Future). So the code I want to allow looks like this:
newObserver[int]((value: int) => echo "Value is: ", value)
newObserver[int](
proc(value: int) {.async.} =
await asyncSleep(1000)
echo "Value is: ", value
)
My first draft of an implementation looks sth like this:
proc newObserver*[T](
next: proc(value: T) | proc(value: T) {.async.},
error: proc(error: CatchableError) | proc(error: CatchableError) {.async.} = nil,
complete: proc() | proc {.async.} = nil,
): Observer[T] =
let nextProc = when next.isAsyncProc():
next
else:
proc(value: T){.async.} = next(value)
... repeat this for error and complete to assign to errorProc and completeProc...
Observer[T](
next: nextProc,
error: errorProc,
complete: completeProc,
)
Now the question is how to implement isAsyncProc. I know I could write a macro, but I feel like this should be solvable via simpler means.
These are the things I've already tried:
import std/[asyncdispatch, macros]
proc a() {.async.} = echo "Potato"
echo a is proc(): Future[void] {.async.}
echo a is proc(): Future[void]
echo a is proc() {.async.}
echo a.hasCustomPragma(async)
All of the above return false.Looking at it, that doesn't return true/false.
import std/[asyncdispatch, macros]
proc a() {.async.} = echo "Potato"
echo typeof(a is Future)
Echo's bool, not true.
Further I'm not too sure how that'd look like if a actually contains parameters, which I'd need to put valid-enough values into to get a return value at compile-time.
Hmmm it appears nim is not a fan at all of making that proc-type generic there:
import std/[asyncdispatch, macros, sugar]
template isAsync(body: untyped): bool = typeof(body) is Future
type
AsyncNextProc[T] = proc(value: T): Future[void] {.async, closure.}
NextProc[T] = proc(value: T) {.closure.}
AsyncErrorProc = proc(error: ref CatchableError): Future[void] {.async, closure.}
ErrorProc = proc(error: ref CatchableError) {.closure.}
AsyncCompleteProc = proc(): Future[void] {.async, closure.}
CompleteProc = proc() {.closure.}
Observer*[T] = ref object
next*: AsyncNextProc[T]
error*: AsyncErrorProc
complete*: AsyncCompleteProc
proc newObserver*[T](
next: NextProc[T] | AsyncNextProc[T],
error: ErrorProc | AsyncErrorProc = nil,
complete: CompleteProc | AsyncCompleteProc = nil
): Observer[T] =
const nextProc = when isAsync(next(default(T))):
next
else:
proc(value: T){.async, closure.} = next(value)
const errorProc = when isAsync(error(nil)):
error
else:
proc(exception: ref CatchableError){.async, closure.} = error(exception)
const completeProc = when isAsync(complete()):
complete
else:
proc(){.async, closure.} = complete()
return Observer[T](
next: nextProc,
error: errorProc,
complete: completeProc,
)
let obs = newObserver[int](proc(value: int) = echo value)
Leads to Error: expression 'newObserver[int](proc (value: int) = echo [value])' cannot be called
... do I really have to write out every single permutation of these 3 procs and hard-code the transformation from sync to async proc?
i think the problemis that you actually trying to call function and not check the procedure
const a = typeof(complete())
maybe you somehow need to do it using macros and check ast