IRC channel logs

2023-06-19.log

back to list of logs

<KrisKowal[m]>I’d be much obliged if someone could point me at an example of a Goblins actor implementation. I’m looking for concrete examples of Actors that can be used like a lambda (nil method name), like an object (symbolically named method), or like a JavaScript object (stringly named method) cc Jessica
<KrisKowal[m]>Would also be obliged to see how these get applied or invoked remotely.
<juliana[m]>the Goblins manual has lots of examples: https://spritely.institute/files/docs/guile-goblins/0.11.0/ I'm not super familiar with JavaScript objects or what you mean by "stringly named method" but https://spritely.institute/files/docs/guile-goblins/0.11.0/A-simple-greeter.html seems to be what you mean by "nil method name" (iiuc) and
<juliana[m]> https://spritely.institute/files/docs/guile-goblins/0.11.0/Methods-aren_0027t-essential-but-they-are-useful.html uses symbolic ones. https://spritely.institute/files/docs/guile-goblins/0.11.0/Asynchronous-message-passing.html covers async code at a high level. and of course there's core and actor-lib documentation as well
<KrisKowal[m]>In JavaScript, object.method() is a stringly-named method. It’s equivalent to object'method'
<KrisKowal[m]>Whereas objectSymbol.for('deposit-gift') is a symbolically named method invocation.
<KrisKowal[m]> * Whereas object[Symbol.for('deposit-gift')]() is a symbolically named method invocation.
<KrisKowal[m]> * In JavaScript, object.method() is a stringly-named method. It’s equivalent to object['method']().
<KrisKowal[m]>Whereas object[Symbo.asyncIterator]() is a well-named-symbolically named method invocation, and the well-known-symbols have a disjoint namespace from other interned symbols. This is the basis of JavaScript’s iterator protocol among other things.
<KrisKowal[m]>s//`/, s/Symbo/Symbol/, s//`/
<KrisKowal[m]>That is to say, object[Symbol.for('asyncIterator')]() is not equivalent to object[Symbol.asyncIterator]().
<KrisKowal[m]>Much obliged for the examples!
<juliana[m]>Hopefully they help! And if not Spritely folks may be able to when they're free
<KrisKowal[m]>Interesting. I could use a reference to the implementation of the methods macro.
<KrisKowal[m]>Specifically, I’m hoping to answer the question, “Is it possible for a Goblins actor to provide all of the behaviors of a function, an object with symbol methods, and an object with string-name methods (since this is currently necessary to emulate a JavaScript object)
<juliana[m]> https://spritely.institute/files/docs/guile-goblins/0.11.0/Methods.html
<KrisKowal[m]>This presumably boils down to something like a switch/case on the method name, and the method name can be any value?
<KrisKowal[m]>Or will O’Cap’n only dispatch in the two calling forms: (f nil ...args) or (f symbol ...args)?
<KrisKowal[m]>That is to say, could an actor handle (actor "toString")?
<KrisKowal[m]> * Or will O’Cap’n only dispatch in the two calling forms: (actor nil ...args) or (actor symbol ...args)?
<juliana[m]>The current Guile implementation does use let limiting method identifiers to symbols, but there's no hard-and-fast reason I'm aware of this has to be the case. The macro could be implemented differently to get support for strings. There are questions of performance and code readibility at play and I don't know all the details well enough. But at the present moment, Guile Goblins cannot handle strings as method names
<KrisKowal[m]>(Also, no urgency. Very happy with an answer in 24 hrs!)
<KrisKowal[m]>Can a single Guile actor provide both (actor nil) and (actor 'symbol) behaviors?
<KrisKowal[m]>(however nil is spelled in this part of the world)
<KrisKowal[m]>(I assume nil would be undefined in JavaScript)
<KrisKowal[m]> * (I assume nil would be `undefined` in JavaScript, `None` in Python, `nil` in Go, &c)
<juliana[m]>No
<juliana[m]>Well...
<juliana[m]>Yeah I think no is the answer
<KrisKowal[m]>I gather that it’s not likely to be expressed and might be expressible.
<KrisKowal[m]>That might be close enough to what I’m hoping, but for Hyrum’s Law, my hope is the answer is no.
<KrisKowal[m]>My impression from speaking with dthompson earlier was that Goblins actors were much more free-form lambdas.
<juliana[m]>That sounds right to my understanding
<KrisKowal[m]>I’m thinking about mutual expressivity, that no behavior hosted by JavaScript cannot be expressed by Goblins and vice versa.
<isd>Sounds like the methods macro can't, but you could in principle just write the switch yourself if you needed to do something else.
<isd>iiuc
<juliana[m]>Yes
<KrisKowal[m]>isd: This is what it looks like from here.
<KrisKowal[m]>May I ask what such a switch would look like in Scheme?
<KrisKowal[m]>(So I may use it in a Github issue)
<KrisKowal[m]>I would not pester, but I’ve been asked to descend from the æther and provide concrete examples!
<isd>Probably depends on how low level you want to go; there are common pattern matching libraries like https://www.gnu.org/software/guile/manual/html_node/Pattern-Matching.html, which I would probably use. But you could also just do `(cond ((equal? methodname "method1") (do-stuff)) ((equal? methodname "method2") (do-other-stuff)))`
<isd>...apologies if the parens are off; don't have brace matching in my matrix client...
<KrisKowal[m]>Thanks. I think that the low-level is the level I’m going for and that’s got all the atoms I wasn’t sure about. cond for and equal? should get me all the way, knowing 'symbol and "string". Oh, I need to know how to spell nil. That’s false?
<isd>#f
<KrisKowal[m]> * Thanks. I think that the low-level is the level I’m going for and that’s got all the atoms I wasn’t sure about. cond and equal? should get me all the way, knowing 'symbol and "string". Oh, I need to know how to spell nil. That’s false?
<isd>...which is semantically false as well.
<KrisKowal[m]>Aside, will #f round-trip in the data model?
<isd>That's a good question; it will map to boolean false at the wire, so I don't know what you'd map null and undefined to
<juliana[m]>'() is traditionally closer to nil
<juliana[m]>But there's also literally undefined
<isd>scheme doesn't have a direct equivalent afaik
<isd>Right, but that's also the empty list, which need to also be different.
<KrisKowal[m]>Yeah, I assume [] will arrive as '() and come back as []
<isd>Probably something is going to have to map in some hacky way if we're going to have two bottoms.
<KrisKowal[m]>As I assume undefined will arrive as undefined and come back as undefined.
<KrisKowal[m]>I assume null will arrive as indigestable tagged data and come back as null.
<isd>Yeah, we might need to do that for both null and undefined.
<isd>juliana: do you just mean the symbol `'undefined`, or is there some scheme construct I'm unaware of?
<KrisKowal[m]>This suggests that JavaScript E(f)() needs to arrive as a #f invocation.
<KrisKowal[m]> * This suggests that JavaScript E(f)() needs to arrive as a(n?) #f invocation.
<KrisKowal[m]> * This suggests that JavaScript E(f)() needs to arrive as a[n] #f invocation.
<KrisKowal[m]> * This suggests that JavaScript E(f)() needs to arrive as a/an #f invocation.
<isd>Another piece of the puzzle to think about: how are non-call pipeline operators supposed to work? E.g. in capnp you can do alice.foo().bar.baz.quux() in one round trip, but some of those are field accesses rather than calls
<KrisKowal[m]>In JavaScript, E.get(remote).baz is an async field access and our hope is to eventually have remote~.baz for that.
<KrisKowal[m]>The ~ indicates an event delimiter.
<isd>What does that look like at the protocol level?
<KrisKowal[m]>Which is to say E.get(remote).baz evaluates to a promise.
<KrisKowal[m]>I can’t speak for Agoric’s implementation, but my implementation of Q-Connection is based on the same prior art. The promise “handler” is obliged to implement “send(method, ...rest)” where “method” is “then”, “get”, “apply”, or “invoke”
<KrisKowal[m]>s/...rest/message/
<isd>Hm, so how do you distinguish .bar.baz from .bar().baz?
<isd>I guess the first would be .get('baz') and the second would be .apply().get('baz')?
<KrisKowal[m]>Not precisely.
<KrisKowal[m]>But I believe you have the gist.
<KrisKowal[m]>Precisely, E.get(E(far).bar()).baz produces an eventual reference for the baz property of the record eventually returned by the bar method of the far object.
<KrisKowal[m]>And that would be far~().baz if we got tilde operators into the language.
<KrisKowal[m]> * And that would be far~.bar()~.baz if we got tilde operators into the language.
<KrisKowal[m]>And E.get(E(far)()).baz produces an eventual reference for the baz property of eventually applying the far function.
<KrisKowal[m]>And that would be far~()~.baz if we got tilde operators.
<KrisKowal[m]> * And E.get(E(far)()).baz produces an eventual reference for the baz property of the record returned by eventually applying the far function.
<KrisKowal[m]><isd> "I guess the first would be .get(..." <- And this is a coherent statement about the handler protocol, if you assume every handler function returns a handler, as would be well and proper in Haskell.
<KrisKowal[m]>And would be true of JavaScript except we allow for auto-wrapping and auto-unwrapping of promises and eventual references.
<KrisKowal[m]>I’m sure this is slightly wrong.... (full message at <https://libera.ems.host/_matrix/media/v3/download/libera.chat/64edef2e3d4a59e3ef6a3ddc3bcb53136dd1110e>)
<KrisKowal[m]>Not sure how to express that each method might have a different signature and handle variadic other arguments differently.
<Zarutian_iPad>isd: js`E.get(foo).baz results in an equiv of op:get message on the wire that is of the form `<6'op:get< 11'desc:answer3"foo>3"baz52+>` syrup wise or something similiar
<Zarutian_iPad>ACTION does not recall exact detail
<Zarutian_iPad>but the point is there is an op:get that takes an target, an selector, and what the answer_pos should be where the eventual thing will be and is pipeline invokable
<Zarutian_iPad>btw this is pretty much same ?niche? of problem that GraphQL occupies
<KrisKowal[m]>Yes. Very similar niche. We’ve observed that here too.
<Zarutian_iPad>as I understand it Agoric is using E.get() to enable promise pipelinable access to minimize the need of .then()/E.when() for only getting a spefic suppart of an array or struct
<KrisKowal[m]>Yes.
<Zarutian_iPad>mainly because there are quite a few structs used in Agoric code to bundle together various associated things.
<KrisKowal[m]>That’s not really the proof-of-value, no.
<KrisKowal[m]>There are indeed many record-style-objects with bits and pieces you might ignore, but the whole record is going to get transferred because it’s pass-style is “copy”, regardless of how you pick it apart.
<KrisKowal[m]>E.get(record).field is much more useful because you can pass the resulting reference somewhere else immediately.
<Zarutian_iPad>so, protocol wise then why have op:deliver messages with a verb selector all?
<KrisKowal[m]>Suppose something where you’re passing the part to another party.
<Zarutian_iPad>it isnt the picking apart that is the ?proof-of-value? it is the lack of awaiting, no?
<KrisKowal[m]>Lacking await is not interesting unless you don’t have to await the result to get something interesting.
<Zarutian_iPad>that is what i was trying to say
<KrisKowal[m]>Like, if you had a remote method that created a controller and controllee pair, then you can E(alice).send(E.get(record).controller) and E(bob).send(E.get(record).controllee) without an await.
<Zarutian_iPad>but back to the wire protocol question. Given an op:get then do we really need the verb part of op:deliver?
<KrisKowal[m]>I would expect op:deliver to accept a verb of "get", not an op:get. But I have not read the work in detail.