On this page:
3.1 Actors
3.1.1 What is an "actor" in Goblins?
3.1.2 Constructors and bcom
3.2 Core procedures
spawn
$
<-
<-np
on
3.3 References
refr?
3.3.1 Live vs Sturdy references
live-refr?
sturdy-refr?
3.3.2 Near vs far references
near-refr?
3.3.3 Local vs remote references
3.4 Actormaps
make-actormap
3.4.1 Actormap methods
actormap?
actormap-spawn
actormap-spawn!
actormap-turn
actormap-reckless-poke!
actormap-poke!
actormap-peek
actormap-run
actormap-run!
3.4.2 whactormap
make-whactormap
whactormap?
3.4.3 transactormap
transactormap?
make-transactormap
transactormap-merge!
transactormap-merged?
transactormap-parent
transactormap-delta
3.4.4 Snapshotting and restoring actormaps
snapshot-whactormap
hasheq->whactormap
3.4.5 Extra actormap procedures
3.4.6 Vat connectors
3.5 Vats
make-vat
3.6 Promises
spawn-promise-values
spawn-promise-cons
3.7 Machines
7.7

3 Goblins API

 (require goblins) package: goblins

3.1 Actors

3.1.1 What is an "actor" in Goblins?

Goblins implements the actor model on top of Racket / scheme.1Sussman and Steele famously came to the conclusion that there was in fact no difference between actor-style message passing and procedure application in the lambda calculus, and indeed both are similar. However, there is a significant difference between synchronous call-and-return procedure application (which is what scheme implements in its most general form, and between actors in Goblins is handled by $) and asynchronous message passing (which in Goblins is handled with <-).

The fundamental operations of actors2Emphasis on "fundamental" operations. Extensions happen from here and vary widely between different systems that call themselves "actors". are:

Goblins’ extensions to these ideas are:

3.1.2 Constructors and bcom

A constructor is a procedure which builds the first message handler an actor will use to process messages / invocations. The constructor has one mandatory argument, traditionally called bcom (pronounced "become" or "bee-com") which can be used to set up a new message handler for future invocations.

> (require goblins)
> (define am (make-actormap))
; Outer procedure is the constructor.
; Implicitly takes a bcom argument.
; count argument may or may not be supplied by spawner.
> (define (^noisy-incrementer bcom [count 0])
    ; Our message handler.
    (lambda ([increment-by 1])
      (let ([new-count (+ count increment-by)])
        ; Here we create a new version of ^noisy-incrementer
        ; with count scoped to a new incremented version.
        ; The second argument to bcom specifies a return value,
        ; would return void if unspecified.
        (bcom (^noisy-incrementer bcom new-count)
              (format "My new count is: ~a" new-count)))))
> (define incr1
    (actormap-spawn! am ^noisy-incrementer))
> (actormap-poke! am incr1)

"My new count is: 1"

> (actormap-poke! am incr1 20)

"My new count is: 21"

> (define incr2
    (actormap-spawn! am ^noisy-incrementer 18))
> (actormap-poke! am incr2 42)

"My new count is: 60"

bcom, as shown above, is a capability (or technically a "sealer") to become another object. However, bcom does not apply a side effect; instead, it wraps the procedure and must be returned from the actor handler to set that to be its new message handler. Since this clobbers the space we would normally use to return a value (for whatever is waiting on the other end of a $ or a promise), bcom supports an optional second argument, which is that return value. If not provided, this defaults to (void).

1Sussman and Steele famously came to the conclusion that there was in fact no difference between actor-style message passing and procedure application in the lambda calculus, and indeed both are similar. However, there is a significant difference between synchronous call-and-return procedure application (which is what scheme implements in its most general form, and between actors in Goblins is handled by $) and asynchronous message passing (which in Goblins is handled with <-).

2Emphasis on "fundamental" operations. Extensions happen from here and vary widely between different systems that call themselves "actors".

3For more on why this is, see chapters 13-15 of Mark Miller’s dissertation.. This was very influential on Goblins’ design, including the decision to move from a coroutine-centric approach to an E-style promise approach. It could be that coroutines are re-added, but would have to be done with extreme care; section 18.2 of that same thesis for an explaination of the challenges and a possible solution for introducing coroutines.

