chansey97
2021-1-26 16:56:04

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).


samth
2021-1-26 17:02:11

You can use delay and force to program in effectively the same way as with promises.


samth
2021-1-26 17:03:22

You can also use delay/thread if the computation is not itself asynchronous.


chansey97
2021-1-26 17:04:52

@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.


samth
2021-1-26 17:05:47

That is also the case for a thunk created with delay.


chansey97
2021-1-26 17:06:55

@samth How to resolve a promise by a result value in Racket?


samth
2021-1-26 17:07:12

I’m not sure what you mean.


samth
2021-1-26 17:08:38

The way to write p.then(f) would be (delay (f (force p)))


chansey97
2021-1-26 17:09:21

@samth But how to do p.resolve(1) ?


samth
2021-1-26 17:09:41

You don’t need to do that, your computation just returns


chansey97
2021-1-26 17:10:01

@samth If I am running a event-loop?


samth
2021-1-26 17:10:46

(you don’t have to mention me)


samth
2021-1-26 17:10:59

I don’t understand what you mean about an event loop.


samth
2021-1-26 17:11:04

Can you give a JS example?


chansey97
2021-1-26 17:11:52

I have no JS example, but I can give you a Racket example with some [seudocode. Wait a moment.


chansey97
2021-1-26 17:44:15

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) )


chansey97
2021-1-26 17:45:02

async-rpc returns a future . future-race means fulfill as soon as any future’s promise resolution is fulfilled.


yilin.wei10
2021-1-26 17:46:40

Have you used the coroutines directly? It’s very similar to async/await if you don’t need parallelism.


samth
2021-1-26 17:46:47

I think I would just have 'RPC-RESPONSE come with a thunk, which you then force


samth
2021-1-26 17:47:35

(Also, in general I would just write code like this with threads and channels and not do promises at all)


chansey97
2021-1-26 17:48:23

But how force return a value?


samth
2021-1-26 17:51:26

