I have a question about asynchronous programming in Racket: Is there any equivalent of future and promise in Racket? (e.g. promise and deferred in javascript).
You can use delay
and force
to program in effectively the same way as with promises.
You can also use delay/thread
if the computation is not itself asynchronous.
@samth I read the document of racket/promise, but it seems not the same. Typically a promise is single-assignment, that can be resolved once, but racket/promise seems not have this feature.
That is also the case for a thunk created with delay.
@samth How to resolve a promise by a result value in Racket?
I’m not sure what you mean.
The way to write p.then(f)
would be (delay (f (force p)))
@samth But how to do p.resolve(1)
?
You don’t need to do that, your computation just returns
@samth If I am running a event-loop?
(you don’t have to mention me)
I don’t understand what you mean about an event loop.
Can you give a JS example?
I have no JS example, but I can give you a Racket example with some [seudocode. Wait a moment.
Something like that (define message-queue '(INIT))
(define (async-rpc op x)
(let ((p (make-promise)))
(rpc-request a-network-process `((,op ,x) ,p)))
(future-then (promise->future p) (λ (v) (co v)))
)
(define co (generator ()
(let* ((x (yield (async-rpc 'A 1)))
(y (yield (future-race
(async-rpc 'B 2)
(async-rpc 'C 2)))))
(print (+ x y)))
))
;; event-loop
(let loop(())
(let ((next-message (dequeue message-queue))) ;; messages sent from another process.
(match next-message
[('INIT) (co)]
[('RPC-RESPONSE v p) (promise-resolve p v)]
[_ ...]))
(sleep 1)
(loop)
)
async-rpc
returns a future
. future-race
means fulfill as soon as any future’s promise resolution is fulfilled.
Have you used the coroutines directly? It’s very similar to async/await if you don’t need parallelism.
I think I would just have 'RPC-RESPONSE
come with a thunk, which you then force
(Also, in general I would just write code like this with threads and channels and not do promises at all)
But how force
return a value?
But also, I think I would write async-rpc
as (delay (co (force (delay/thread (rpc-request ...)))
But I still don’t really understand what you’re trying to do. Can you write the code you want without generators?
OK. I can rewrite this code so that it has no generator, but use future-then instead.
(define message-queue '(INIT))
(define (async-rpc op x)
(let ((p (make-promise)))
(rpc-request a-network-process `((,op ,x) ,p)))
(promise->future p)
)
(define (init)
(future-then (async-rpc 'A 1)
(λ (x)
(future-then (future-race
(async-rpc 'B 2)
(async-rpc 'C 2))
(λ (y)
(print (+ x y)))))))
;; event-loop
(let loop(())
(let ((next-message (dequeue message-queue))) ;; messages sent from another process.
(match next-message
[('INIT) (init)]
[('RPC-RESPONSE v p) (promise-resolve p v)]
[_ ...]))
(sleep 1)
(loop)
)
So, I would write async-rpc
as (delay/thread (rpc-request a-n-p
((op x) ,p)))`
And then promise-then
is just (lambda (p f) (delay (f (force p)))
and then I think the whole event loop goes away
Which one corresponds to (promise-resolve p v)
?
You don’t need that
and future-race
?
Why don’t need?
Racket already has an event loop
RPC needs to run on a network thread, and message communication is required.
You can use sync
as future-race
, although then you need to use delay/sync
instead of just delay
.
So we must have a message queue.
Then the event loop just takes everything it gets back and calls force on it
if the network thread returns promises
event-loop needs to receive a message which includes a result.
The result must be sent to the promise.
what’s the point of promises here? just pass functions then.
That is (promise-resolve p result)
, this trigger the futures
.
Is there some API you’re trying to work with?
I still find the constraints very confusing.
> what’s the point of promises here? just pass functions then. Yes, but not exactly.. In fact, addition to notification, promise has many other features, e.g. error-handing, fulfill state, … Promise also return future which can be composed, e.g. then
, future-all
, future-race
,…
Note that since Racket use prefix expression, there is no dot notation, so code you written must in CPS. In other words, there is no sense to join the nested then
to the flatten then
, which like traditional promise. This is why I use generator to simulate coroutine.
There’s no connection between prefix and CPS. You can write then
in exactly the same way.
I admit I’m a little confused - is there something wrong with sync
et al? They seemed equivalent in power to Javascript’s promise model and practically 1–1 with it’s async/await syntax?
@samth Yes. What I mean is that due to the lack of the dot operator, in the prefix syntax, the code will be ugly regardless of whether the then
be flattened.
@yilin.wei10 My code above is simulating async/await
.
But, can’t you do it using sync
?
More fundamentally, why are you trying to simulate async/await
?
async/await
is a solution to the problem that synchronous operations block, and threads are too expensive/not available. But those problems don’t exist in Racket.
Because I want to write some code in single-threaded asynchronous style with event-loop. For example, gamedev.
When you mean asynchronous
- do you mean you want the computations to be interleaved?
Yes.
Doesn’t thread
just do that for you?
No… Threads make things more complicated, especially in game development.
Racket’s thread is not the same as an OS thread I think?
So if you’re calling a C wrapper it’s still on the same OS thread.
This is the low-level details.
What I want is an asynchronous solution which based on event-loop with single-thread, like nodejs.
(define (loop)
(define evt (sync (some-evt)))
(match evt
...)
(loop))
Is something like this acceptable?
@yilin.wei10 Thank you for telling me sync
, I haven’t use it.
But you still need to send value from evt to promise.
evt
is the returned value from some-evt
.
Uh… not quite accurate, but it’s like await promise
.
Maybe a larger example would help?
I don’t understand what’s your confusion? “single-thread event-loop” + “asynchronous” is a very common programming model.
I think the confusion is because “asynchronous” can mean both concurrency and parallelism, and I’m not too sure where the problem is at the moment.
I’m just looking for the equivalent of future&promise in Racket. In fact, the code of future&promise is not complicated, I can write it by myself.
I think, the closest analogy is the evt
and thread
.
Promise can do both concurrency and parallelism
Yes - but in an event loop; depending on what you’re offloading you might not need to have the parallelism.
(for example, if you’re listening for an event on a TCP port)
Event loop is just one use-case.
You could certainly write the equivalent of a cooperative thread system in Racket using evts
But it would be similar to and less convenient than just using threads
That is, that’s how Racket’s threads work internally
So it would help to understand why you don’t want to use them
I think there are two questions: 1. How to write good asynchronous code in single-thread event-loop enviroment. (need promise) 2. How to achieve the same purpose with thread.
Of course, I will look into sync
of Racket.
> (sync (some-evt))
> Blocks as long as none of the synchronizable eventsevts are ready That is unacceptable in UI/Graphic thread.
For example, in game develop, we have something like: ;; Graphic/UI thread
(let loop(())
(update) ;<--- logic code
(draw)
(loop))
In the logic code (update)
, we can’t (sync evt)
, that is blocking operation!
Sorry, I found a small https://racket.slack.com/archives/C09L257PY/p1611683055076100?thread_ts=1611680164.072800&cid=C09L257PY\|mistake that may confuse you.
It should be (rpc-request a-network-process `((,op ,x) ,p))) ; send message ('A 1) to a network process
instead of (rpc-request a-network-process `((op x) ,p)))
That means the computation is performed on the remote machine, not apply op to x
in the current thread. I have fixed it and hope it did not cause confusion. Thanks.
> Is there some API you’re trying to work with? @samth Sorry, I missed this message. See https://docs.scala-lang.org/overviews/core/futures.html\|https://docs.scala-lang.org/overviews/core/futures.html
The promise and future in my example is very similar with Scala’s Promise and Future, except that I use single-thread event-loop model.
I understand how they work. I’m trying to understand your use case.
I have to go to bed. I just saw this message on ipad. I will come back tomorrow.
#lang racket/base
(require racket/tcp
racket/format)
(define listener (tcp-listen 8845 4 #t))
(define std-in (current-input-port))
(define std-out (current-output-port))
(define-values (tcp-in tcp-out)
(tcp-accept listener))
(displayln "received connection...")
(for
([req (in-lines tcp-in)])
(displayln (~a "recieved request: " req) std-out)
(displayln "please answer:" std-out)
(writeln (read-line std-in) tcp-out)
(flush-output tcp-out))
#lang racket/base
(require racket/tcp
racket/function
racket/format
racket/match
racket/port)
(define-values (tcp-in tcp-out)
(tcp-connect "localhost" 8845))
(define std-in (current-input-port))
(define std-out (current-output-port))
(define (loop)
(define msg
(sync/timeout 2
(wrap-evt
(read-line-evt tcp-in)
(curry cons 'tcp))
(wrap-evt
(read-line-evt std-in)
(curry cons 'stdin))))
(match msg
[(cons 'tcp line)
(displayln (~a "received from tcp:" line) std-out)]
[(cons 'stdin line)
(displayln (~a "sending to tcp: " line))
(writeln line tcp-out)
(flush-output tcp-out)]
[_
(displayln "no events, looping...")
(void)])
(loop))
(loop)
If you run both of the above in two separate shells, you’ll have an event loop and a mock async process.
The event loop reads from stdin and sends to the mock async server
Is this how you imagined it?
(Note, we keep looping, even when the async server doesn’t reply)
the confusion here is that “single-thread event-loop” + “asynchronous” model is mainly for nodejs/javascript…. and you’re imposing languages to do it the javascript/node way rather than using threads/coroutines/whatever the language provides. https://youtu.be/8aGhZQkoFbQ?t=715 also gamedevs use threads to implement asynchronous operations all the time. I use rust for gamedev and everyone just uses threads. we have async await but its still iffy.