4In this sense, <-np, which does not return a promise, is closer to the foundational actors message passing. The kind of value that promises give us can be constructed manually by providing return addresses to <-np, but this is painful given how common needing to operate on the result of an operation is.

5Scheme is a beautiful language to build Goblins on top of, but actually we could build a Goblins-like abstraction layer on top of any programming language with sane lexical scoping and weak hash tables (so that actors which are no longer referenced can be garbage collected).

3.2 Core procedures

The following procedures are the core API used to write Goblins code. All of them must be run within an "actor context", which is to say either from an actor running within a vat or an actormap or within one of the procedures used to bootstrap the vat/actormap.

procedure

(spawn constructor argument ...)  live-refr?

  constructor : procedure?
  argument : any/c
Spawn an actor built by constructor with the rest of the arguments being passed to that constructor. Returns a live reference to the newly spawned actor.

The constructor is, as the name sounds, a goblins constructor, and is first passed a bcom argument (which is a way specify how it will behave on its next invocation) and then is passed the remaining arguments.

procedure

($ actor-refr arg ...)  any/c

  actor-refr : near-refr?
  arg : any/c
Pronounced "call", "dollar call", or "money-call".1Why "money call"? Because you need $ to make distributed ocap financial instruments!

Provide a synchronous call against the current message handler of actor-refr, which must be a near live-refr? in the same vat as the currently running actor context. The value returned is that which is returned by the actor-refr’s message handler upon invocation (or, if actor-ref chose to bcom something else, the second argument passed to its bcom, or void if not provided.) Exceptions raised by actor-refr’s invocation will be propagated and can be captured.

Note that excape continuations can be set up between a caller of $ and can be used by the callee. However, a continuation barrier is installed and so capturing of the continuation is not possible. (Note that in the future this may be relaxed so that async/await coroutines can be used with mutual consent of caller/callee; whether or not this is a good idea is up for debate.)

procedure

(<- actor-refr arg ...)  [promise live-refr?]

  actor-refr : live-refr?
  arg : any/c
Like $, but called asynchronously and returns a promise, which may be handled with on. Unlike $, <- is not limited to only near actor-refrs; interaction with far actors are permitted as well.

procedure

(<-np actor-refr arg ...)  void?

  actor-refr : live-refr?
  arg : any/c
Like <- but does not return (or require the overhead of) a promise. <-np is effectively an optimization; where promises are not needed, using <-np over <- is a good idea.

procedure

