5.3.4.10
2 High-level interface
This high-level interface between a VM and a process is analogous to
+the C library interface of a Unix-like operating system. The
+Low-level interface corresponds to the system call
+interface of a Unix-like operating system.
2.1 Using #lang marketplace and friends
Programs written for Marketplace differ from normal Racket modules
+only in their selection of language. A Racket module written with
+#lang marketplace, such as the echo server in
+TCP echo server, specifies a sequence of definitions
+and startup actions for an application. Typically, initial
+actions spawn application processes and nested VMs, which in turn
+subscribe to sources of events from the outside world.
There are a handful of variant languages to choose from:
marketplace is for untyped programs, and uses
+the tcp-bare TCP driver;
marketplace/flow-control is like
+marketplace, but uses the flow-controlled tcp
+driver;
marketplace/typed is like marketplace, but
+for typed programs;
marketplace/typed/flow-control is like
+marketplace/flow-control, but for typed programs.
2.2 Using Marketplace as a library
Instead of using Racket’s #lang feature, ordinary Racket programs
+can use Marketplace features by requiring Marketplace modules
+directly.
Such programs need to use ground-vm/ground-vm: to
+start the ground-level VM explicitly. They also need to explicitly
+start any drivers they need; for example, the file
+"examples/echo-plain.rkt" uses ground-vm along with
+tcp and an initial endpoint action:
(ground-vm |
tcp |
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?) |
#:conversation (tcp-channel from to _) |
#:on-presence (spawn #:child (echoer from to)))) |
(ground-vm maybe-boot-pid-binding maybe-initial-state initial-action ...)
|
|
(ground-vm: maybe-boot-pid-binding maybe-typed-initial-state initial-action ...)
| | maybe-boot-pid-binding | | = | | | | | | | | #:boot-pid id | | | | | | maybe-initial-state | | = | | | | | | | | #:initial-state expr | | | | | | maybe-typed-initial-state | | = | | | | | | | | #:initial-state expr : type | | | | | | initial-action | | = | | expr |
|
|
Starts the ground VM, in untyped and typed programs, respectively. If
+
#:boot-pid is specified, the given identifier is bound within
+the form to the PID of the
primordial process that performs the
+initial actions. If
#:initial-state is specified (with a
+type, for
ground-vm:), it is used as the initial state for
+the primordial process; if it is not supplied, the primordial process
+is given
(void) as its initial state (and
Void as
+its state type).
2.3 Constructing transitions
(transition new-state action-tree ...)
|
|
(transition: new-state : State action-tree ...)
|
|
(transition/no-state action-tree ...)
|
|
Each of these forms produces a Transition structure. The
+first is for untyped code, the second for typed code (where the
+mandatory State is the type of the transitioning process’s
+private state), and the third for either.
Each action-tree must be an (ActionTree State).
It’s fine to include no action-trees, in which case the
+transition merely updates the state of the process without taking any
+actions.
In the case of transition/no-state, the type Void
+and value (void) is used for the process state.
+transition/no-state is useful for processes that are
+stateless other than the implicit state of their endpoints.
(struct | | transition (state actions) | | | #:transparent) |
| state : State | actions : (ActionTree State) |
|
Transition : (All (State) (transition State))
|
|
A transition structure. The transition-state field is the new
+private state the process will have after the transition is applied,
+and the transition-actions are the actions that will be
+performed by the VM in order to apply the transition.
ActionTree : (All (State) (Constreeof (Action State)))
|
|
|
An action-tree is a
cons-tree of
Actions. When
+performing actions, a VM will traverse an action-tree in left-to-right
+order.
'(), (void), and #f may also be present in
+action-trees: when the VM reaches such a value, it ignores it and
+continues with the next leaf in the tree.
For example, all of the following are valid action trees which will
+send messages 1, 2 and 3 in that order:
(list (send-message 1) |
(send-message 2) |
(send-message 3)) |
(list (list (send-message 1)) |
(cons (send-message 2) (cons '() (send-message 3)))) |
(cons (cons (send-message 1) |
(send-message 2)) |
(list #f #f (send-message 3))) |
Because #f and (void) are valid, ignored, members of
+an action-tree, and and when can be used to
+selectively include actions in an action-tree:
(list (first-action) |
(when (condition?) |
(optional-action)) |
(final-action)) |
(list (first-action) |
(and (condition?) |
(optional-action)) |
(final-action)) |
Finally, these inert placeholders can be used to represent "no action
+at all" in a transition:
(transition new-state) ; No action-trees at all |
(transition new-state '()) |
(transition new-state (void)) |
(transition new-state #f) |
(sequence-actions | | initial-transition | | | | | | | item ...) | | → | | (Transition State) |
|
initial-transition : (Transition State) |
| item | | : | | (U (ActionTree State) | (State -> (Transition State))) |
|
|
Returns a transition formed from the initial-transition
+extended with new actions, possibly updating its carried state. Each
+of the supplied items is examined: if it is an
+ActionTree, it is appended to the pending transition’s
+actions; if it is a procedure, it is called with the state of
+the pending transition, and is expected to return an updated
+transition.
For example,
(sequence-actions (transition 'x |
(send-message (list 'message 0))) |
(send-message (list 'message 1)) |
(send-message (list 'message 2)) |
(lambda (old-state) |
(transition (cons 'y old-state) |
(send-message (list 'message 3)))) |
(send-message (list 'message 4))) |
produces the equivalent of
(transition (cons 'y 'x) |
(send-message (list 'message 0)) |
(send-message (list 'message 1)) |
(send-message (list 'message 2)) |
(send-message (list 'message 3)) |
(send-message (list 'message 4))) |
2.4 Creating endpoints
The primitive action that creates new endpoints is
+add-endpoint, but because endpoints are the most flexible and
+complex point of interaction between a process and its VM, a DSL,
+endpoint, streamlines endpoint setup.
(endpoint orientation topic maybe-interest-type | maybe-let-name | maybe-name | maybe-state-pattern | maybe-on-presence | maybe-on-absence | maybe-role-patterns | maybe-reason-pattern | maybe-message-handlers) |
|
|
(endpoint: maybe-typed-state-pattern : State | orientation topic maybe-interest-type | maybe-let-name | maybe-name | maybe-on-presence | maybe-on-absence | maybe-role-patterns | maybe-reason-pattern | maybe-message-handlers) |
| | maybe-typed-state-pattern | | = | | | | | | | | pattern | | | | | | orientation | | = | | #:subscriber | | | | | | #:publisher | | | | | | topic | | = | | expr | | | | | | maybe-interest-type | | = | | | | | | | | #:participant | | | | | | #:observer | | | | | | #:everything | | | | | | maybe-let-name | | = | | | | | | | | #:let-name identifier | | | | | | maybe-name | | = | | | | | | | | #:name expr | | | | | | maybe-state-pattern | | = | | | | | | | | #:state pattern | | | | | | maybe-on-presence | | = | | | | | | | | #:on-presence handler-expr | | | | | | maybe-on-absence | | = | | | | | | | | #:on-absence handler-expr | | | | | | maybe-role-patterns | | = | | | | | | | | #:role pattern | | | | | | #:peer-orientation pattern | #:conversation pattern | #:peer-interest-type pattern |
| | | | | | maybe-reason-pattern | | = | | | | | | | | #:reason pattern | | | | | | maybe-message-handlers | | = | | | | | | | | message-handler ... | | | | | | message-handler | | = | | [pattern handler-expr] | | | | | | handler-expr | | = | | expr |
|
|
Almost everything is optional in an endpoint. The only
+mandatory parts are the orientation and the topic. For
+endpoint:, the expected type of the process state must also
+be supplied.
For example, a minimal endpoint subscribing to all messages would be:
(endpoint #:subscriber ?)
or in Typed Racket, for a process with Integer as its process
+state type,
(endpoint: : Integer #:subscriber ?)
A minimal publishing endpoint would be:
(endpoint #:publisher ?) |
(endpoint: : Integer #:publisher ?) |
While topic patterns are ordinary Racket data with embedded ?
+wildcards (see Messages and Topics), all the other patterns
+in an endpoint are match-patterns. In particular
+note that ? is a wildcard in a topic pattern, while
+_ is a wildcard in a match-pattern.
2.4.1 Receiving messages
Supply one or more message-handler clauses to handle incoming
+message events (as distinct from presence- or absence-events).
The following endpoint subscribes to all messages, but only
+handles some of them:
(endpoint #:subscriber ? |
['ping (send-message 'pong)] |
['hello (list (send-message 'goodbye) |
(quit))]) |
2.4.2 Action-only vs. State updates
If #:state occurs in an endpoint, or the
+maybe-typed-state-pattern occurs in an endpoint:,
+then all the handler-exprs in that endpoint are expected to
+return transition structures.
If not, however, the event handler expressions are expected to return
+plain ActionTrees.
This way, simple endpoints that do not need to examine the process
+state, and simply act in response to whichever event triggered them,
+can be written without the clutter of threading the process state
+value through the code.
For example, a simple endpoint could be written either as
(endpoint #:subscriber 'ping |
['ping (send-message 'pong)]) |
or, explicitly accessing the endpoint’s process’s state,
(endpoint #:subscriber 'ping |
#:state old-state |
['ping (transition old-state |
(send-message 'pong))]) |
2.4.3 Naming endpoints
Endpoint names can be used to update
+or delete endpoints.
If #:name is supplied, the given value is used as the name of
+the endpoint. If not, a fresh name is created. (At present,
+gensym is used.)
If #:let-name is supplied, the given identifier is bound in
+the handler-exprs to the name of the endpoint. If not, the
+name of the endpoint is inaccessible to the handler-exprs.
2.4.4 Handling presence and absence events
Other endpoints (in this or other processes) may have matching topics
+and complementary orientations to the current endpoint. When such
+endpoints come and go, presence and absence events are generated in
+the current endpoint.
By default, no actions are taken on such events, but
+#:on-presence and #:on-absence override this
+behaviour.
For example, say process A establishes the following endpoint:
(endpoint #:subscriber 'ping |
#:on-presence (send-message 'pinger-arrived) |
#:on-absence (send-message 'pinger-departed) |
['ping (send-message 'pong)]) |
Some time later, process B takes the following endpoint-establishing
+action:
(endpoint #:publisher 'ping |
#:let-name ping-endpoint-name |
#:on-presence |
(list (endpoint #:subscriber 'pong |
#:let-name pong-waiter-name |
['pong (list (delete-endpoint ping-endpoint-name) |
(delete-endpoint pong-waiter-name))]) |
(send-message 'ping))) |
The sequence of events will be:
Process A’s #:on-presence handler will run, and the
+'pinger-arrived message will be sent. At the same
+time,In the current implementation, one happens before the
+other, but it is nondeterministic which is run first. process B’s
+#:on-presence handler runs, installing a second endpoint
+and sending the 'ping message.
Process A’s endpoint receives the 'ping message, and
+sends the 'pong message.
Process B’s second endpoint receives the 'pong
+message, and deletes both of process B’s endpoints.
The #:on-absence handler in process A runs, sending
+the 'pinger-departed message.
One possible trace of messages in the VM containing processes A and B is
'pinger-arrived |
'ping |
'pong |
'pinger-departed |
By sending the 'ping message only once the
+#:on-presence handler has fired, process B ensures that
+someone is listening for pings.
This way, if process B starts before process A, then B will
+automatically wait until A is ready to receive ping requests before
+issuing any.
2.4.5 Exit reasons
If a #:reason pattern is supplied, then the exit reason
+supplied to the delete-endpoint or quit action that
+led to the absence-event is available to the endpoint’s
+#:on-absence handler expression.
2.4.6 Updating endpoints
If, when an endpoint is created, an existing endpoint with an
+equal? name is already present, then if the existing and
+to-be-added endpoints have exactly equal roles (meaning equal
+orientations, interest-types, and topic patterns), the handlers
+for the endpoint are updated without emitting presence or
+absence notifications.
This dubious feature can be used to avoid "glitching" of presence
+signals. A future release of this library will include better
+automatic support for avoiding such transients.
2.4.7 Who am I talking to?
If either #:role or any of #:peer-orientation,
+#:conversation, or #:peer-interest-type are
+supplied, the handler-exprs are given access to the role
+carried in the EndpointEvent that triggered them.
This role describes the intersection of interests between the
+current endpoint and the peer endpoint, and so can proxy for the
+identity of the other party. It is in a sense a description of the
+scope of the current conversation.
Using #:role allows a handler complete access to the
+role structure in the triggering event. It is more common
+however to simply use #:conversation to extract the
+role-topic alone, since it is seldom necessary to examine
+role-orientation (since it’s guaranteed to be complementary
+to the orientation of the current endpoint) or
+role-interest-type. If access to those parts is required, use
+#:peer-orientation and #:peer-interest-type.
2.5 Deleting endpoints
(delete-endpoint id [reason]) → Action
|
id : Any |
reason : Any = #f |
Use this action to delete a previously-added endpoint by name. The
+
delete-endpoint-id must be
equal? to the
+corresponding
add-endpoint-pre-eid; when
endpoint
+was used to construct the endpoint to be deleted, the relevant name is
+that bound by
#:let-name or supplied to
#:name. See
+
Naming endpoints.
If reason is supplied, it is included in the corresponding
+action, and made available in any resulting absence-events.
2.6 Sending messages and feedback
(send-message body [orientation]) → Action
|
body : Any |
orientation : Orientation = 'publisher |
Constructs a message-sending action with the given orientation.
+Usually the correct orientation to use is 'publisher; it
+means that the sender of the message is acting in the "publisher"
+role. Use 'subscriber instead when acting in the "subscriber"
+role, i.e. sending feedback.
(send-feedback body) → Action
|
body : Any |
Equivalent to (send-message body 'subscriber).
2.7 Creating processes
(spawn maybe-pid-binding maybe-debug-name maybe-parent-continuation | #:child boot-expr) |
|
|
(spawn: maybe-pid-binding maybe-debug-name typed-parent-continuation | #:child : ChildStateType boot-expr) |
| | maybe-pid-binding | | = | | | | | | | | #:pid identifier | | | | | | maybe-debug-name | | = | | | | | | | | #:debug-name expr | | | | | | maybe-parent-continuation | | = | | | | | | | | #:parent k-expr | | | | | | #:parent parent-state-pattern k-expr | | | | | | typed-parent-continuation | | = | | #:parent : ParentStateType | | | | | | #:parent : ParentStateType k-expr | | | | | | #:parent parent-state-pattern : ParentStateType k-expr | | | | | | k-expr | | = | | expr | | | | | | boot-expr | | = | | expr |
|
|
Action describing a new process to create. The boot-expr
+should be an expression yielding a transition that contains
+the child process’s initial state and initial actions.
If #:pid is supplied, the associated identifier is bound to
+the child process’s PID in both boot-expr and the parent’s
+k-expr.
Any supplied #:debug-name will be used in VM debug output.
+See also logging (MATRIX_LOG).
If #:parent is supplied, the associated k-expr will
+run in the parent process after the child process has been created. If
+the parent-state-pattern is also supplied, then
+k-expr must return a Transition; otherwise, it must
+return an ActionTree. Note that in Typed Racket, for type
+system reasons, spawn: requires ParentStateType to
+be supplied.
2.8 Exiting and killing processes
(quit [who reason]) → Action
|
who : (Option PID) = #f |
reason : Any = #f |
Action causing the termination of a process. If who is
+omitted or #f, terminates the acting process; otherwise,
+terminates the peer process having who as its PID.
If reason is supplied, it is included in the corresponding
+action, and made available in any resulting absence-events.
Terminating the current process is as simple as:
(quit)
When a process raises an exception that it does not catch, its
+containing VM catches the exception and turns it into an implicit quit
+action. In that case, the reason will be the raised exception
+itself.
2.9 Cooperative scheduling
(yield maybe-state-pattern k-expr)
|
|
(yield: typed-state-pattern k-expr)
| | maybe-state-pattern | | = | | | | | | | | #:state pattern | | | | | | typed-state-pattern | | = | | : State | | | | | | pattern : State | | | | | | k-expr | | = | | expr |
|
|
Lets other processes in the system run for a step, returning to
+evaluate k-expr only after doing a complete round of the
+scheduler.
If pattern is supplied, k-expr should evaluate to a
+Transition; otherwise it should produce an ActionTree.
2.10 Creating nested VMs
***** nested-vm, nested-vm:
+TODO
2.11 Relaying across layers
***** at-meta-level, at-meta-level:
+TODO