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. :-)

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".

I will also explain briefly what "nodes" are, but to keep it simple, I will
try to talk about just actors and agents. If you think something I said
about agents would just as well apply to any nodes, you are probably
correct.

I think in summary we can say that there is a theme here -- FBP defines
more and narrower, more restricted things, and does earlier binding, which
I believe helps verification, reasoning and composability, and actors are
more minimalist and open-ended, which I believe helps live hacking,
simplicity in client code, and whipuptitude.

I don't believe, after considering these things, that implementing
fractalide in goblins (or vice versa) would make much sense.

I do believe though, that the models are compatible between lower and
higher levels of modeling, so that e.g. implementing an actor model like
ActivityPub might make sense to do using FBP internally (this is on my
bucket list).

Here are the specific aspects where they differ, and how they might, or
might not, be expressed in terms of each other:

- - - -

 - Inboxes vs ports

One actor, one inbox. One agent, multiple in-ports.

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.

 - 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.

If you want an agent to be able to send messages to arbitrary in-ports, you
might connect them all to an array out-port, but as I'll come back to in
the next point, it isn't as dynamic.

 - 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.

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).

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.

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.

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.

 - 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.

Actors can and probably most of the time should send messages
asynchronously.

Mapping: Not trivial.

 - 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. 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.

-- 
   /c


On Sat, May 12, 2018, 04:04 Christopher Lemmer Webber <
cweb...@dustycloud.org> wrote:

> Arie van Wingerden writes:
>
> > Hi Christopher,
> >
> > 2018-05-11 15:31 GMT+02:00 Christopher Lemmer Webber <
> cweb...@dustycloud.org:
> >
> >>
> >> In comparison to Syndicate, Goblins is less a new language and more a
> >> lightweight library for actors that interfaces nicely with "#lang
> >> racket" type code
> >> ​..
> >>
> >
> > For me personally Goblin would be the better choice I think.​
>
> If you do think that's the case (again, with the warning that it's
> pre-alpha), I'd encourage you to look at the goblins/demos/ directory
> for some examples.  But here is the main API:
>
> racket@> (require goblins)
>
> ;; Spawning an actor from a procedure
> racket@> (define counting-actor
>            (spawn
>             (let ([counter 0])
>               (lambda ()
>                 (set! counter (+ counter 1))
>                 (format "I've been called ~a times" counter)))))
>
> ;; this sends a message, but we don't get a response...
> ;; "fire and forget"
> racket@> (<- counting-actor)
>
> ;; this sends a message, but we do wait for a response...
> ;; "resume the continuation with a response"
> racket@> (<<- counting-actor)
> "I've been called 2 times"
>
> ;; Note that doing <<- outside an actor context will block the current
> ;; thread until a response comes in.  Doing <<- within an actor will
> ;; stuff this into a queue we're "waiting on responses" from, but
> ;; the actor will robustly continue to respond to messages.
>
> ;; We can also spawn Racket's classes as actors fairly easily:
> racket@> (define greeter%
>            (class object%
>              (super-new)
>              (define/public (greet name)
>                (string-append "Hello, " name "!"))))
> ;; And we can send messages to their public methods
> racket@> (<<- (spawn-new greeter%) 'greet "Bert")
> "Hello, Bert!"
>
> ;; Actors are shut down when all references to them are garbage
> ;; collected, so we don't need to worry about doing a one-off
> ;; spawn as we did above.
>
> ;; Subclassing of course works too.
> racket@> (define groucher%
>            (class greeter%
>              (super-new)
>              (init [annoyed-by "my back"])
>              (define am-annoyed-by annoyed-by)
>              (define/override (greet name)
>                (string-append "Grumble grumble... "
>                               (super greet name)
>                               "... "
>                               am-annoyed-by " is irritating me..."))))
> racket@> (<<- (spawn-new groucher%
>                          [annoyed-by "your hair"])
>               'greet "Grover")
> "Grumble grumble... Hello, Grover!... your hair is irritating me..."
>
> ;; Here's a more complex example that demonstrates using our own address
> ;; with (self)
> racket@> (let* ([narrator
>                  (spawn
>                   (lambda (msg)
>                     (displayln msg)))]
>                 [gossiper
>                  (spawn-new (class object%
>                               (super-new)
>                               (define listeners '())
>                               (define/public (subscribe address)
>                                 ;; We don't care about the order of this
>                                 ;; so fire and forget
>                                 (<- narrator "gossiper> Got a new
> subscriber!")
>                                 (set! listeners (cons address listeners)))
>                               (define/public (spread-gossip msg)
>                                 (<- narrator
>                                     (format "gossiper> Spreading juicy
> gossip: ~a"
>                                             msg))
>                                 (for ([listener listeners])
>                                   (<- listener 'receive-gossip msg)))))]
>                 [listener%
>                  (class object%
>                    (super-new)
>                    (init name)
>                    (define our-name name)
>                    (define/public (start-listening)
>                      ;; We're going to wait here because we don't want to
> return
>                      ;; until this completes
>                      ;; (self) is our own actor address and is bound
> dynamically
>                      ;; in a message handling context
>                      (<<- gossiper 'subscribe (self)))
>                    (define/public (receive-gossip msg)
>                      (<- narrator
>                          (format "~a> Ooh, I just heard that ~a"
>                                  our-name msg))))]
>                 [sam (spawn-new listener% [name "sam"])]
>                 [pat (spawn-new listener% [name "pat"])])
>            (<<- sam 'start-listening)
>            (<<- gossiper 'spread-gossip "Tim has great hair")
>            (<<- pat 'start-listening)
>            (<<- gossiper 'spread-gossip "Samantha has a nice car"))
> gossiper> Got a new subscriber!
> gossiper> Spreading juicy gossip: Tim has great hair
> sam> Ooh, I just heard that Tim has great hair
> gossiper> Got a new subscriber!
> gossiper> Spreading juicy gossip: Samantha has a nice car
> pat> Ooh, I just heard that Samantha has a nice car
> racket@> sam> Ooh, I just heard that Samantha has a nice car
>
> ;; In fact you can see on that last one that since talking to the
> ;; gossiper is asynchronous with <-, it clobbered my racket prompt ;)
>
> ====== End tutorial ======
>
> Hope that was interesting!
> As you can see, it tries very hard to look a lot like racket code.
> I used to have addresses even be callable as if
> (address args ...) was implicitly the same as (<<- address args)
> but since Goblins actors are "robust", I didn't want people to be
> surprised that their actors may respond to other messages while waiting
> for replies to their continuation with <<-.
> But when I had it like that it didn't even look like message passing,
> just normal procedure calls, which in a way was fun :)
>
>

-- 
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