Input and Output

To use ChoRus as part of a larger system, you need to be able to write a choreography that takes input and returns output.

Moreover, you may want to write a choreography that takes located values as input and returns located values as output. In such cases, you need to be able to construct/unwrap located values outside of the run method.

In this section, we will show you how to write a choreography that takes input and returns output.

Input

To take input, you can use fields of the struct that implements the Choreography trait. For example, the following choreography takes a String as input.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());

struct DemoChoreography {
    input: String,
}

impl Choreography for DemoChoreography {
    type L = LocationSet!();
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        println!("Input: {}", self.input);
    }
}
}

You can construct an instance of the choreography with the input and pass it to the epp_and_run function.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography {
    input: String,
}
impl Choreography for DemoChoreography {
    type L = LocationSet!(Alice);
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        println!("Input: {}", self.input);
    }
}

let choreo = DemoChoreography {
    input: "World".to_string(),
};

let projector = Projector::new(Alice, alice_transport);
projector.epp_and_run(choreo);
}

Located Input

Input of normal types such as String must be available at all locations. However, you may want to take input that is only available at a single location. You can do so by using a located value as a field of the choreography struct.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography {
    input: Located<String, Alice>,
}

impl Choreography for DemoChoreography {
    type L = LocationSet!(Alice);
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        op.locally(Alice, |un| {
            let input = un.unwrap(&self.input);
            println!("Input at Alice: {}", input);
        });
    }
}
}

Because the input field is located at Alice, you can only access the string at Alice using the Unwrapper.

To construct this choreography, you must pass a located value. The Projector struct provides two methods to construct located values: local and remote.

local constructs a located value that is available at the projection target. You must provide an actual value as an argument. The location will be the same as the target of the projector.

remote constructs a located value that is available at a different location. You must provide a location of the value. Note that this location must be different from the target of the projector. As of now, ChoRus does not check this at compile time. If you pass the same location as the target of the projector, the program will panic at runtime.

To run the sample choreography above at Alice, we use the local method to construct the located value.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography {
    input: Located<String, Alice>,
}

impl Choreography for DemoChoreography {
    type L = LocationSet!(Alice);
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        op.locally(Alice, |un| {
            let input = un.unwrap(&self.input);
            println!("Input at Alice: {}", input);
        });
    }
}
let projector_for_alice = Projector::new(Alice, alice_transport);
// Because the target of the projector is Alice, the located value is available at Alice.
let string_at_alice: Located<String, Alice> = projector_for_alice.local("Hello, World!".to_string());
// Instantiate the choreography with the located value
let choreo = DemoChoreography {
    input: string_at_alice,
};
projector_for_alice.epp_and_run(choreo);
}

For Bob, we use the remote method to construct the located value.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography {
    input: Located<String, Alice>,
}

impl Choreography for DemoChoreography {
    type L = LocationSet!(Alice, Bob);
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        op.locally(Alice, |un| {
            let input = un.unwrap(&self.input);
            println!("Input at Alice: {}", input);
        });
    }
}
let projector_for_bob = Projector::new(Bob, bob_transport);
// Construct a remote located value at Alice. The actual value is not required.
let string_at_alice = projector_for_bob.remote(Alice);
// Instantiate the choreography with the located value
let choreo = DemoChoreography {
    input: string_at_alice,
};
projector_for_bob.epp_and_run(choreo);
}

Output

Similarly, we can get output from choreographies by returning a value from the run method.

To do so, we specify the output type to the Choreography trait and return the value of the type from the run method.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography;

impl Choreography<String> for DemoChoreography {
    type L = LocationSet!();
    fn run(self, op: &impl ChoreoOp<Self::L>) -> String {
        "Hello, World!".to_string()
    }
}
}

epp_and_run returns the value returned from the run method.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography;

impl Choreography<String> for DemoChoreography {
    type L = LocationSet!(Alice);
    fn run(self, op: &impl ChoreoOp<Self::L>) -> String {
        "Hello, World!".to_string()
    }
}
let choreo = DemoChoreography;
let projector = Projector::new(Alice, alice_transport);
let output = projector.epp_and_run(choreo);
assert_eq!(output, "Hello, World!".to_string());
}

Located Output

You can use the Located<V, L1> as a return type of the run method to return a located value. The projector provides a method unwrap to unwrap the output located values.

#![allow(unused)]
fn main() {
extern crate chorus_lib;
use chorus_lib::core::{ChoreoOp, Choreography, ChoreographyLocation, Projector, Located, MultiplyLocated, Runner, LocationSet, Serialize, Deserialize};
use chorus_lib::transport::local::{LocalTransport, LocalTransportChannelBuilder};
#[derive(ChoreographyLocation)]
struct Alice;
#[derive(ChoreographyLocation)]
struct Bob;
#[derive(ChoreographyLocation)]
struct Carol;
let transport_channel = LocalTransportChannelBuilder::new().with(Alice).with(Bob).with(Carol).build();
let alice_transport = LocalTransport::new(Alice, transport_channel.clone());
let bob_transport = LocalTransport::new(Bob, transport_channel.clone());
let carol_transport = LocalTransport::new(Carol, transport_channel.clone());
struct DemoChoreography;

impl Choreography<Located<String, Alice>> for DemoChoreography {
    type L = LocationSet!(Alice);
    fn run(self, op: &impl ChoreoOp<Self::L>) -> Located<String, Alice> {
        op.locally(Alice, |_| {
            "Hello, World!".to_string()
        })
    }
}

let projector = Projector::new(Alice, alice_transport);
let output = projector.epp_and_run(DemoChoreography);
let string_at_alice = projector.unwrap(output);
assert_eq!(string_at_alice, "Hello, World!".to_string());
}

Because projectors are parametric over locations, you can only unwrap located values at the target location.

You can return multiple located values by returning a tuple or struct that contains multiple located values. They don't have to be located at the same location, but you can only unwrap them at the correct location.