Much more highlevel text
This commit is contained in:
parent
441a2c20a3
commit
e4edfce465
|
@ -96,7 +96,7 @@ its state type).
|
|||
|
||||
}
|
||||
|
||||
@section{Constructing transitions}
|
||||
@section[#:tag "constructing-transitions"]{Constructing transitions}
|
||||
|
||||
@deftogether[(
|
||||
@defform[(transition new-state action-tree ...)]
|
||||
|
@ -111,6 +111,10 @@ private state), and the third for either.
|
|||
|
||||
Each @racket[action-tree] must be an @racket[(ActionTree State)].
|
||||
|
||||
It's fine to include @emph{no} action-trees, in which case the
|
||||
transition merely updates the state of the process without taking any
|
||||
actions.
|
||||
|
||||
In the case of @racket[transition/no-state], the type @racket[Void]
|
||||
and value @racket[(void)] is used for the process state.
|
||||
@racket[transition/no-state] is useful for processes that are
|
||||
|
@ -139,7 +143,7 @@ An action-tree is a @deftech{cons-tree} of @racket[Action]s. When
|
|||
performing actions, a VM will traverse an action-tree in left-to-right
|
||||
order.
|
||||
|
||||
Nulls, @racket[(void)] values, and @racket[#f] may also be present in
|
||||
@racket['()], @racket[(void)], and @racket[#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.
|
||||
|
||||
|
@ -171,6 +175,14 @@ selectively include actions in an action-tree:
|
|||
(optional-action))
|
||||
(final-action))]
|
||||
|
||||
Finally, these inert placeholders can be used to represent "no action
|
||||
at all" in a transition:
|
||||
|
||||
@racketblock[(transition new-state) (code:comment "No action-trees at all")
|
||||
(transition new-state '())
|
||||
(transition new-state (void))
|
||||
(transition new-state #f)]
|
||||
|
||||
}
|
||||
|
||||
@defproc[(sequence-actions [initial-transition (Transition State)]
|
||||
|
@ -178,21 +190,411 @@ selectively include actions in an action-tree:
|
|||
(State -> (Transition State)))]
|
||||
...) (Transition State)]{
|
||||
|
||||
TODO
|
||||
Returns a transition formed from the @racket[initial-transition]
|
||||
extended with new actions, possibly updating its carried state. Each
|
||||
of the supplied @racket[item]s is examined: if it is an
|
||||
@racket[ActionTree], it is appended to the pending transition's
|
||||
actions; if it is a procedure, it is called with the @emph{state} of
|
||||
the pending transition, and is expected to return an updated
|
||||
transition.
|
||||
|
||||
For example,
|
||||
|
||||
@racketblock[(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
|
||||
|
||||
@racketblock[(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)))]
|
||||
|
||||
}
|
||||
|
||||
@section{Actions}
|
||||
**** Communication-related
|
||||
***** endpoint, endpoint:
|
||||
[#:tag "endpoint-dsl"]
|
||||
***** delete-endpoint
|
||||
***** send-message
|
||||
***** send-feedback
|
||||
**** Process- and scheduling-related
|
||||
***** spawn, spawn:
|
||||
***** quit
|
||||
***** yield, yield:
|
||||
@section[#:tag "endpoint-dsl"]{Creating endpoints}
|
||||
|
||||
The primitive action that creates new endpoints is
|
||||
@racket[add-endpoint], but because endpoints are the most flexible and
|
||||
complex point of interaction between a process and its VM, a DSL,
|
||||
@racket[endpoint], streamlines endpoint setup.
|
||||
|
||||
@deftogether[(
|
||||
@defform[(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)]
|
||||
@defform[#:literals (:)
|
||||
(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)
|
||||
#:grammar
|
||||
[(maybe-typed-state-pattern (code:line)
|
||||
(code:line pattern))
|
||||
(orientation #:subscriber
|
||||
#:publisher)
|
||||
(topic expr)
|
||||
(maybe-interest-type (code:line)
|
||||
#:participant
|
||||
#:observer
|
||||
#:everything)
|
||||
(maybe-let-name (code:line)
|
||||
(code:line #:let-name identifier))
|
||||
(maybe-name (code:line)
|
||||
(code:line #:name expr))
|
||||
(maybe-state-pattern (code:line)
|
||||
(code:line #:state pattern))
|
||||
(maybe-on-presence (code:line)
|
||||
(code:line #:on-presence handler-expr))
|
||||
(maybe-on-absence (code:line)
|
||||
(code:line #:on-absence handler-expr))
|
||||
(maybe-role-patterns (code:line)
|
||||
(code:line #:role pattern)
|
||||
(code:line #:peer-orientation pattern
|
||||
#:conversation pattern
|
||||
#:peer-interest-type pattern))
|
||||
(maybe-reason-pattern (code:line)
|
||||
(code:line #:reason pattern))
|
||||
(maybe-message-handlers (code:line)
|
||||
(code:line message-handler ...))
|
||||
(message-handler [pattern handler-expr])
|
||||
(handler-expr expr)]]
|
||||
)]{
|
||||
|
||||
Almost everything is optional in an @racket[endpoint]. The only
|
||||
mandatory parts are the orientation and the topic. For
|
||||
@racket[endpoint:], the expected type of the process state must also
|
||||
be supplied.
|
||||
|
||||
For example, a minimal endpoint subscribing to all messages would be:
|
||||
|
||||
@racketblock[(endpoint #:subscriber ?)]
|
||||
|
||||
or in Typed Racket, for a process with @racket[Integer] as its process
|
||||
state type,
|
||||
|
||||
@racketblock[(endpoint: : Integer #:subscriber ?)]
|
||||
|
||||
A minimal publishing endpoint would be:
|
||||
|
||||
@racketblock[(endpoint #:publisher ?)
|
||||
(endpoint: : Integer #:publisher ?)]
|
||||
|
||||
While topic patterns are ordinary Racket data with embedded @racket[?]
|
||||
wildcards (see @secref{messages-and-topics}), all the other patterns
|
||||
in an @racket[endpoint] are @racket[match]-patterns. In particular
|
||||
note that @racket[?] is a wildcard in a topic pattern, while
|
||||
@racket[_] is a wildcard in a @racket[match]-pattern.
|
||||
|
||||
@subsection{Receiving messages}
|
||||
|
||||
Supply one or more @racket[message-handler] clauses to handle incoming
|
||||
message events (as distinct from presence- or absence-events).
|
||||
|
||||
The following endpoint @emph{subscribes} to all messages, but only
|
||||
@emph{handles} some of them:
|
||||
|
||||
@racketblock[(endpoint #:subscriber ?
|
||||
['ping (send-message 'pong)]
|
||||
['hello (list (send-message 'goodbye)
|
||||
(quit))])]
|
||||
|
||||
@subsection{Action-only vs. State updates}
|
||||
|
||||
If @racket[#:state] occurs in an @racket[endpoint], or the
|
||||
@racket[maybe-typed-state-pattern] occurs in an @racket[endpoint:],
|
||||
then all the @racket[handler-expr]s in that endpoint are expected to
|
||||
return @seclink["constructing-transitions"]{transition structures}.
|
||||
|
||||
If not, however, the event handler expressions are expected to return
|
||||
plain @racket[ActionTree]s.
|
||||
|
||||
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
|
||||
|
||||
@racketblock[(endpoint #:subscriber 'ping
|
||||
['ping (send-message 'pong)])]
|
||||
|
||||
or, explicitly accessing the endpoint's process's state,
|
||||
|
||||
@racketblock[(endpoint #:subscriber 'ping
|
||||
#:state old-state
|
||||
['ping (transition old-state
|
||||
(send-message 'pong))])]
|
||||
|
||||
@subsection[#:tag "naming-endpoints"]{Naming endpoints}
|
||||
|
||||
Endpoint names can be used to @seclink["updating-endpoints"]{update}
|
||||
or @seclink["deleting-endpoints"]{delete} endpoints.
|
||||
|
||||
If @racket[#:name] is supplied, the given value is used as the name of
|
||||
the endpoint. If not, a fresh name is created. (At present,
|
||||
@racket[gensym] is used.)
|
||||
|
||||
If @racket[#:let-name] is supplied, the given identifier is bound in
|
||||
the @racket[handler-expr]s to the name of the endpoint. If not, the
|
||||
name of the endpoint is inaccessible to the @racket[handler-expr]s.
|
||||
|
||||
@subsection{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
|
||||
@racket[#:on-presence] and @racket[#:on-absence] override this
|
||||
behaviour.
|
||||
|
||||
For example, say process A establishes the following endpoint:
|
||||
|
||||
@racketblock[(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:
|
||||
|
||||
@racketblock[(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:
|
||||
|
||||
@itemlist[
|
||||
|
||||
@item{Process A's @racket[#:on-presence] handler will run, and the
|
||||
@racket['pinger-arrived] message will be sent. At the same
|
||||
time,@note{In the current implementation, one happens before the
|
||||
other, but it is nondeterministic which is run first.} process B's
|
||||
@racket[#:on-presence] handler runs, installing a second endpoint
|
||||
and sending the @racket['ping] message.}
|
||||
|
||||
@item{Process A's endpoint receives the @racket['ping] message, and
|
||||
sends the @racket['pong] message.}
|
||||
|
||||
@item{Process B's second endpoint receives the @racket['pong]
|
||||
message, and deletes both of process B's endpoints.}
|
||||
|
||||
@item{The @racket[#:on-absence] handler in process A runs, sending
|
||||
the @racket['pinger-departed] message.}
|
||||
|
||||
#:style 'ordered]
|
||||
|
||||
One possible trace of messages in the VM containing processes A and B is
|
||||
|
||||
@racketblock['pinger-arrived
|
||||
'ping
|
||||
'pong
|
||||
'pinger-departed]
|
||||
|
||||
By sending the @racket['ping] message @emph{only} once the
|
||||
@racket[#: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.
|
||||
|
||||
@subsection{Exit reasons}
|
||||
|
||||
If a @racket[#:reason] pattern is supplied, then the exit reason
|
||||
supplied to the @racket[delete-endpoint] or @racket[quit] action that
|
||||
led to the @racket[absence-event] is available to the endpoint's
|
||||
@racket[#:on-absence] handler expression.
|
||||
|
||||
@subsection[#:tag "updating-endpoints"]{Updating endpoints}
|
||||
|
||||
If, when an endpoint is created, an existing endpoint with an
|
||||
@racket[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 @emph{handlers}
|
||||
for the endpoint are @emph{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.
|
||||
|
||||
@subsection{Who am I talking to?}
|
||||
|
||||
If either @racket[#:role] or any of @racket[#:peer-orientation],
|
||||
@racket[#:conversation], or @racket[#:peer-interest-type] are
|
||||
supplied, the @racket[handler-expr]s are given access to the role
|
||||
carried in the @racket[EndpointEvent] that triggered them.
|
||||
|
||||
This role describes the @emph{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 @racket[#:role] allows a handler complete access to the
|
||||
@racket[role] structure in the triggering event. It is more common
|
||||
however to simply use @racket[#:conversation] to extract the
|
||||
@racket[role-topic] alone, since it is seldom necessary to examine
|
||||
@racket[role-orientation] (since it's guaranteed to be complementary
|
||||
to the orientation of the current endpoint) or
|
||||
@racket[role-interest-type]. If access to those parts is required, use
|
||||
@racket[#:peer-orientation] and @racket[#:peer-interest-type].
|
||||
|
||||
}
|
||||
|
||||
@section[#:tag "deleting-endpoints"]{Deleting endpoints}
|
||||
|
||||
@defproc[(delete-endpoint [id Any] [reason Any #f]) Action]{
|
||||
|
||||
Use this action to delete a previously-added endpoint by name. The
|
||||
@racket[delete-endpoint-id] must be @racket[equal?] to the
|
||||
corresponding @racket[add-endpoint-pre-eid]; when @racket[endpoint]
|
||||
was used to construct the endpoint to be deleted, the relevant name is
|
||||
that bound by @racket[#:let-name] or supplied to @racket[#:name]. See
|
||||
@secref{naming-endpoints}.
|
||||
|
||||
If @racket[reason] is supplied, it is included in the corresponding
|
||||
action, and made available in any resulting @racket[absence-event]s.
|
||||
|
||||
}
|
||||
|
||||
@section{Sending messages and feedback}
|
||||
|
||||
@defproc[(send-message [body Any] [orientation Orientation 'publisher]) Action]{
|
||||
|
||||
Constructs a message-sending action with the given orientation.
|
||||
Usually the correct orientation to use is @racket['publisher]; it
|
||||
means that the sender of the message is acting in the "publisher"
|
||||
role. Use @racket['subscriber] instead when acting in the "subscriber"
|
||||
role, i.e. sending feedback.
|
||||
|
||||
}
|
||||
|
||||
@defproc[(send-feedback [body Any]) Action]{
|
||||
|
||||
Equivalent to @racket[(send-message body 'subscriber)].
|
||||
|
||||
}
|
||||
|
||||
@section{Creating processes}
|
||||
|
||||
@deftogether[(
|
||||
@defform[(spawn maybe-pid-binding maybe-debug-name maybe-parent-continuation
|
||||
#:child boot-expr)]
|
||||
@defform[#:literals (:)
|
||||
(spawn: maybe-pid-binding maybe-debug-name typed-parent-continuation
|
||||
#:child : ChildStateType boot-expr)
|
||||
#:grammar
|
||||
[(maybe-pid-binding (code:line)
|
||||
(code:line #:pid identifier))
|
||||
(maybe-debug-name (code:line)
|
||||
(code:line #:debug-name expr))
|
||||
(maybe-parent-continuation (code:line)
|
||||
(code:line #:parent k-expr)
|
||||
(code:line #:parent parent-state-pattern k-expr))
|
||||
(typed-parent-continuation (code:line #:parent : ParentStateType)
|
||||
(code:line #:parent : ParentStateType k-expr)
|
||||
(code:line #:parent parent-state-pattern : ParentStateType k-expr))
|
||||
(k-expr expr)
|
||||
(boot-expr expr)]]
|
||||
)]{
|
||||
|
||||
Action describing a new process to create. The @racket[boot-expr]
|
||||
should be an expression yielding a @racket[transition] that contains
|
||||
the child process's initial state and initial actions.
|
||||
|
||||
If @racket[#:pid] is supplied, the associated identifier is bound to
|
||||
the child process's PID in both @racket[boot-expr] and the parent's
|
||||
@racket[k-expr].
|
||||
|
||||
Any supplied @racket[#:debug-name] will be used in VM debug output.
|
||||
See also @secref{logging}.
|
||||
|
||||
If @racket[#:parent] is supplied, the associated @racket[k-expr] will
|
||||
run in the parent process after the child process has been created. If
|
||||
the @racket[parent-state-pattern] is also supplied, then
|
||||
@racket[k-expr] must return a @racket[Transition]; otherwise, it must
|
||||
return an @racket[ActionTree]. Note that in Typed Racket, for type
|
||||
system reasons, @racket[spawn:] requires @racket[ParentStateType] to
|
||||
be supplied.
|
||||
|
||||
}
|
||||
|
||||
@section{Exiting and killing processes}
|
||||
|
||||
@defproc[(quit [who (Option PID) #f] [reason Any #f]) Action]{
|
||||
|
||||
Action causing the termination of a process. If @racket[who] is
|
||||
omitted or @racket[#f], terminates the acting process; otherwise,
|
||||
terminates the peer process having @racket[who] as its PID.
|
||||
|
||||
If @racket[reason] is supplied, it is included in the corresponding
|
||||
action, and made available in any resulting @racket[absence-event]s.
|
||||
|
||||
Terminating the current process is as simple as:
|
||||
|
||||
@racketblock[(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 @racket[reason] will be the raised exception
|
||||
itself.
|
||||
|
||||
}
|
||||
|
||||
@section{Cooperative scheduling}
|
||||
|
||||
@deftogether[(
|
||||
@defform[(yield maybe-state-pattern k-expr)]
|
||||
@defform[#:literals (:)
|
||||
(yield: typed-state-pattern k-expr)
|
||||
#:grammar
|
||||
[(maybe-state-pattern (code:line)
|
||||
(code:line #:state pattern))
|
||||
(typed-state-pattern (code:line : State)
|
||||
(code:line pattern : State))
|
||||
(k-expr expr)]]
|
||||
)]{
|
||||
|
||||
Lets other processes in the system run for a step, returning to
|
||||
evaluate @racket[k-expr] only after doing a complete round of the
|
||||
scheduler.
|
||||
|
||||
If @racket[pattern] is supplied, @racket[k-expr] should evaluate to a
|
||||
@racket[Transition]; otherwise it should produce an @racket[ActionTree].
|
||||
|
||||
}
|
||||
|
||||
@section{Creating nested VMs}
|
||||
|
||||
***** nested-vm, nested-vm:
|
||||
**** Cross-layer
|
||||
|
||||
@section{Relaying across layers}
|
||||
|
||||
***** at-meta-level, at-meta-level:
|
||||
|
|
|
@ -22,7 +22,7 @@ each @racket['publisher] message sent to the VM's network.
|
|||
|
||||
}
|
||||
|
||||
@section{logging (MATRIX_LOG)}
|
||||
@section[#:tag "logging"]{logging (MATRIX_LOG)}
|
||||
|
||||
@defmodule*[(marketplace/log-untyped
|
||||
marketplace/log-typed)]{
|
||||
|
@ -80,4 +80,4 @@ use of @racket[debug].
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue