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))
(remote (tcp-address "the.remote.host.example.com" 5999)))
(transition/no-state
(endpoint #:publisher (tcp-channel local remote ?))
(endpoint #:subscriber (tcp-channel remote local ?)
[(tcp-channel _ _ (? eof-object?))
(code:comment "Handle a received end-of-file object")
(transition ...)]
[(tcp-channel _ _ (? bytes? data))
(code:comment "Handle received data")
(transition ...)])))
(publisher (tcp-channel local remote ?))
(subscriber (tcp-channel remote local ?)
(on-message
[(tcp-channel _ _ (? eof-object?))
(code:comment "Handle a received end-of-file object")
(transition ...)]
[(tcp-channel _ _ (? bytes? data))
(code:comment "Handle received data")
(transition ...)]))))
]
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:
@racketblock[
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?) #:observer
#:conversation (tcp-channel them us _)
#:on-presence (spawn #:child (chat-session them us)))
(observe-publishers (tcp-channel ? (tcp-listener 5999) ?)
(match-conversation (tcp-channel 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
monitoring demand for such exchanges. The TCP driver uses a rare
@racket[#:everything] endpoint to monitor the presence of
@racket[#:observer]s, and creates listening TCP server sockets in
monitoring demand for such exchanges. The TCP driver uses the unusual
@racket['everything] @racket[InterestType] to monitor the presence of
@racket['observer]s, and creates listening TCP server sockets in
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.
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[
(define (chat-session them us)
(transition/no-state
(endpoint #:subscriber (tcp-channel them us ?)
#:on-absence (quit)
[(tcp-channel _ _ (? bytes? data))
(code:comment "Handle incoming data")
(transition ...)])))
(subscriber (tcp-channel them us ?)
(on-absence (quit))
(on-message [(tcp-channel _ _ (? bytes? data))
(code:comment "Handle incoming data")
(transition ...)]))))
]
@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:
@racketblock[
(ground-vm
tcp
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?)
#:conversation (tcp-channel from to _)
#:on-presence (spawn #:child (echoer from to))))
(ground-vm tcp
(subscriber (tcp-channel ? (tcp-listener 5999) ?)
(match-conversation (tcp-channel from to _)
(on-presence (spawn (echoer from to))))))
]
@deftogether[(
@ -241,139 +240,106 @@ produces the equivalent of
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.
complex point of interaction between a process and its VM, a
collection of macros helps streamline 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)
@defform[(publisher topic handler ...)]
@defform[(publisher: State topic handler ...)]
@defform[(subscriber topic handler ...)]
@defform[(subscriber: State topic handler ...)]
@defform[(observe-subscribers topic handler ...)]
@defform[(observe-subscribers: State topic handler ...)]
@defform[(observe-publishers topic handler ...)]
@defform[(observe-publishers: State topic handler ...)]
@defform[(observe-subscribers/everything topic handler ...)]
@defform[(observe-subscribers/everything: State topic handler ...)]
@defform[(observe-publishers/everything topic handler ...)]
@defform[(observe-publishers/everything: State topic handler ...)]
@defform[(build-endpoint pre-eid role handler ...)]
@defform[(build-endpoint: State pre-eid role handler ...)
#: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)]]
[(handler unfiltered-handler
(match-state pattern handler ...)
(match-orientation pattern handler ...)
(match-conversation pattern handler ...)
(match-interest-type pattern handler ...)
(match-reason pattern handler ...))
(unfiltered-handler (on-presence expr ...)
(on-absence expr ...)
(on-message [pattern 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.
The many variations on the core
@racket[build-endpoint]/@racket[build-endpoint:] form exist to give
good control over @racket[InterestType] in the endpoint under
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:
@racketblock[(endpoint #:subscriber ?)]
@racketblock[(subscriber ?)]
or in Typed Racket, for a process with @racket[Integer] as its process
state type,
@racketblock[(endpoint: : Integer #:subscriber ?)]
@racketblock[(subscriber: Integer ?)]
A minimal publishing endpoint would be:
@racketblock[(endpoint #:publisher ?)
(endpoint: : Integer #:publisher ?)]
@racketblock[(publisher ?)
(publisher: Integer ?)]
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
in an endpoint definition 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).
Supply an @racket[on-message] handler clause to an endpoint definition
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))])]
@racketblock[(subscriber ?
(on-message
['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 a group of handlers is wrapped in @racket[match-state], then all
the wrapped handlers are expected to return
@seclink["constructing-transitions"]{transition structures}.
If not, however, the event handler expressions are expected to return
plain @racket[ActionTree]s.
If not, however, the handler expressions are expected to return plain
@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,
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)])]
@racketblock[(subscriber 'ping
(on-message ['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.
@racketblock[(subscriber 'ping
(match-state old-state
(on-message ['ping (transition old-state
(send-message 'pong))])))]
@subsection{Handling presence and absence events}
@ -383,37 +349,39 @@ 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
@racket[on-presence] and @racket[on-absence] handlers 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)])]
@racketblock[(subscriber 'ping
(on-presence (send-message 'pinger-arrived))
(on-absence (send-message 'pinger-departed))
(on-message ['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)))]
@racketblock[(let-fresh (ping-endpoint-name pong-waiter-name)
(name-endpoint ping-endpoint-name
(publisher 'ping
(on-presence
(list (name-endpoint pong-waiter-name
(subscriber 'pong
(on-message
['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
@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
@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
@ -422,7 +390,7 @@ The sequence of events will be:
@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
@item{The @racket[on-absence] handler in process A runs, sending
the @racket['pinger-departed] message.}
#:style 'ordered]
@ -435,7 +403,7 @@ One possible trace of messages in the VM containing processes A and B is
'pinger-departed]
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.
This way, if process B starts before process A, then B will
@ -444,10 +412,10 @@ 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.
If a handler is wrapped in a @racket[match-reason] form, 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}
@ -464,24 +432,86 @@ 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.
Wrapping a handler in @racket[match-orientation],
@racket[match-conversation], and/or @racket[match-interest-type] gives
a handler access to the contents of the @racket[role] structure
carried in the triggering @racket[EndpointEvent].
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.
The carried 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
It is most common to simply use @racket[match-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].
@racket[role-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}
@deftogether[(
@defform[(spawn maybe-pid-binding maybe-debug-name maybe-parent-continuation
#:child boot-expr)]
@defform[(spawn maybe-pid-binding boot-expr)]
@defform[(spawn/continue maybe-pid-binding
#:parent parent-state-pattern k-expr
#:child boot-expr)]
@defform[#:literals (:)
(spawn: maybe-pid-binding maybe-debug-name typed-parent-continuation
#:child : ChildStateType boot-expr)
(spawn: maybe-pid-binding
#: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
[(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)]]
)]{
@ -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
@racket[k-expr].
Any supplied @racket[#:debug-name] will be used in VM debug output.
See also @secref{logging}.
The @racket[spawn/continue] and @racket[spawn/continue:] variations
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
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.
In Typed Racket, for type system reasons, @racket[spawn:] and
@racket[spawn/continue:] require @racket[ParentStateType] to be
supplied as well as @racket[ChildStateType].
}
@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}
@deftogether[(
@defform[(yield maybe-state-pattern k-expr)]
@defform[(yield 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)]]
(yield: state-pattern : State k-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].
The state of the yielding process will be matched against
@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[
(spawn-vm
(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
@ -685,7 +717,7 @@ nested VM rather than as a sibling of its primordial process:
@racketblock[
(spawn-vm
(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
@ -693,7 +725,7 @@ nested VM's primordial process:
@racketblock[
(spawn-vm
(spawn #:child (transition/no-state (send-message 'hello-world))))
(spawn (transition/no-state (send-message 'hello-world))))
]
}