Programs written for Marketplace differ from normal Racket modules
@@ -13,65 +13,65 @@ be a variation for Typed Racket, and languages providing greater
support for flow control, responsibility transfer, and other
networking concepts. For now, Typed Racket programs must be written as
#lang typed/racket programs using (require marketplace)
-and ground-vm: explicitly.
Starts the ground VM, in untyped and typed programs, respectively. If
+
"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
+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
+its state type).
2.3 Constructing transitions
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
+private state), and the third for either.
A transition structure. The
transition-state field is the new
+
transition/no-state is useful for processes that are
+stateless other than the implicit state of their endpoints.
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
+and the
transition-actions are the actions that will be
+performed by the VM in order to apply the transition.
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
+send messages 1, 2 and 3 in that order:
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
+at all" in a transition:
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
+
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
+transition.
For example,
produces the equivalent of
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
+
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 ?
+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:
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
+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:,
+handles some of them:
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
+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
+value through the code.
For example, a simple endpoint could be written either as
or, explicitly accessing the endpoint’s process’s state,
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
@@ -81,8 +81,8 @@ 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
+behaviour.
For example, say process A establishes the following endpoint:
Some time later, process B takes the following endpoint-establishing
+action:
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
@@ -95,8 +95,8 @@ the 'pinger-departed mes
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
+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
@@ -107,65 +107,65 @@ 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
+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
+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
+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
+
role-interest-type. If access to those parts is required, use
+
#:peer-orientation and
#:peer-interest-type.
2.5 Deleting endpoints
Use this action to delete a previously-added endpoint by name. The
+
id given 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.
+action, and made available in any resulting
absence-events.
2.6 Sending messages and feedback
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
+role, i.e. sending feedback.
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
+
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
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
+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
+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 maybe-vm-pid-binding maybe-boot-pid-binding | maybe-initial-state | maybe-debug-name | boot-action-expr ...) |
|
|
(nested-vm: : ParentStateType | maybe-vm-pid-binding maybe-boot-pid-binding | maybe-typed-initial-state | maybe-debug-name | boot-action-expr ...) |
| | maybe-vm-pid-binding | | = | | | | | | | | #:vm-pid identifier | | | | | | maybe-boot-pid-binding | | = | | | | | | | | #:boot-pid identifier | | | | | | maybe-initial-state | | = | | | | | | | | #:initial-state expr | | | | | | maybe-typed-initial-state | | = | | | | | | | | #:initial-state expr : StateType | | | | | | maybe-debug-name | | = | | | | | | | | #:debug-name expr | | | | | | boot-action-expr | | = | | expr |
|
|
Results in a
spawn action that starts a nested VM. The
+
Transition; otherwise it should produce an
ActionTree.
2.10 Creating nested VMs
(nested-vm maybe-vm-pid-binding maybe-boot-pid-binding | maybe-initial-state | maybe-debug-name | boot-action-expr ...) |
|
|
(nested-vm: : ParentStateType | maybe-vm-pid-binding maybe-boot-pid-binding | maybe-typed-initial-state | maybe-debug-name | boot-action-expr ...) |
| | maybe-vm-pid-binding | | = | | | | | | | | #:vm-pid identifier | | | | | | maybe-boot-pid-binding | | = | | | | | | | | #:boot-pid identifier | | | | | | maybe-initial-state | | = | | | | | | | | #:initial-state expr | | | | | | maybe-typed-initial-state | | = | | | | | | | | #:initial-state expr : StateType | | | | | | maybe-debug-name | | = | | | | | | | | #:debug-name expr | | | | | | boot-action-expr | | = | | expr |
|
|
Results in a
spawn action that starts a nested VM. The
primordial process in the new VM executes the boot-actions with the
given initial state. (If no initial state is supplied,
(void)
is used.)
If #:vm-pid is present, the corresponding identifier is bound
in the boot-action expressions to the container-relative PID of the
new VM itself. If #:boot-pid is present, however, the
corresponding identifier is bound to the new-VM-relative PID of the
-primordial process in the new VM.
2.11 Relaying across layers
(at-meta-level: : StateType preaction ...)
|
|
(at-meta-level preaction ...) → (Action StateType)
| preaction : (PreAction State) |
|
Each VM gives its processes access to two distinct IPC facilities: the
+primordial process in the new VM.
2.11 Relaying across layers
Each VM gives its processes access to two distinct IPC facilities: the
internal one, provided for the VM’s processes to talk amongst
themselves, and the external one, the network that the VM
itself is a process within.
Marketplace’s actions can apply to either of those two networks. By
default, actions apply to the VM of the acting process directly, but
-using at-meta-level (or at-meta-level: in typed
+using at-meta-level (or at-meta-level: in typed
code) to wrap an action level-shifts the action to make it
-apply at the level of the acting process’s VM’s container instead.
For example, wrapping an endpoint in at-meta-level
+apply at the level of the acting process’s VM’s container instead.
For example, wrapping an endpoint in at-meta-level
adds a subscription to the VM’s container’s network. Instead of
listening to sibling processes of the acting process, the new endpoint
will listen to sibling processes of the acting process’s VM. In this
-example, the primordial process in the nested-vm creates an
-endpoint in the VM’s own network, the ground VM:
(nested-vm |
(at-meta-level |
(endpoint #:subscriber (tcp-channel ? (tcp-listener 5999) ?) ...))) |
In this example, a new process is spawned as a sibling of the
-nested-vm rather than as a sibling of its primordial process:
(nested-vm |
(at-meta-level |
(spawn #:child (transition/no-state (send-message 'hello-world))))) |
Compare to this example, which spawns a sibling of the
-nested-vm’s primordial process:
(nested-vm |
(spawn #:child (transition/no-state (send-message 'hello-world)))) |
\ No newline at end of file
+example, the primordial process in the
nested-vm creates an
+endpoint in the VM’s own network, the ground VM:
In this example, a new process is spawned as a sibling of the
+nested-vm rather than as a sibling of its primordial process:
Compare to this example, which spawns a sibling of the
+nested-vm’s primordial process:
\ No newline at end of file
diff --git a/index.html b/index.html
index f5fee15..424ba0a 100644
--- a/index.html
+++ b/index.html
@@ -34,4 +34,4 @@ location of mutable state, failure detection and recovery, access
control, I/O and user interface, debugging and profiling.
Marketplace addresses these concerns with a small set of primitives
chosen to make network programming in-the-small as flexible, scalable,
manageable and securable as network programming in-the-large—and
-vice versa.
\ No newline at end of file
+vice versa.