Skip to content
This repository has been archived by the owner on Aug 13, 2020. It is now read-only.

Applications

Ola Angelsmark edited this page Mar 13, 2017 · 11 revisions

Assumptions on a Calvin application

  • 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

Writing CalvinScript Applications

The structure of a CalvinScript is as follows:

  1. constant definitions (optional)
  2. component declarations (optional)
  3. program (optional)

Constant definitions

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 CalvinScript program

A program consists of a number of statements:

  • comments
  • instantiations
  • connections
  • port property declarations (optional)
  • deployment rules (optional)

Comments

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.

Instantiation

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.

Connections

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.

Port property declarations and deployment rules

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  

A CalvinScript component declaration

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.

Convenience syntax

There are a couple of convenience constructs in CalvinScript.

Values instead of output ports (implicit actors)

"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.

Token value replacement (implicit actors)

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.

Labels

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

Port references

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}

Port lists

In the case of fan-out, the input ports may be stated in a comma separated list:

42 > print.token, send.token