IRC channel logs

2024-04-26.log

back to list of logs

<jfred>congrats!
<jfred>(though with Randy leaving, I wonder if anything's changed about the plans to get the electric communities stuff out there?)
<dthompson>jfred: my understanding is that Randy is going to continue that effort
<parnikkapore_x>Congrats everyone!
<dthompson>thanks parnikkapore_x :)
<parnikkapore_x>TIL that Juliana actually has a position within Spritely; good for her 🙂️
<jfred>dthompson: :)
<randy__>I'm all over the EC opening.
<andreas-e>Hello! I am trying to learn how to goblin, but am rather lost as to how "(on ...)" works.
<andreas-e>I thought that "on" would block/synchronise the program, and that any lines following it would be executed afterwards. However, this does not seem to be the case.
<andreas-e>Something like '(on (<- ...) (lambda () (format #t "1\n"))) (format #t "2\n")' prints 2 followed by 1. Is that a bug or supposed to be like this?
<dthompson>andreas-e: that's how it's supposed to be. 'on' doesn't block, it creates a promise.
<dthompson>sorry, I mean it handles a promise
<dthompson>it's kinda like doing promise.then(...) in javascript, if you're familiar with that
<andreas-e>dthompson: Thanks for your reply! I am not familiar at all with promises...
<dthompson>if you've ever registered an event callback on something, you can think about it like that
<andreas-e>The goblin promises seem to be quite different from the Guile promises; I was expecting "on" to behave more like "force". Actually, initially I would have liked it to return the value of the lambda expression.
<dthompson>yeah scheme's has its own definition of promise with delay/force
<dthompson>but I'd say the definition of promise that is most widespread is the goblins kind, thanks to promises in javascript
<andreas-e>Is there any way of waiting for a promise to be resolved and then to return a value it has computed? The only solution I can see so far is to create two methods, and to call one containing the "on" and computing a value and putting it into a cell, and then a second one returning this value. Then if I understood correctly, calling the second method after the first one will make sure the first one has finished?
<andreas-e>Or do I need to busy-wait until the cell content is not #f anymore?
<dthompson>you don't need to busy-wait, no
<dthompson>if you want to wait for a value then you want a promise
<dthompson>'on' can optionally return a promise, allowing for the chaining of promises
<andreas-e>Hm. This does not look like what I would try to achieve. I do not want an endless chain of promises, but at some point in time a synchronisation/resolution, of the kind "(define a (force...))" and then to continue with a containing a value and not a promise.
<tsyesika>andreas-e: can you give an example of what you want to achieve?
<andreas-e>Essentially I want to distribute computations. I have been writing up a lengthy blog post which should make it clear, and which I could share; but maybe I can make it clear in a few words.
<andreas-e>I would like to ask two actors to evaluate a complicated function and to return the results. And then I would like to wait for the computation to be finished and do something with the results.
<andreas-e>The tutorial does stuff like '(on (<- ...) (lambda (x) (format #t "x")))'
<andreas-e>What I would like do have is that "on" waits for the promise to be fulfilled and then returns its result, that is, "on" behaves like a normal function; or like "force" in Guile, I suppose.
<andreas-e>Then I could write something like '(define a (on (<- ...) (lambda (x) x))) (format #t a)' which does the same thing as before, but where I have succeeded in moving the result of the function inside "on" outside the "on", into the normal, synchronous context.
<andreas-e>(Sorry for the mistakes: I mean (format #t "~a" x) and (format #t "~a" a))
<tsyesika>you can't have `on` wait for the result, but the result will be passed to the lambda. The typical thing to do in goblins is just to pass around the promise and when you are interested in the result use `on` and put the stuff you want to do with the result in the handler.
<dthompson>right. zooming way out, if you call an actor to do some complicated work that takes time, it should return a promise for the eventual result.
<andreas-e>So the result is that I can never assign the result itself to a variable? Only a promise for the result? Or print it?
<tsyesika>you typically just work with the promise
<dthompson>the result of asynchronous message is boxed in a promise, much like the result of (delay (expensive-call)) is boxed
<dthompson>of an*
<andreas-e>I am working on a simple example, just a few minutes.
<tsyesika>cool :) I'd be happy to take a look once you've got an example and I can probably offer better advice
<andreas-e>Here is one, assuming one is in an arbitrary vat:
<andreas-e>(define a (spawn ^cell 2)) (define b (spawn ^cell 3))
<andreas-e>(+ ($ a) ($ b))
<andreas-e>This creates a value of 5 in my Guile program.
<andreas-e>I could write something like: '(define c (+ ($ a) ($ b)))' and then continue my code.
<andreas-e>Now if a and b live in different vats, I would like to do the same thing with <- instead of $.
<andreas-e>For instance, I can do this:
<andreas-e>(on (all-of (<- a)(<- b)) (lambda (x) (format #t "~a\n" (fold + 0 x))))
<andreas-e>This will *print* 5, but I do not get a variable c containing the value 5.
<andreas-e>Because, maybe, in the next step I want to define a variable (define d (+ c c)) containing 10 or something like this.
<tsyesika>you can do (on (all-of ...) (lambda (a b) ...) #:promise? #t)
<tsyesika>the on will then return a promise which will be what the lambda returned
<andreas-e>(It is possible I am thinking too imperatively...)
<dthompson>it's a bit of shift in thinking, yeah
<andreas-e>I see, thanks. The following code works:
<andreas-e>(define c (on (all-of (<- a)(<- b)) (lambda (x) (fold + 0 x)) #:promise? #t))
<andreas-e>(on c (lambda (x) (format #t "~a\n" x)))
<tsyesika>`(on (all-of (<- a) (<- b)) (lambda (result) (+ (car result) (cadr result))) #:promise? #t))` is an example of the a and b addition which results a vow
<tsyesika>yeah exactly
<dthompson>for fun, the same thing but using let-on syntax: (let-on ((x (<- a)) (y (<- b))) (+ x y))
<tsyesika>I keep forgetting let-on exists :)
<andreas-e>That is nice syntax indeed!
<dthompson>that's in (goblins actor-lib let-on)
<dthompson>but without using let-on, you could also apply pattern matching for the list of values flowing into the success handler of the all-of: (on (all-of (<- a) (<- b)) (match-lambda ((x y) (+ x y))))
<dthompson>forget the #:promise? #t but you get the idea :)
<andreas-e>I am still bothered that there is no way out of promises... So it looks like the only way to "force results" is to print them? (And that also does not wait for them to be computed, but will just show them eventually.)
<dthompson>boxing future values in promises enables all the cool async stuff
<dthompson>the car factory example in our docs is a good one
<dthompson>you can send messages to things that don't even exist yet
<dthompson>I don't know how much you do from the REPL, but consider importing (goblins repl) and using ,vat-resolve as a handy way of viewing the results of async computations
<tsyesika>yeah in goblins because you usually write code assuming actors are on different vats or even different machines across OCapN, you typically just work with promises and if they return refrs to other objects promise pipelining on them
<andreas-e>The point is that I want to synchronise from time to time... The car factory example shows that one does not have to, but I think it would make things easier.
<tsyesika>can you give an example of when you might need to synchronise?
<dthompson>there is an await-style facility in goblins where you can write async code that looks like synchronous code *but* there's security implications
<andreas-e>The example above: Before I can compute the 5 as 2+3, I need to wait until 2 and 3 have been computed. As you just kindly showed me, I can also write it asynchronously wrapped in promises, but the eventual flow of computation and data necessarily synchronises there, if only implicitly.
<tsyesika>yeah in goblins because actors often are on different vats, sometimes on different machines (across a network with OCapN) you usually just write async code using promises and `on`
<dthompson>we wouldn't want to block the event loop waiting for results
<andreas-e>Just as I can write '(define c (+ ($ a) ($ b)))', it would be nice to write something like '(define c (on (all-of (<- a)(<-b)) (lambda (x) (+ x)))' or something similar.
<andreas-e>Well, in my example things are blocked until the 2 and 3 return, regardless of the machines they live on.
<andreas-e>And that is fine, it is how it is supposed to be :)
<tsyesika>well the vat can continue processing messages until those promises resolve :)
<dthompson>we can imagine some syntax sugar that would allow you to write code that looks like that
<dthompson>but under the hood it's still all promises
<tsyesika>you can technically pull an encapsulated value out of promises using `$` but the promise needs to be "settled" (resolved with a value)
<andreas-e>This is what I have tried to do, but now I am not so sure whether there are no racing conditions. I will try to condense my example.
<tsyesika>since you don't know if they've resolved (after all they're promises), I'd typically suggest not doing that.
<dthompson>you don't have to worry about a race if you just use 'all-of' and 'on' like you're doing :)
<andreas-e>Here is the example:
<andreas-e>(define var (spawn ^cell)) (define (^setter bcom) (lambda () ($ var 2))) (define setter (spawn ^setter))
<andreas-e>($ setter) ($ var)
<andreas-e>Am I sure that the message sent to setter is executed before the one sent to var, so that the result is 2 and not #f?
<dthompson>in this case, yes, because the code is synchronous
<tsyesika>vats do ensure the order of messages so in this case `var` will always be set to 2
<tsyesika>ACTION is heading off for the evening
<andreas-e>Thanks for your help, tsyesika!
<tsyesika>you're welcome :) happy hacking!
<andreas-e>Now if in the above, I replace 2 by the distributed way of computing 2+3 as above, will it still work? Like so:
<andreas-e>(define a (spawn ^cell 2)) (define b (spawn ^cell 3)) (define var (spawn ^cell)) (define (^setter bcom) (lambda () (on (all-of (<- a)(<- b)) (lambda (x) ($ var (fold + 0 x)))))) (define setter (spawn ^setter))
<andreas-e>($ setter) ($ var)
<andreas-e>Can the result be #f, because ($ setter) is called, the promise waits for hours before being resolved, and ($ var) is called before the resolution?
<andreas-e>(In practice, it worked for me even when putting a in vat1, b in vat2, and setter and var in vat3.)
<dthompson>yes now you've introduced a race condition
<andreas-e>Okay, thanks!
<andreas-e>So there is really no way around promises, once a promise, always a promise...
<andreas-e>It will make things much more complicated, but at least I have understood how it could be done!
<dthompson>how does it make things more complicated?
<andreas-e>Because I need to argue about promises instead of values... Maybe it is just a question of getting used to things and replacing "let" by "let-on".
<dthompson>there's a little bit of an adjustment period to think asynchronously, but hopefully eventually you'll see how promises are actually pretty transparent because you message them like any other actor.
<andreas-e>Thanks for all your help! I will send you a link to my blog post draft, which I have mainly written for myself while trying to understand how things work; maybe this will make my motivation clear.
<dthompson>andreas-e: look forward to reading it and hopefully continuing to talk about things :)
<Zarutian_iPad>time to overfeed the thing
<Zarutian_iPad>sneek, botsnack.
<sneek>:)
<sneek>Welcome back Zarutian_iPad!!