From: Steven Parkes Date: 2008-07-27T10:43:37+09:00 Subject: Re: DSL/thread design question > From: David Masover [mailto:ninja@slaphack.com] > Keep in mind, I don't care about implementation at this > point, but design. Well, we're together on that, though, of course, peoples' opinion of design differs. > (I find Erlang ugly.) Beyond that, it's predominantly functional, not object-oriented, and not used widely for general purpose development. I could get over the syntax but ... > > release( s ).push(1,2,3,4,5) => nil > > What does "release" do, in this context? And why not make it > a method on the > wrapped object? It generates a new name that when used to call methods, calls the methods asynchronously/with a null continuation. s.push is a sync/rpc-style call, release( s ).push is async. release does this by returning a new name with different continuation semantics. It's all encapsulated within the name. I didn't make it a method because I didn't want it to always have to be there, i.e., I wanted to be able to use s.push in the simple case (as opposed to s.sync.push and s.async.push). With that constraint, I didn't want to make it a method because it impinges on the namespace of the serial behavior of the actor, i.e., if sync is the default, and you have to say s.async to get async, you can't (easily) use an #async method on the actor itself. It's important to me that there be no methods on the proxy that are aimed at the proxy rather than what the proxy points at. > I would much rather use GC, if it would work. I'm not sure > how to make GC work > here, though -- and certainly not for one thread/actor. Yeah: you could have a problem with the thread-per-actor because it might not be clear when the actor is not actually doing anything (it can't be GC'd while its doing something)? But GC is hard when you move to distributed anyway: distributed GC is hard, which might be reason enough to bag it. I haven't gone there yet. > However, Java threads are probably heavier than YARV threads. > Just a guess. Actually, if I had to guess, I'd guess the opposite. They're both kernel scheduled threads and there's a heck of a lot more experience with threads in Java than there is in 1.9. > I do want to know how "async by default" was painful, though. In my code, sync calls, for example for status, were fairly common. Requiring all those to have something explicit to make sync work was painful. Taking a trivial call like "other.status" and exploding it to lots more characters or multiple lines gets old fast. In my eyes, anyway. I really want code that looks serial to do the right serial thing, even if the objects are actors. So far, this works in dramatis. If you're doing sync calls w/o an explicit receive, you might want to look at Erlang/OTP's gen_server behaviour: it does that (and raises the selective receive issues). > s.push(...).now > s[...] Yup; just using a sync call even if you don't need the value is common and valid way of generating the necessary control flow. Brings up selective receive again, though. Can the calling actor receive any other messages while it's waiting for #now? That's one of the trickier things to handle in actor programs. > Well, I think the problem with this is, what happens to > anyone else who wants > to send something to the actor? Is the actor still in a valid > state after > this? If the exception is delivered to the caller, the actor remains in a fine state. It's pretty much what Erlang does if you manually catch the exception and forward it. But given the wide variety of exceptions that can occur, maybe sending the exception up is a poor default. And you can't do it in the async case, anyway, so ... But this introduces a big difference between serial and actor code even in the rpc case, which I don't like. So I don't know .... I> In single-threaded code, it's easy -- it's up to the caller. Right. There's no ambiguity. No choice. Here there's a choice. As soon as you have multiple actors, you have multiple stacks and in theory you can send the exception up either. I have cases where both are useful but I don't have anyway of making the runtime figure out the right way to handle things except making it explicit. > That brings up other interesting problems -- how do you > handle the main > thread? There are two parts to this. Any thread that the runtime didn't create is considered external/exogenous and in order to fit it into the actor model, a pseudo-actor is created for it if necessary (when, for example, it needs to accept the response from an rpc-like call). The other issue unique to main is keeping it from exiting when there is actor work to be done. I use an at_exit handler for that. > And how are we catching this, then? A method, maybe -- something like > linked_process_died? Something similar to that. More likely I'll provide a method that takes a block: if you want to catch an exception signal (using Erlang terminology), the actor calls this method with the block that it wants to get the signal. That block will be called when the signal is received, in which case the recipient won't be killed. This is more or less what Erlang does (I forget the BIF you call to do this.) This is getting pretty deep into the guts. I started a list a few weeks ago for people discussing actor issues across languages/implementations: http://groups.google.com/group/actor-talk. Would it make more sense to do this there? There's also a list for dramatis (http://groups.google.com/group/dramatis) but if you just want to compare, actor-talk is probably better.