Higher-order Choreography

Higher-order choreography is a choreography that takes another choreography as an argument. Just like higher-order functions, higher-order choreographies are useful for abstracting over common patterns.

This section describes how to define and execute higher-order choreographies.

Defining a Higher-order Choreography

To define a higher-order choreography, you need to create a generic struct that takes a type parameter that implements the Choreography trait.

#![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 HigherOrderChoreography<C: Choreography> {
    sub_choreo: C,
};
}

When you implement the Choreography trait, you have access to the sub_choreo field. You can use the call method to execute the sub-choreography.

#![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 HigherOrderChoreography<C: Choreography> {
    sub_choreo: C,
};
impl<C: Choreography<(), L = LocationSet!(Alice, Bob)>> Choreography for HigherOrderChoreography<C> {
    type L = LocationSet!(Alice, Bob);
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        op.call(self.sub_choreo);
    }
}
}

Passing values to a sub-choreography

It is often useful to pass values to a sub-choreography. To do so, instead of storing the sub-choreography object as a field, you associate the sub-choreography trait with the choreography using the std::marker::PhantomData type.

#![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());
use std::marker::PhantomData;

trait SubChoreography {
    fn new(arg: Located<i32, Alice>) -> Self;
}

struct HigherOrderChoreography<C: Choreography<Located<bool, Alice>> + SubChoreography> {
    _marker: PhantomData<C>,
};

impl<C: Choreography<Located<bool, Alice>, L = LocationSet!(Alice)> + SubChoreography> Choreography for HigherOrderChoreography<C> {
    type L = LocationSet!(Alice);
    fn run(self, op: &impl ChoreoOp<Self::L>) {
        let num_at_alice = op.locally(Alice, |_| {
            42
        });
        let sub_choreo = C::new(num_at_alice);
        op.call(sub_choreo);
    }
}
}

Here, the HigherOrderChoreography struct takes a type parameter C that implements both the Choreography trait and the SubChoreography trait. The SubChoreography trait ensures that the C type can be constructed with a located integer at Alice using the new constructor.