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.