But also, I think I would write async-rpc as (delay (co (force (delay/thread (rpc-request ...)))


samth
2021-1-26 17:51:57

But I still don’t really understand what you’re trying to do. Can you write the code you want without generators?


chansey97
2021-1-26 17:54:35

OK. I can rewrite this code so that it has no generator, but use future-then instead.


chansey97
2021-1-26 17:59:33

(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) )


samth
2021-1-26 18:05:36

So, I would write async-rpc as (delay/thread (rpc-request a-n-p((op x) ,p)))`


samth
2021-1-26 18:06:11

And then promise-then is just (lambda (p f) (delay (f (force p)))


samth
2021-1-26 18:06:42

and then I think the whole event loop goes away


chansey97
2021-1-26 18:07:38

Which one corresponds to (promise-resolve p v)?


samth
2021-1-26 18:07:48

You don’t need that


chansey97
2021-1-26 18:07:48

and future-race ?


chansey97
2021-1-26 18:08:51

Why don’t need?


samth
2021-1-26 18:09:16

Racket already has an event loop


chansey97
2021-1-26 18:11:00

RPC needs to run on a network thread, and message communication is required.


samth
2021-1-26 18:11:09

You can use sync as future-race, although then you need to use delay/sync instead of just delay.


chansey97
2021-1-26 18:11:18

So we must have a message queue.


samth
2021-1-26 18:12:19

Then the event loop just takes everything it gets back and calls force on it


samth
2021-1-26 18:12:39

if the network thread returns promises


chansey97
2021-1-26 18:12:51

event-loop needs to receive a message which includes a result.


chansey97
2021-1-26 18:13:34

The result must be sent to the promise.


samth
2021-1-26 18:14:15

what’s the point of promises here? just pass functions then.


chansey97
2021-1-26 18:14:21

That is (promise-resolve p result), this trigger the futures.


samth
2021-1-26 18:14:35

Is there some API you’re trying to work with?


samth
2021-1-26 18:14:55

I still find the constraints very confusing.


chansey97
2021-1-26 18:19:54

> 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 ,…


chansey97
2021-1-26 18:25:59

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.


samth
2021-1-26 18:27:06

There’s no connection between prefix and CPS. You can write then in exactly the same way.


yilin.wei10
2021-1-26 18:28:18

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?


chansey97
2021-1-26 18:29:19

@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.


chansey97
2021-1-26 18:30:50

@yilin.wei10 My code above is simulating async/await.


yilin.wei10
2021-1-26 18:31:45

But, can’t you do it using sync ?


samth
2021-1-26 18:32:13

More fundamentally, why are you trying to simulate async/await?


samth
2021-1-26 18:33:13

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.


chansey97
2021-1-26 18:33:40

Because I want to write some code in single-threaded asynchronous style with event-loop. For example, gamedev.


yilin.wei10
2021-1-26 18:35:00

When you mean asynchronous - do you mean you want the computations to be interleaved?


chansey97
2021-1-26 18:35:34

Yes.


yilin.wei10
2021-1-26 18:35:52

Doesn’t thread just do that for you?


chansey97
2021-1-26 18:37:24

No… Threads make things more complicated, especially in game development.


yilin.wei10
2021-1-26 18:37:46

Racket’s thread is not the same as an OS thread I think?


yilin.wei10
2021-1-26 18:38:21

So if you’re calling a C wrapper it’s still on the same OS thread.


chansey97
2021-1-26 18:38:28

This is the low-level details.


chansey97
2021-1-26 18:39:32

What I want is an asynchronous solution which based on event-loop with single-thread, like nodejs.


yilin.wei10
2021-1-26 18:41:28

(define (loop) (define evt (sync (some-evt))) (match evt ...) (loop))


yilin.wei10
2021-1-26 18:41:37

Is something like this acceptable?


chansey97
2021-1-26 18:43:05

@yilin.wei10 Thank you for telling me sync , I haven’t use it.


chansey97
2021-1-26 18:43:31

But you still need to send value from evt to promise.


yilin.wei10
2021-1-26 18:44:27

evt is the returned value from some-evt .


yilin.wei10
2021-1-26 18:45:12

Uh… not quite accurate, but it’s like await promise .


yilin.wei10
2021-1-26 18:50:55

Maybe a larger example would help?


chansey97
2021-1-26 18:54:28

I don’t understand what’s your confusion? “single-thread event-loop” + “asynchronous” is a very common programming model.


yilin.wei10
2021-1-26 18:57:11

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.


chansey97
2021-1-26 18:57:27

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.


yilin.wei10
2021-1-26 18:57:56

I think, the closest analogy is the evt and thread .


chansey97
2021-1-26 18:58:04

Promise can do both concurrency and parallelism


yilin.wei10
2021-1-26 18:58:48

Yes - but in an event loop; depending on what you’re offloading you might not need to have the parallelism.


yilin.wei10
2021-1-26 18:59:24

(for example, if you’re listening for an event on a TCP port)


chansey97
2021-1-26 19:00:42

Event loop is just one use-case.


samth
2021-1-26 19:01:10

You could certainly write the equivalent of a cooperative thread system in Racket using evts


samth
2021-1-26 19:01:37

But it would be similar to and less convenient than just using threads


samth
2021-1-26 19:01:59

That is, that’s how Racket’s threads work internally


samth
2021-1-26 19:02:16

So it would help to understand why you don’t want to use them


chansey97
2021-1-26 19:08:16

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.


chansey97
2021-1-26 19:31:41

> (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!


chansey97
2021-1-26 20:19:10

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.


chansey97
2021-1-26 21:06:01

> 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.


samth
2021-1-26 21:30:16

I understand how they work. I’m trying to understand your use case.


chansey97
2021-1-26 21:45:55

I have to go to bed. I just saw this message on ipad. I will come back tomorrow.


yilin.wei10
2021-1-26 21:52:19

#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))


yilin.wei10
2021-1-26 21:52:40

#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)


yilin.wei10
2021-1-26 21:53:50

If you run both of the above in two separate shells, you’ll have an event loop and a mock async process.


yilin.wei10
2021-1-26 21:54:41

The event loop reads from stdin and sends to the mock async server


yilin.wei10
2021-1-26 21:55:27

Is this how you imagined it?


yilin.wei10
2021-1-26 21:56:08

(Note, we keep looping, even when the async server doesn’t reply)


jestarray
2021-1-26 22:21:11

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.