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.

Reply via email to