Claes Wallin (韋嘉誠) writes: > Hey, Chris! > > Wow, that was very easy to read, even in the mail client on my phone! I am > officially impressed. > > As you know we're working on a Flow-Based Programming framework within > fractalide (github:fractalide/fractalide, which also has a pre-existing > rust implementation of FBP) and I've been thinking of (because of something > you said the other week) what the difference really is between actors and > FBP agents. > > I'm new at this, so if I misspeak I hope Stewart will step in and correct > me. :-)
I'm also new to FBP so we're in good company together. > Consider this my way of learning by thinking out loud. > > I went into your message with the outset of "what would it look like if we > implemented fractalide agents as goblins actors?", which turned out to be a > really good approach for figuring out what the differences are at least > between these two particular takes on actors and agents. I'm not read up on > the academic definitions, so "actors" below means specifically "goblins > actors", "agents" means "fractalide agents". Sounds like a good framing. > I will also explain briefly what "nodes" are, but to keep it simple, I will > - Inboxes vs ports > > One actor, one inbox. One agent, multiple in-ports. This also makes FBP sound similar to CSP. CSP and actors are known to be duals: https://en.wikipedia.org/wiki/Communicating_sequential_processes#Comparison_with_the_actor_model You can build either on top of each other and in fact, Goblins is built on top of Racket's CSP model. However I'm not sure if this is also true of FBP and actors, since it sounds like FBP may be more of an "etched circuit"? > Mapping: Your example already shows that you can have method dispatch which > could be seen as an emulation of multiple in-ports. I take it each > class-based actor is single-threaded, so it acts in a serialized manner on > the actor/object state? > > In the reverse direction, of course you can have an agent with one in-port. I guess there's no reason you couldn't interpret it that way. Though, one "feature" about CSP is that a process can have say, two ports, and choose only to listen to one port at this particular moment. That bothers me though actually, because it opens up the possibility of live-locking (CSP is a lot more like an asynchronous state machine in that respect). By contrast, an actor's definition is that it can do the following things: handle a message, send a message, spawn another actor, and configure how it will respond to the next message (this last one is critical to how <<- works in Goblins). But it is always "returning to" the same inbox, even if it can have multiple dispatches there. In FBP I get the impression that an entity might possibly only have access to a single port of another entity, even if the receiving agent does have multiple ports. Having an actor address allows you to send messages to any of the methods potentially in the actor model, but we can get away with a narrower scope by setting up a "facade", or a proxy actor that passes along some messages but not others, and hand the reference to the facade instead. > - Inversion of control, out-ports > > Actors know about inboxes they want to talk to, agents only read from their > in-ports and write to their out-ports. This makes agents composable. The > actual unit of composition is the "node", where a node is an agent or a > subgraph, and subgraphs have virtual edges from the subgraph's ports to the > internally unconnected ports of the nodes it contains. In the live graph, > all edges are directly between agents. > > Mapping: Actors can be internally written in this way, and then have their > out-ports assigned at instantiation or with some initial message. Slight aside: As for composition in actors, thinking about composition in terms of how we do it in the lambda calculus is pretty good since it's really the same thing. In general, actors only have references to the other actors they have reference to, usually (at least in ocap actor systems like E) via the same lexical scope mechanisms that power scheme. This is kind of a side note, but actors and the lambda calculus can both be object capability secure and the way that it's done is pretty much the same in both cases: principle of least authority by actors only having access to the things they have access to, either via their initial environment or because they were handed access through other message passing. Same thing with lambda calculus. For more on this I recommend reading up on E for the actors case and W7 for the scheme/lambda calculus case. Secure composition is thus trivial. I encourage reading this amazing paper in particular: http://mumble.net/~jar/pubs/secureos/secureos.html Which is, in fact, my favorite paper of all time. > - Dynamic vs static > > This shows in several aspects of how the two approaches work: > > First, unless it's an explicit array-port, a port binds to exactly one edge. That does seem to be a major difference. This seems to match my idea that FBP is an "etched circuit". Do you agree with that description? By contrast, actors can freely send references to other actors at runtime. > Second, an edge is a reified thing and the edge is created at > composition-time. Now, in the racket scheduler composition-time means > runtime, it's just a procedure call "connect this to that", but in the rust > scheduler composition happens on the Nix level. You get an error about > doubly-connected or unconnected ports before you even run the thing (I > believe). Ah yes, that's a major difference. You could, of course, write up a fixed actor model where you simply never connect more than one actor connection together, but it wouldn't likely be verified via static analysis. > An actor "edge" just means an actor holds a reference to somebody's inbox > (capabilities yay), and messages in the inbox could come from anyone > holding a reference. Actors could even pass the inboxes of other actors to > each other. Yup, you've got it. (Though I'd say "pass the addresses" rather than "pass the inboxes".) > Mapping: You can certainly have actors express edges with the inversion of > control pattern described above. > > In the reverse direction, inboxes cannot be trivially expressed as ports. > You can't e.g. have an agent hand over one of its out-ports to another > without violating the expectations of the model. That seems true. > Third, currently we're doing this in untyped racket with typed racket in > places, but ideally all messages would be typed, as they obviously are on > the rust side. But then, actors could choose to type-check their messages > too. Right, and so you probably very frequently want to set up contracts on your message handlers. In fact, in the case of a *distributed* actor system (which Goblins will support soon) across processes or machines or what have you, contract-style type checking may be the best you can do at the Racket boundary. Consider an ocap secure system where I don't necessarily know the code of a remote actor (or know if it's telling the truth). I can't do static analysis based on types securely (some voluntary sharing may be done but we can't be sure the other end isn't trying to trick us into an optimization that isn't actually true, and have us say, read out of bounds of an array or something) so the best we can do is to do type checking on the boundary level. > - Event-driven vs data-driven > > Agents are written as sequential code interacting with bounded buffers, > using "recv" and "send". Unless specifically told not to ("try-recv"), > every operation blocks on missing data or busy receiver. This provides > automatic back-pressure in the system, and data moves in "waves" through > the graph. Sounds very CSP-like indeed with "channels" instead of "ports" (except as I'm seeing, one difference is that CSP systems, like actor systems, a reference to another "channel" can be sent across another channel to another process... the same does not seem true of agents and ports). > Actors can and probably most of the time should send messages > asynchronously. Yes, and as for backpressure, one does have the option to implement queue limits on actors (and I will be adding this eventually). There's also a bit of differing opinions, as I understand it, on how much emphasis should be placed on consistency versus robustness. I had the opportunity to speak to Carl Hewitt about this, who is aboslutely in the robustness camp to the point of advocating that nondetermism is a feature of actors. On the other hand I think E handles "turns" (a single handling of a message) very carefully and under the hood semi-functionally, and you can "rewind" a vat of actors (vats are meta-processes that control actors in some actor systems) to previous state. (Of course, that won't rewind all vats in a possibly large and distributed system.) > - Resulting usage patterns > > I expect the technical differences to result in differing preferred ways of > working with the systems. I haven't used either very much, but I imagine > that e.g. if you want a response to your message you would as an actor pass > your inbox with your query message, whereas in FBP you just connect your > request out-port and response in-port to the request in-port and response > out-port of the other node, and that's simply the way those messages will > go. It's possible to request a response by handing a reference to yourself to the other actor, but there's an even easier way in Goblins. It's not a "standard" actor feature (though it appears in some other actor systems), but it's one of Goblins' best features: the <<- operator. (spawn (lambda (my-friend) (displayln (format "My friend's favorite color is ~a" (<<- my-friend 'get-favorite-color))))) What's actually happening here? Unlike <- which just "fires and forgets", <<- actually suspends the code at its call point to a prompt and stores the continuation in a queue of coroutines that are "waiting on a response". This actor is robustly able to continue handling messages while it's waiting... no need to be held up. (This is what I meant by previously saying that we take advantage of actors being able to "declare how they handle the next message"... one thing we can do is to declare that we'll keep handling other messages, but that we have a coroutine to resume once a reply comes back.) When my-friend's handler completes, the actor code for my-friend checks if the message it was handling was expecting a reply, and in this case it is. It then takes whatever value was returned from the handler and sends it back to the original sender who was "waiting on the reply". So our original actor here does get a message in that sees it's "in-reply-to" its previous message where it was asking 'get-favorite-color. It then resumes the continuation/coroutine of the message handler that had suspended with <<- and carries on its way. (Errors can also be back-propagated... or at least, will be able to be shortly, that's something I haven't finished porting from 8sync yet.) I think this is one of Goblins' nicest features: it effectively "hides" promises and makes doing this "waiting on a response" code look like plain ol' straight-ahead coding. (The observant reader may notice one downside however: in cases where we're "waiting on a reply" like this, we might not want to continue handling other messages until the "correspondence" absolutely completes... in other words, we want to block *ourselves*. If this isn't obvious, consider the case of a convenience store clerk who has the job to answer a customer's question about "I would like to buy this item, do you have it", and then if so ask for the right amount of change, and then hand the customer the item. What if after confirming the item was available (but it was the last one in stock) and while waiting for the customer to fish their funds from their wallet, another customer asks for that same item? If we handled their message and said that the item was available simply because we were handling other messages, that would be bad... we should know that we need to complete this transaction with this customer. For that reason I am planning to implement a kind of <-block operation, which might be done by making sending a message that wants a reply a synchronizable event.) > As for array ports, the connections can be named, so you can name them > to match. > > Phew. There's probably more to be said, but I think this is a decent chunk > to bite off as it is. Yep, lots said already... thanks for taking the time to explore with me! -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.