Applications
- An application is a set of actors and a graph describing the connections
- The application graph is static during application lifetime
- Actor classes are uniquely identifiable
- Actor instances are uniquely identifiable
- If a actor instance is migrated it retains its unique id
- Actors run on nodes
- Actors communicate over ports, the default port behaviour is:
- Each input port receives tokens from exactly one output port
- Each output port may connect to one or more input ports (fan-out)
- Fan-out delivers the same sequence to all readers
The structure of a CalvinScript is as follows:
- constant definitions (optional)
- component declarations (optional)
- program (optional)
Constant declarations take the form
define FOO=<value>
where value must be valid JSON. Once defined, a constant can be used anywhere a value is expected.
A program consists of a number of statements:
- comments
- instantiations
- connections
- port property declarations (optional)
- deployment rules (optional)
A line comment begin with a #
anywhere and extends to the end of the line.
A block comment begins with /*
and ends with */
and can appear anywhere.
An instantiation (of an actor) is is a statement of the form:
<actor> : <Namespace>.<ActorType>(<named_argument_list>)
e.g.
iip : std.Init(data="Hello World!")
log : io.Print()
where we have created two actors, iip
and log
of type Init
and
Print
, belonging to the std
and io
namespace, respectively.
A connection describes how actors are connected over ports using the form:
<actor>.<outport> > <actor>.<inport>
e.g.
iip.out > log.token
where the tokens produced by iip
on its out
output port is
connected to the token
input port of the log
actor.
When a port is not going to be used, a special keyword voidport
must be used:
voidport > iip.in
The resulting application,
iip : std.Init(data="Hello World!")
log : io.Print()
iip.out > log.token
voidport > iip.in
will write a 'Hello World!' message to a terminal.
Together, the actor instances and connections between their ports define the application graph.
N.B. A somewhat surprising aspect of CalvinScript is that the statement order is irrelevant. The reason is that a CalvinScript is not executed in a traditional sense, it is a discription of a data flow graph.
These are advanced concepts that are described in the port properties and deployment sections.
In general, port properties should not be set explicitly in a CalvinScript, but hidden in actors with well defined port properties, see the example below.
By default, an output port can be connected to many input ports (fan-out), but multiple connections to input ports (fan-in) is not allowed. Some actors have non-defualt port settings, e.g. the std.Collect
or json.CollectDict
actors. When a port property deviates from the default behaviour, it is stated in the documentation for that actor, e.g.:
Actor: std.Collect()
Collect tokens from many ports on the inport
Inports:
token : collecting token stream -- properties {'routing': u'collect-unordered'}
Outports:
token : resulting token stream
To provide a means for hierarchy and reuse, it is possible to declare components in CalvinScript.
A component declaration has the form:
component <ComponentName>(<argname_list>) <inport_list> -> <outport_list> {
<program>
}
Reading the above from left to right we have a keyword component
, a name for
the new component, a possibly empty list of the names of the arguments to the
component instantiation, a list of input port names and a list of output port
names (either of which may be empty), and a CalvinScript program between the
curly brackets. When referring to the ports of the component from within the same,
they should be prefixed with .
.
As always, an example might be useful:
component DelayedCounter(delay) -> integer {
"""
Produce a sequence of numbers with a short delay between numbers
"""
ctr : std.Counter()
delay : std.Delay(delay = delay)
ctr.integer > delay.token
delay.token > .integer
}
dctr : DelayedCounter(delay=0.5)
out : io.Print()
dctr.integer > out.token
In the above script, the component is immediately usable since it is defined in
the same source file as the program using it. For other users to enjoy its
benefits, it can be installed locally. To install a component, use the compiler
with the --install
directive on the file where one or more components was
defined, together with the namespace under which to install the components. NB.
Only components will ever be installed, never scripts themselves nor the
program following the component declaration.
Example:
$ csinstall --script script.calvin --all --namespace usr
This will install all components found in script script.calvin
in namespace usr
.
These will behave just like any other actor, and can be used by other scripts.
There are a couple of convenience constructs in CalvinScript.
"Infinite greetings!" > print.token
This is a short form for
_unique_name : std.Constant(data="Infinite greetings!")
_unique_name.token > print.token
and a constant actor will be auto-generated in application graph.
A token replacement
foo.token > /"Hello!"/ print.token
will replace whatever token is sent from foo.token
with a "Hello!" token.
This is a short form for
_unique_name : std.Constantify(constant="Hello!")
foo.token > _unique_name.in
_unique_name.out > print.token
and a constantify actor will be generated in application graph.
When using implicit actors it is possible to assign a name to them, in order to be able to reference them elsewhere, e.g.:
:greeting "Infinite greetings!" > print.token
greeting.token > log.token
Sometimes it is necessary to reference a particular port (e.g. json.CollectDict
) which is done by preceeding an actor port with an &
,
and optionally specify a port direction using [in]
or [out]
if necessary to resolve ambiguities.
{"foo": &identity.token[in], "bar": &iip.out}
In the case of fan-out, the input ports may be stated in a comma separated list:
42 > print.token, send.token