Update documentation for extrasugar

This commit is contained in:
Tony Garnock-Jones 2013-06-10 19:44:04 -04:00
parent f671ac3bef
commit 08879f2a9a
2 changed files with 220 additions and 187 deletions

View File

@ -130,14 +130,15 @@ Choose a @racket[tcp-handle], and then create endpoints as follows:
(let ((local (tcp-handle 'some-unique-value)) (let ((local (tcp-handle 'some-unique-value))
(remote (tcp-address "the.remote.host.example.com" 5999))) (remote (tcp-address "the.remote.host.example.com" 5999)))
(transition/no-state (transition/no-state
(endpoint #:publisher (tcp-channel local remote ?)) (publisher (tcp-channel local remote ?))
(endpoint #:subscriber (tcp-channel remote local ?) (subscriber (tcp-channel remote local ?)
[(tcp-channel _ _ (? eof-object?)) (on-message
(code:comment "Handle a received end-of-file object") [(tcp-channel _ _ (? eof-object?))
(transition ...)] (code:comment "Handle a received end-of-file object")
[(tcp-channel _ _ (? bytes? data)) (transition ...)]
(code:comment "Handle received data") [(tcp-channel _ _ (? bytes? data))
(transition ...)]))) (code:comment "Handle received data")
(transition ...)]))))
] ]
The TCP driver will automatically create an outbound connection in The TCP driver will automatically create an outbound connection in
@ -153,18 +154,18 @@ Choose a port number, and then create an @emph{observer} endpoint as
follows: follows:
@racketblock[ @racketblock[
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?) #:observer (observe-publishers (tcp-channel ? (tcp-listener 5999) ?)
#:conversation (tcp-channel them us _) (match-conversation (tcp-channel them us _)
#:on-presence (spawn #:child (chat-session them us))) (on-presence (spawn (chat-session them us)))))
] ]
The use of @racket[#:observer] here indicates that this endpoint isn't The use of @racket[observe-publishers] here indicates that this endpoint isn't
actually interested in exchanging any TCP data; instead, it is actually interested in exchanging any TCP data; instead, it is
monitoring demand for such exchanges. The TCP driver uses a rare monitoring demand for such exchanges. The TCP driver uses the unusual
@racket[#:everything] endpoint to monitor the presence of @racket['everything] @racket[InterestType] to monitor the presence of
@racket[#:observer]s, and creates listening TCP server sockets in @racket['observer]s, and creates listening TCP server sockets in
response. When a connection comes in, the TCP driver spawns a manager response. When a connection comes in, the TCP driver spawns a manager
process which offers regular @racket[#:participant] endpoints for process which offers regular @racket['participant] endpoints for
communicating on the newly-arrived socket. communicating on the newly-arrived socket.
To illustrate the code for handling a newly-arrived connection, To illustrate the code for handling a newly-arrived connection,
@ -172,11 +173,11 @@ To illustrate the code for handling a newly-arrived connection,
@racketblock[ @racketblock[
(define (chat-session them us) (define (chat-session them us)
(transition/no-state (transition/no-state
(endpoint #:subscriber (tcp-channel them us ?) (subscriber (tcp-channel them us ?)
#:on-absence (quit) (on-absence (quit))
[(tcp-channel _ _ (? bytes? data)) (on-message [(tcp-channel _ _ (? bytes? data))
(code:comment "Handle incoming data") (code:comment "Handle incoming data")
(transition ...)]))) (transition ...)]))))
] ]
@subsection{Receiving data} @subsection{Receiving data}

View File

@ -77,11 +77,10 @@ start any drivers they need; for example, the file
@racket[tcp] and an initial @racket[endpoint] action: @racket[tcp] and an initial @racket[endpoint] action:
@racketblock[ @racketblock[
(ground-vm (ground-vm tcp
tcp (subscriber (tcp-channel ? (tcp-listener 5999) ?)
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?) (match-conversation (tcp-channel from to _)
#:conversation (tcp-channel from to _) (on-presence (spawn (echoer from to))))))
#:on-presence (spawn #:child (echoer from to))))
] ]
@deftogether[( @deftogether[(
@ -241,139 +240,106 @@ produces the equivalent of
The primitive action that creates new endpoints is The primitive action that creates new endpoints is
@racket[add-endpoint], but because endpoints are the most flexible and @racket[add-endpoint], but because endpoints are the most flexible and
complex point of interaction between a process and its VM, a DSL, complex point of interaction between a process and its VM, a
@racket[endpoint], streamlines endpoint setup. collection of macros helps streamline endpoint setup.
@deftogether[( @deftogether[(
@defform[(endpoint orientation topic maybe-interest-type @defform[(publisher topic handler ...)]
maybe-let-name @defform[(publisher: State topic handler ...)]
maybe-name @defform[(subscriber topic handler ...)]
maybe-state-pattern @defform[(subscriber: State topic handler ...)]
maybe-on-presence @defform[(observe-subscribers topic handler ...)]
maybe-on-absence @defform[(observe-subscribers: State topic handler ...)]
maybe-role-patterns @defform[(observe-publishers topic handler ...)]
maybe-reason-pattern @defform[(observe-publishers: State topic handler ...)]
maybe-message-handlers)] @defform[(observe-subscribers/everything topic handler ...)]
@defform[#:literals (:) @defform[(observe-subscribers/everything: State topic handler ...)]
(endpoint: maybe-typed-state-pattern : State @defform[(observe-publishers/everything topic handler ...)]
orientation topic maybe-interest-type @defform[(observe-publishers/everything: State topic handler ...)]
maybe-let-name @defform[(build-endpoint pre-eid role handler ...)]
maybe-name @defform[(build-endpoint: State pre-eid role handler ...)
maybe-on-presence
maybe-on-absence
maybe-role-patterns
maybe-reason-pattern
maybe-message-handlers)
#:grammar #:grammar
[(maybe-typed-state-pattern (code:line) [(handler unfiltered-handler
(code:line pattern)) (match-state pattern handler ...)
(orientation #:subscriber (match-orientation pattern handler ...)
#:publisher) (match-conversation pattern handler ...)
(topic expr) (match-interest-type pattern handler ...)
(maybe-interest-type (code:line) (match-reason pattern handler ...))
#:participant (unfiltered-handler (on-presence expr ...)
#:observer (on-absence expr ...)
#:everything) (on-message [pattern expr ...] ...))]]
(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 The many variations on the core
mandatory parts are the orientation and the topic. For @racket[build-endpoint]/@racket[build-endpoint:] form exist to give
@racket[endpoint:], the expected type of the process state must also good control over @racket[InterestType] in the endpoint under
be supplied. construction;
see @secref{participating-vs-observing}.
Almost everything is optional in an endpoint definition. The only
mandatory part is the topic, unless you're using Typed Racket, in
which case the process state type must also be specified.
For example, a minimal endpoint subscribing to all messages would be: For example, a minimal endpoint subscribing to all messages would be:
@racketblock[(endpoint #:subscriber ?)] @racketblock[(subscriber ?)]
or in Typed Racket, for a process with @racket[Integer] as its process or in Typed Racket, for a process with @racket[Integer] as its process
state type, state type,
@racketblock[(endpoint: : Integer #:subscriber ?)] @racketblock[(subscriber: Integer ?)]
A minimal publishing endpoint would be: A minimal publishing endpoint would be:
@racketblock[(endpoint #:publisher ?) @racketblock[(publisher ?)
(endpoint: : Integer #:publisher ?)] (publisher: Integer ?)]
While topic patterns are ordinary Racket data with embedded @racket[?] While topic patterns are ordinary Racket data with embedded @racket[?]
wildcards (see @secref{messages-and-topics}), all the other patterns wildcards (see @secref{messages-and-topics}), all the other patterns
in an @racket[endpoint] are @racket[match]-patterns. In particular in an endpoint definition are @racket[match]-patterns. In particular
note that @racket[?] is a wildcard in a topic pattern, while note that @racket[?] is a wildcard in a topic pattern, while
@racket[_] is a wildcard in a @racket[match]-pattern. @racket[_] is a wildcard in a @racket[match]-pattern.
@subsection{Receiving messages} @subsection{Receiving messages}
Supply one or more @racket[message-handler] clauses to handle incoming Supply an @racket[on-message] handler clause to an endpoint definition
message events (as distinct from presence- or absence-events). to handle incoming message events (as distinct from presence- or
absence-events).
The following endpoint @emph{subscribes} to all messages, but only The following endpoint @emph{subscribes} to all messages, but only
@emph{handles} some of them: @emph{handles} some of them:
@racketblock[(endpoint #:subscriber ? @racketblock[(subscriber ?
['ping (send-message 'pong)] (on-message
['hello (list (send-message 'goodbye) ['ping (send-message 'pong)]
(quit))])] ['hello (list (send-message 'goodbye)
(quit))]))]
@subsection{Action-only vs. State updates} @subsection{Action-only vs. State updates}
If @racket[#:state] occurs in an @racket[endpoint], or the If a group of handlers is wrapped in @racket[match-state], then all
@racket[maybe-typed-state-pattern] occurs in an @racket[endpoint:], the wrapped handlers are expected to return
then all the @racket[handler-expr]s in that endpoint are expected to @seclink["constructing-transitions"]{transition structures}.
return @seclink["constructing-transitions"]{transition structures}.
If not, however, the event handler expressions are expected to return If not, however, the handler expressions are expected to return plain
plain @racket[ActionTree]s. @racket[ActionTree]s.
This way, simple endpoints that do not need to examine the process This way, simple handlers that do not need to examine the process
state, and simply act in response to whichever event triggered them, state, and simply act in response to whichever event triggered them,
can be written without the clutter of threading the process state can be written without the clutter of threading the process state
value through the code. value through the code.
For example, a simple endpoint could be written either as For example, a simple endpoint could be written either as
@racketblock[(endpoint #:subscriber 'ping @racketblock[(subscriber 'ping
['ping (send-message 'pong)])] (on-message ['ping (send-message 'pong)]))]
or, explicitly accessing the endpoint's process's state, or, explicitly accessing the endpoint's process's state,
@racketblock[(endpoint #:subscriber 'ping @racketblock[(subscriber 'ping
#:state old-state (match-state old-state
['ping (transition old-state (on-message ['ping (transition old-state
(send-message 'pong))])] (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} @subsection{Handling presence and absence events}
@ -383,37 +349,39 @@ endpoints come and go, presence and absence events are generated in
the current endpoint. the current endpoint.
By default, no actions are taken on such events, but By default, no actions are taken on such events, but
@racket[#:on-presence] and @racket[#:on-absence] override this @racket[on-presence] and @racket[on-absence] handlers override this
behaviour. behaviour.
For example, say process A establishes the following endpoint: For example, say process A establishes the following endpoint:
@racketblock[(endpoint #:subscriber 'ping @racketblock[(subscriber 'ping
#:on-presence (send-message 'pinger-arrived) (on-presence (send-message 'pinger-arrived))
#:on-absence (send-message 'pinger-departed) (on-absence (send-message 'pinger-departed))
['ping (send-message 'pong)])] (on-message ['ping (send-message 'pong)]))]
Some time later, process B takes the following endpoint-establishing Some time later, process B takes the following endpoint-establishing
action: action:
@racketblock[(endpoint #:publisher 'ping @racketblock[(let-fresh (ping-endpoint-name pong-waiter-name)
#:let-name ping-endpoint-name (name-endpoint ping-endpoint-name
#:on-presence (publisher 'ping
(list (endpoint #:subscriber 'pong (on-presence
#:let-name pong-waiter-name (list (name-endpoint pong-waiter-name
['pong (list (delete-endpoint ping-endpoint-name) (subscriber 'pong
(delete-endpoint pong-waiter-name))]) (on-message
(send-message 'ping)))] ['pong (list (delete-endpoint ping-endpoint-name)
(delete-endpoint pong-waiter-name))])))
(send-message 'ping))))))]
The sequence of events will be: The sequence of events will be:
@itemlist[ @itemlist[
@item{Process A's @racket[#:on-presence] handler will run, and the @item{Process A's @racket[on-presence] handler will run, and the
@racket['pinger-arrived] message will be sent. At the same @racket['pinger-arrived] message will be sent. At the same
time,@note{In the current implementation, one happens before the time,@note{In the current implementation, one happens before the
other, but it is nondeterministic which is run first.} process B's other, but it is nondeterministic which is run first.} process B's
@racket[#:on-presence] handler runs, installing a second endpoint @racket[on-presence] handler runs, installing a second endpoint
and sending the @racket['ping] message.} and sending the @racket['ping] message.}
@item{Process A's endpoint receives the @racket['ping] message, and @item{Process A's endpoint receives the @racket['ping] message, and
@ -422,7 +390,7 @@ The sequence of events will be:
@item{Process B's second endpoint receives the @racket['pong] @item{Process B's second endpoint receives the @racket['pong]
message, and deletes both of process B's endpoints.} message, and deletes both of process B's endpoints.}
@item{The @racket[#:on-absence] handler in process A runs, sending @item{The @racket[on-absence] handler in process A runs, sending
the @racket['pinger-departed] message.} the @racket['pinger-departed] message.}
#:style 'ordered] #:style 'ordered]
@ -435,7 +403,7 @@ One possible trace of messages in the VM containing processes A and B is
'pinger-departed] 'pinger-departed]
By sending the @racket['ping] message @emph{only} once the By sending the @racket['ping] message @emph{only} once the
@racket[#:on-presence] handler has fired, process B ensures that @racket[on-presence] handler has fired, process B ensures that
someone is listening for pings. someone is listening for pings.
This way, if process B starts before process A, then B will This way, if process B starts before process A, then B will
@ -444,10 +412,10 @@ issuing any.
@subsection{Exit reasons} @subsection{Exit reasons}
If a @racket[#:reason] pattern is supplied, then the exit reason If a handler is wrapped in a @racket[match-reason] form, then the exit
supplied to the @racket[delete-endpoint] or @racket[quit] action that reason supplied to the @racket[delete-endpoint] or @racket[quit]
led to the @racket[absence-event] is available to the endpoint's action that led to the @racket[absence-event] is available to the
@racket[#:on-absence] handler expression. endpoint's @racket[on-absence] handler expression.
@subsection[#:tag "updating-endpoints"]{Updating endpoints} @subsection[#:tag "updating-endpoints"]{Updating endpoints}
@ -464,24 +432,86 @@ automatic support for avoiding such transients.
@subsection{Who am I talking to?} @subsection{Who am I talking to?}
If either @racket[#:role] or any of @racket[#:peer-orientation], Wrapping a handler in @racket[match-orientation],
@racket[#:conversation], or @racket[#:peer-interest-type] are @racket[match-conversation], and/or @racket[match-interest-type] gives
supplied, the @racket[handler-expr]s are given access to the role a handler access to the contents of the @racket[role] structure
carried in the @racket[EndpointEvent] that triggered them. carried in the triggering @racket[EndpointEvent].
This role describes the @emph{intersection of interests} between the The carried role describes the @emph{intersection of interests}
current endpoint and the peer endpoint, and so can proxy for the between the current endpoint and the peer endpoint, and so can proxy
identity of the other party. It is in a sense a description of the for the identity of the other party. It is in a sense a description of
scope of the current conversation. the scope of the current conversation.
Using @racket[#:role] allows a handler complete access to the It is most common to simply use @racket[match-conversation] to extract
@racket[role] structure in the triggering event. It is more common the @racket[role-topic] alone, since it is seldom necessary to examine
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 @racket[role-orientation] (since it's guaranteed to be complementary
to the orientation of the current endpoint) or to the orientation of the current endpoint) or
@racket[role-interest-type]. If access to those parts is required, use @racket[role-interest-type].
@racket[#:peer-orientation] and @racket[#:peer-interest-type].
See @secref{Examples} for examples of the use of
@racket[match-conversation] and friends.
@subsection[#:tag "participating-vs-observing"]{Participating in a conversation vs. observing conversations}
The core @racket[build-endpoint] form takes an expression evaluating
to a @racket[role], rather than a simple topic. This gives full
control over the new endpoint's @racket[Orientation] and
@racket[InterestType].
The other forms exist for convenience, since usually the orientation
and interest-type is known statically, and only the topic varies
dynamically:
@itemlist[
@item{@racket[publisher] and @racket[subscriber] (and typed
variations ending in @tt{:}) are for ordinary @emph{participation} in
conversations;}
@item{@racket[observe-subscribers] and @racket[observe-publishers]
are for @emph{observing} conversations without participating in them; and}
@item{@racket[observe-subscribers/everything] and
@racket[observe-publishers/everything] are like the ordinary
@tt{observe-...} variants, but use interest-type @racket['everything]
instead of @racket['observer].}
]
The @racket[publisher], @racket[observe-subscribers] and
@racket[observe-subscribers/everything] forms create
@emph{publisher}-oriented endpoints, and @racket[subscriber],
@racket[observe-publishers] and @racket[observe-publishers/everything]
create @emph{subscriber}-oriented endpoints. The rationale for this is
that as a participant, the code should declare the role being played;
but as an observer, the code should declare the roles being observed.
}
@subsection[#:tag "naming-endpoints"]{Naming endpoints}
Endpoint names can be used to @seclink["updating-endpoints"]{update}
or @seclink["deleting-endpoints"]{delete} endpoints.
@defproc[(name-endpoint [id Any] [add-endpoint-action AddEndpoint]) AddEndpoint]{
Returns a copy of the passed-in @racket[add-endpoint] action
structure, with the @racket[id] field set to the passed-in identifying
value.
}
@defform[(let-fresh (identifier ...) expr ...)]{
Binds the @racket[identifier]s to freshly-gensymmed symbols so that
they are available to the @racket[exprs]. @racket[let-fresh] is useful
for inventing a guaranteed-unused name for a temporary endpoint:
@racketblock[(let-fresh (my-name)
(name-endpoint my-name
(subscriber ?
(on-message [_ (list (delete-endpoint my-name)
...)]))))]
} }
@ -522,22 +552,21 @@ Equivalent to @racket[(send-message body 'subscriber)].
@section{Creating processes} @section{Creating processes}
@deftogether[( @deftogether[(
@defform[(spawn maybe-pid-binding maybe-debug-name maybe-parent-continuation @defform[(spawn maybe-pid-binding boot-expr)]
#:child boot-expr)] @defform[(spawn/continue maybe-pid-binding
#:parent parent-state-pattern k-expr
#:child boot-expr)]
@defform[#:literals (:) @defform[#:literals (:)
(spawn: maybe-pid-binding maybe-debug-name typed-parent-continuation (spawn: maybe-pid-binding
#:child : ChildStateType boot-expr) #:parent : ParentStateType
#:child : ChildStateType boot-expr)]
@defform[#:literals (:)
(spawn/continue: maybe-pid-binding
#:parent parent-state-pattern : ParentStateType k-expr
#:child : ChildStateType boot-expr)
#:grammar #:grammar
[(maybe-pid-binding (code:line) [(maybe-pid-binding (code:line)
(code:line #:pid identifier)) (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) (k-expr expr)
(boot-expr expr)]] (boot-expr expr)]]
)]{ )]{
@ -550,16 +579,24 @@ 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 the child process's PID in both @racket[boot-expr] and the parent's
@racket[k-expr]. @racket[k-expr].
Any supplied @racket[#:debug-name] will be used in VM debug output. The @racket[spawn/continue] and @racket[spawn/continue:] variations
See also @secref{logging}. include a @racket[k-expr], which will run in the parent process after
the child process has been created. Note that @racket[k-expr] must
return a @racket[Transition], since @racket[parent-state-pattern] is
always supplied for these variations.
If @racket[#:parent] is supplied, the associated @racket[k-expr] will In Typed Racket, for type system reasons, @racket[spawn:] and
run in the parent process after the child process has been created. If @racket[spawn/continue:] require @racket[ParentStateType] to be
the @racket[parent-state-pattern] is also supplied, then supplied as well as @racket[ChildStateType].
@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. @defproc[(name-process [id Any] [spawn-action Spawn]) Spawn]{
Returns a copy of the passed-in @racket[spawn] action structure, with
the @racket[debug-name] field set to the passed-in identifying value.
The debug name of a process is used in VM debug output. See also
@secref{logging}.
} }
@ -588,23 +625,18 @@ itself.
@section{Cooperative scheduling} @section{Cooperative scheduling}
@deftogether[( @deftogether[(
@defform[(yield maybe-state-pattern k-expr)] @defform[(yield state-pattern k-expr)]
@defform[#:literals (:) @defform[#:literals (:)
(yield: typed-state-pattern k-expr) (yield: state-pattern : State 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 Lets other processes in the system run for a step, returning to
evaluate @racket[k-expr] only after doing a complete round of the evaluate @racket[k-expr] only after doing a complete round of the
scheduler. scheduler.
If @racket[pattern] is supplied, @racket[k-expr] should evaluate to a The state of the yielding process will be matched against
@racket[Transition]; otherwise it should produce an @racket[ActionTree]. @racket[state-pattern] when the process is resumed, and
@racket[k-expr] must evaluate to a @racket[Transition].
} }
@ -676,7 +708,7 @@ endpoint in the VM's own network, the ground VM:
@racketblock[ @racketblock[
(spawn-vm (spawn-vm
(at-meta-level (at-meta-level
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?) ...))) (subscriber (tcp-channel ? (tcp-listener 5999) ?) ...)))
] ]
In this example, a new process is spawned as a sibling of the In this example, a new process is spawned as a sibling of the
@ -685,7 +717,7 @@ nested VM rather than as a sibling of its primordial process:
@racketblock[ @racketblock[
(spawn-vm (spawn-vm
(at-meta-level (at-meta-level
(spawn #:child (transition/no-state (send-message 'hello-world))))) (spawn (transition/no-state (send-message 'hello-world)))))
] ]
Compare to this example, which spawns a sibling of the Compare to this example, which spawns a sibling of the
@ -693,7 +725,7 @@ nested VM's primordial process:
@racketblock[ @racketblock[
(spawn-vm (spawn-vm
(spawn #:child (transition/no-state (send-message 'hello-world)))) (spawn (transition/no-state (send-message 'hello-world))))
] ]
} }