(on vow    
  [on-fulfilled    
  #:catch on-broken    
  #:finally on-finally    
  #:promise? promise?])  (or/c live-refr? void?)
  vow : (or/c live-refr? any/c)
  on-fulfilled : (or/c procedure? refr? #f) = #f
  on-broken : (or/c procedure? refr? #f) = #f
  on-finally : (or/c procedure? refr? #f) = #f
  promise? : bool? = #f
Sets up promise handlers for vow, which is typically a live-refr? to a promise but may be anything.

on-fulfilled, on-broken, and on-finally all, if specified, may be either a procedure (the most common case) which is run when vow becomes resolved, or a reference to an actor that should be messaged when the promise is resolved with the same arguments that would be passed to an equivalent procedure. As their names suggest, on-fulfilled will run if a promise is fulfilled and takes one argument, the fulfilled value. on-broken will run if a promise is broken and takes one argument, the error value. on-finally will run when a promise is resolved, no matter the outcome and is called with no arguments.

If #:promise? is set to #t, then on will itself return a promise. The promise will be resolved as follows:

1Why "money call"? Because you need $ to make distributed ocap financial instruments!

3.3 References

A reference is a capability to communicate with an actor. References are an indirection and abstractly correspond to an actor handler in an actormap somewhere, often in a vat.

procedure

(refr? obj)  bool?

  obj : any/c
Returns #t if obj is a reference.

3.3.1 Live vs Sturdy references

The most common kind of reference is a live reference, meaning that they correspond to some actor which we have an established connection to. However some references may be sturdy references, meaning they are serialized in such a way that they probably refer to some actor, but the connection to it is dormant in this reference itself. A sturdy reference must be enlivened with enliven before it can be used. (TODO: or do we just want to reuse <-? Dunno.)

NOTE: Sturdy references aren’t implemented yet, and neither is enliven. Change that!

procedure

(live-refr? obj)  bool?

  obj : any/c
Returns #t if obj is a live reference.

procedure

(sturdy-refr? obj)  bool?

  obj : any/c
Returns #t if obj is a sturdy reference.

TODO: Define and document enliven, maybe.

3.3.2 Near vs far references

An actor is near if it is in the same vat as the actor being called in an actor context. An actor is far if it is not. The significance here is that only near actors may perform immediate calls with $, whereas any actor may perform asynchronous message sends with <- and <-np.

procedure

(near-refr? obj)  bool?

  obj : any/c
Returns #t if obj is a near reference.

3.3.3 Local vs remote references

Well, once machines exist, this will matter, but they don’t yet :P

3.4 Actormaps

An actormap is the key abstraction that maps actor references to their current method handlers. There are actually two kinds of actormaps, whactormaps and transactormaps.

procedure

(make-actormap [#:vat-connector vat-connector])  whactormap?

  vat-connector : (or/c procedure? #f) = #f
Alias for make-whactormap, since this is the most common actormap users make.

Actormaps are also wrapped by vats. More commonly, users will use vats than actormaps directly; however, there are some powerful aspects to doing so, namely for strictly-synchronous programs (such as games) or for snapshotting actormaps for time-traveling purposese.

In general, there are really two key operations for operating on actormaps. The first is actormap-spawn, which is really just used to bootstrap an actormap with some interesting actors. Actors then operate on turns, which are basically a top-level invocation; the core operation for that is actormap-turn. This can be thought of as like a toplevel invocation of a procedure at a REPL: the procedure called may create other objects, instantiate and call other procedures, etc, but (unless some portion of computation goes into an infinite loop) will eventually return to the REPL with some value. Actormap turns are similar; actors may do anything that actors can normally do within the turn, including spawning new actors and calling other actors, but the turn should ideally end in some result (as well as some new messages to possibly dispatch).1Due to the halting problem, this cannot be pre-guaranteed in a turing-complete environment such as what Goblins runs in. Actors can indeed go into an infinite loop; in general the security model of Goblins is to assume that actors in the same vat can thus "hose their vat" (but really this means, an actormap turn might not end on its own, and vats currently don’t try to stop it). Pre-emption can be layered manually though when operating on the actormap directly; if you want to do this, see Suspending, Resuming, and Killing Threads.

3.4.1 Actormap methods

procedure

(actormap? obj)  bool?

  obj : any/c
Determines if obj is an actormap.

procedure

(actormap-spawn actormap constructor arg ...)

  
[actor-refr live-refr?]
[new-actormap transactormap?]
  actormap : actormap?
  constructor : procedure?
  arg : any/c
Like spawn, but low-level and transactional. Returns two values to its continuation, the new actor live reference, and a transactormap representing the change.

procedure

(actormap-spawn! actormap    
  constructor    
  arg ...)  [actor-refr live-refr?]
  actormap : actormap?
  constructor : procedure?
  arg : any/c
Like actormap-spawn!, but directly commits the actor to actormap. Only returns the tech{reference} of the new actor. No changes are committed in an exceptional condition.

procedure

(actormap-turn actormap to-refr args ...)

  
[result any/c]
[new-actormap transactormap?]
[to-near (listof message?)]
[to-far (listof message?)]
  actormap : actormap?
  to-refr : live-refr?
  args : any/c
Similar to performing $, applying args to to-refr, but transactional and a little bit cumbersome to use. (In many cases, you’ll prefer to use actormap-peek, actormap-poke!, actormap-run, or actormap-run! which are easier.) Returns four values to its continuation: the result of applying args to to-refr, a transactional new actormap, and two lists of messages that may need to be sent (one to near actors, one to far actors).

procedure

(actormap-reckless-poke! actormap    
  to-refr    
  arg ...)  [actor-refr live-refr?]
  actormap : whactormap?
  to-refr : live-refr?
  arg : any/c
Like actormap-poke!, but only usable on a whactormap and mutates all bcom-effects immediately to the mapping. A little bit faster but non-transactional... corrupt state can occur in the case of exceptional conditions, as the system will not "roll back". Use with caution!

procedure

(actormap-poke! actormap to-refr args ...)  any/c

  actormap : actormap?
  to-refr : live-refr?
  args : any/c
Similar to performing $, applying args to to-refr. Commits its result immediately, barring an exceptional condition.

procedure

(actormap-peek actormap to-refr args ...)  void?

  actormap : actormap?
  to-refr : live-refr?
  args : any/c
Like actormap-poke!, but does not commit its result. Useful for interrogating an actor in an actormap without allowing for become-effects within it.

procedure

(actormap-run actormap proc)  any/c

  actormap : actormap?
  proc : (-> any/c)
Run proc, which is a thunk (procedure with no arguments) in the actormap context, but do not commit its results, instead returning its value.

Like actormap-peek, this is useful for interrogating an actormap, but can be useful for doing several things at once.

procedure

(actormap-run! actormap proc)  any/c

  actormap : actormap?
  proc : (-> any/c)
Like actormap-run but, barring exceptional conditions, does commit its results.

3.4.2 whactormap

A whactormap is the default kind of actormap; uses a weak hashtable for mapping.

procedure

(make-whactormap [#:vat-connector vat-connector])  whactormap?

  vat-connector : (or/c procedure? #f) = #f
Makes a weak hashtable actormap. Used to mutably track the current state of actors.

procedure

(whactormap? obj)  bool?

  obj : any/c
Determines if obj is a whactormap.

3.4.3 transactormap

A transactormap is an actormap that stores a delta of its changes and points at a previous actormap. It must be committed using transactormap-commit! before its changes officially make it into its parent.

procedure

(transactormap? obj)  bool?

  obj : any/c
Returns #t if obj is a transactormap.

procedure

(make-transactormap parent 
  [#:vat-connector vat-connector]) 
  transactormap?
  parent : actormap?
  vat-connector : (or/c procedure? #f) = #f
Makes a new transactormap which is not yet committed and does not have any new changes. It is unlikely you will need this procedure, since actormap-turn, actormap-spawn and friends produce it for you.

procedure

(transactormap-merge! transactormap)  void/c

  transactormap : transactormap?
Recursively merges this and any parent transactormaps until it reaches the root whactormap.

Note that creating two forking timelines of transactormaps upon a whactormap and merging them may corrupt your whactormap.

procedure

(transactormap-merged? transactormap)  bool?

  transactormap : transactormap?
Returns #t if this transactormap has been merged.

procedure

(transactormap-parent transactormap)  actormap?

  transactormap : transactormap?
Returns the parent actormap of this transactormap.

procedure

(transactormap-delta transactormap)  hasheq?

  transactormap : transactormap?
Returns the delta of changes to this transactormap. Mutating this yourself is not prevented but is highly inadvisable.

3.4.4 Snapshotting and restoring actormaps

procedure

(snapshot-whactormap whactormap)  hasheq?

  whactormap : whactormap?
Snapshots a whactormap by transforming it into a hasheq? table mapping references to ephemeron wrapped actor handlers.

procedure

(hasheq->whactormap ht)  whactormap?

  ht : hasheq?
Restores a whactormap from the ht snapshot.

3.4.5 Extra actormap procedures

 (require (submod goblins/core actormap-extra))

These are very low level but can be useful for interrogating an actormap.

TODO: document.

3.4.6 Vat connectors

Somewhat awkwardly named since they most visibly show up in actormaps, a vat connector is a procedure (or #f) which is attached to an actormap. It serves two purposes:

If you are using make-actormap, this defaults to #f, meaning that all other actors that also have no vat connector will assume they are likewise near. This of course also means that an actor which is in a vat will have no way of communicate with an actor which isn’t.

On the other hand, vats built with make-vat set up their own vat connectors for you.

1Due to the halting problem, this cannot be pre-guaranteed in a turing-complete environment such as what Goblins runs in. Actors can indeed go into an infinite loop; in general the security model of Goblins is to assume that actors in the same vat can thus "hose their vat" (but really this means, an actormap turn might not end on its own, and vats currently don’t try to stop it). Pre-emption can be layered manually though when operating on the actormap directly; if you want to do this, see Suspending, Resuming, and Killing Threads.

2There is one case in which this could be misleading: if both references are spawned in different actormaps that have no vat connector (ie, it is #f), then they likely won’t appear in each others’ vats.

3.5 Vats

A vat1"Vat" might strike you as a strange name; if so, you’re not alone. The term apparently refers to the musing, "How do you know if you’re a brain in a vat?" Previously "vat" was called "hive" in Goblins (and its predecessors, 8sync and XUDD); it was an independent discovery of the same concept in E. Initially, Goblins stuck with "hive" because the primary author of Goblins thought it was a more descriptive term; various ocap people implored the author to not further fragment ocap vocabulary and so the term was switched. Since then, a number of readers of this documentation have complained that "vat" is confusing, and upon hearing this story have asked for the term to be switched back. Whether it’s better to avoid naming fragmentation or possibly increase naming clarity is currently up for debate; your feedback welcome! is an event loop that wraps an actormap. In most cases, users will use vats rather than the more low-level actormaps.

Actors in vats can communicate with actors in other vats on the same machine over a vat connector. For inter-machine communication, see machines. Nonetheless, for the most part users don’t need to worry about this as most inter-vat communication happens using <-.

procedure

(make-vat)  procedure?

Starts up a vat event loop.

Returns a procedure that can be invoked to communicate with the vat (documented below).

The returned procedure uses symbol-based method dispatch.

The procedure returned from make-vat is called the vat dispatcher and is mostly used for bootstrapping or otherwise poking at a vat. Once the actors are bootstrapped in a vat they tend to do their own thing.

The vat dispatcher supports the following symbol-dispatched methods:

1"Vat" might strike you as a strange name; if so, you’re not alone. The term apparently refers to the musing, "How do you know if you’re a brain in a vat?" Previously "vat" was called "hive" in Goblins (and its predecessors, 8sync and XUDD); it was an independent discovery of the same concept in E. Initially, Goblins stuck with "hive" because the primary author of Goblins thought it was a more descriptive term; various ocap people implored the author to not further fragment ocap vocabulary and so the term was switched. Since then, a number of readers of this documentation have complained that "vat" is confusing, and upon hearing this story have asked for the term to be switched back. Whether it’s better to avoid naming fragmentation or possibly increase naming clarity is currently up for debate; your feedback welcome!

3.6 Promises

An eventual send with <- returns a promise whose resolution will happen at some future time, either being fulfilled or broken. Fulfilled promises have resolved to a value, whereas broken promises have been resolved with an error.

Every promise has a corresponding resolver which is messaged (often implicitly on completion of a turn)

It turns out that it is possible to make your own promises using spawn-promise-values or spawn-promise-cons, both of which return a promise / resolver pair.

procedure

(spawn-promise-values)  
[promise live-refr?]
[resolver live-refr?]
Spawns and returns two values to its continuation: a "promise" and a "resolver".

procedure

(spawn-promise-cons)

  (cons/c [promise live-refr?] [resolver live-refr?])
Just like spawn-promise-values but returns a cons cell of the "promise"/"resolver" pair rather than returning multiple values. This requires an extra allocation (and thus destructuring on the receiving side) but can be convenient since actors can only return one value from their message handler at a time.

Promises in Goblins work closer to E than some other languages like Javascript; notable exceptions are that on is used rather than ".then() sausages". Likewise, having a separate resolver object also comes from E.

One major, but perhaps not very important, difference that may not be obvious is that once a promise pointed to by a reference is resolved to something, it for all intents and purposes appears to act just like that thing. If the resolution is to a near reference, it can even be immediately called with "$". However, you still need to know when you can finally dollar-call such a thing, thus you still need to use on anyway.

3.7 Machines

A machine is another layer of abstraction you don’t need to worry about... yet! ;)