Detailed design
In this section, we will show the most critical design choices made that led to the realization of the framework as it currently is.
Scafi-core
Language
We decided to keep the core language constructs under a common Language
trait that can be mixed in with other traits to make it possible to use the core constructs inside code. The core language constructs are the following:
Rep
Rep captures state evolution, starting from an init
value that is updated each round through fun
.
def rep[A](init: => A)(fun: A => A): A
Nbr
Nbr captures communication, of the value computed from its expr
expression, with neighbors.
def nbr[A](expr: => A): A
Foldhood
Foldhood supports neighborhood data aggregation, through a standard, FP-oriented “fold” with initial value init
, aggregation function aggr
, and the set of values to fold over obtained by evaluating expr
against all neighbors;
def foldhood[A](init: => A)(aggr: (A, A) => A)(expr: => A): A
Branch
This function partitions the domain into two subspaces that do not interact with each other, based on the cond
expression.
def branch[A](cond: => Boolean)(thn: => A)(els: => A): A
By combining these constructs, higher-level functions can be defined to capture increasingly complex collective behavior, called Builtins
.
Aggregate Program Execution
The execution of an aggregate program uses a VM
, which is updated through rounds
of field calculus execution.
Field Calculus Execution
The concept of field calculus execution corresponds to a function from the local device Context
to an Export
that contains information regarding the execution of the aggregate program. The execution is carried over in rounds by repeatedly updating the local context, calling the round
function and producing the local export that, alongside the neighboring exports will form the context for the next round of computation. Here’s the signature of the round function:
def round(c: Context, e: => Any = main()): Export
This function takes the starting local context and the aggregate program to produce the export. Inside the FieldCalculusExecution
trait, we also define a main function, that can be overridden, where the aggregate program’s code can be written.
VMstatus
VMStatus models the status of the virtual machine used for the evaluation of the constructs. It acts like a stack, containing the Path
of the computation, the index
of the current Slot
and the id of the current neighbor
.
RoundVM
RoundVM evaluates the aggregate program at each local computation in a device. When done, it shares its exports with the neighbors to be aligned with each other.
This module works with Context
and Export
modules.
Context
Context models the context of local computation, meaning that it has the ID of the device, all the exports available to it and various functions to get neighboring values.
Export, Path and Slot
Export is a data structure that contains the information needed for coordination with the neighbors. The values that are contained inside the Export are Paths
, working like a stack, that is a list of Slots
representing a path in a tree (the Export tree-like data structure).
A Slot
is the most nested component of this structure. A Slot
can represent primitives like nbr
, rep
, foldhood
and branch
. It also has methods to create paths and associate values with them.
Rufi-core
The design of the Rufi-core
project resembles the one adopted in the Scafi-core
module. However, the differences between the Rust language and Scala lead to some notable design differences that are here discussed. Please note that these differences have arisen from the limitations that the Rust compiler imposes on the developer and will be discussed in detail in the Implementation Issues](../implementation/implementation-issues.md) section.
Language
The Language
trait is now a simple rust module that contains the core language constructs such as Rep, Nbr, Foldhood and Branch.
pub mod lang {
pub fn rep<A>(...)
pub fn nbr<A>(...)
pub fn foldhood<A>(...)
pub fn branch<A>(...)
}
However, these functions have some differences between the Scala counterparts, mainly they take a RoundVM
as a parameter and return a tuple (RoundVM, A) instead of a simple A value. For example, here’s the nbr function signature:
pub fn nbr<A: Copy + 'static>(mut vm: RoundVM, expr: impl Fn(RoundVM) -> (RoundVM,A)) -> (RoundVM, A)
Execution
The execution is still based on RoundVM
and the round
function:
fn round<A, P>(vm: RoundVM, program: P) -> (RoundVM, A)
where
P: Fn(RoundVM) -> (RoundVM, A)
However, we can see that Context and Export are now substituted by a RoundVM. Also, the export’s root value is returned in a tuple alongside the resulting RoundVM.
RoundVM
Due to other language limitations to this application, the nest
function from Scafi-core
has been divided into nest-in
, nest-write
and nest-out
functions.
/// Pushes the slot in the current Path.
pub fn nest_in(slot: Slot)
/// Checks if the value needs to be written inside the Export and returns it.
pub fn nest_write<A>(write: bool, value: A) -> A
/// Checks if the status' index needs to be increased, then pops the current status.
pub fn nest_out(inc: bool)
Here is an example of how a language construct is implemented using these functions:
pub fn nbr<A>(vm: RoundVM, expr: F) -> (RoundVM, A) {
vm.nest_in(Nbr(...));
let (vm_, nbr_val) = ... // nbr's logic that also uses expr
let result = vm_.nest_write(nbr_val, ...);
vm_.nest_out(...);
(vm_, result)
}
Also, the locally
function has been taken outside the RoundVM’s associate functions and is now a private function that is used inside the constructs in the lang
module. Here is the updated locally signature:
fn locally<A, F>(mut vm: RoundVM, expr: F) -> (RoundVM, A)
where
F: Fn(RoundVM) -> (RoundVM, A)
Integration layer between RuFi-core and ScaFi-core
Two possible options have been identified for the detailed design of the integration layer between RuFi-core and ScaFi-core:
- Wrap the APIs exposed by the original core written in Rust.
- Rewrite a version of RuFi-core fully interoperable with C.
In any case, the APIs exposed in C are bound to Scala.
In the first solution, we have a layer that wraps the Round VM APIs and makes them compatible with C. The exposed C APIs are bound in Scala, in this way ScaFi-core can use the Rust implementation in a completely transparent way.
The second solution is to rewrite a version of RuFi-core entirely compatible with C. This option is chosen in case it is not possible to implement the first one, it also involves the partial replication of the RuFi-core code.
Scafi-fields
The Scafi-fields
module contains a library to enable the use of reified fields inside aggregate programs. Its main purpose is to enable explicit manipulation of fields through the use of language constructs, monadic operations and comprehensions.
A field maps devices to values of a generic type, if a device is not in the map, a default value is used. Here’s a simple representation of a Field of A’s:
case class Field[A](getMap: Map[Int, A], default: A)
The Field is meant to represent the local knowledge of the device about the neighboring values, much like the Context data structure.
Fields
The Fields
trait defines the concept of Field
, alongside some useful functions and factories. It also includes the instance of cats
‘Monad
type class for Field, to enable special syntax and manipulation inside for-comprehensions. This trait can be mixed with other traits to enable field manipulation inside functions as we do inside the FieldLanguage
trait.
Field Language
This trait defines the core language constructs for the Field Calculus with reified fields. These constructs are the same as the regular ScaFi-core
ones, but they differ in that they also use fields as values.
Field Language Execution
The ‘fields’ extension of ScaFi-core
is meant as a simple extension of the syntax and functionalities that are available inside a regular aggregate program. This means that the core execution architecture is the one defined inside the core module of ScaFi. To enable the execution of an aggregate program with reified fields, the FieldLanguageProgram trait has been introduced:
trait FieldLanguageProgram extends AggregateProgram with FieldLanguageImpl