Skip to content

Commit

Permalink
Merge pull request #12 from lsd-ucsc/shumbo/available-locations
Browse files Browse the repository at this point in the history
Type-Level Location Set
  • Loading branch information
shumbo authored Oct 7, 2023
2 parents 8d8c46a + 897104c commit 286794d
Show file tree
Hide file tree
Showing 24 changed files with 1,024 additions and 290 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ members = ["chorus_lib", "chorus_derive"]
resolver = "2"

[workspace.package]
version = "0.1.3"
version = "0.2.0"
edition = "2021"
authors = ["Shun Kashiwa <[email protected]>"]
homepage = "https://lsd-ucsc.github.io/ChoRus/"
Expand Down
50 changes: 41 additions & 9 deletions chorus_book/src/guide-choreography.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ struct HelloWorldChoreography;

// 2. Implement the `Choreography` trait
impl Choreography for HelloWorldChoreography {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!(Alice);
fn run(self, op: &impl ChoreoOp<Self::L>) {
// 3. Use the `op` parameter to access operators
op.locally(Alice, |_| {
println!("Hello, World!");
Expand All @@ -20,6 +21,8 @@ impl Choreography for HelloWorldChoreography {

`Choreography` must implement the `run` method which defines the behavior of the system. The `run` method takes a reference to an object that implements the `ChoreoOp` trait. The `ChoreoOp` trait provides choreographic operators such as `locally` and `comm`.

Also, each `Choreography` has an associated `LocationSet` type, `L`; this is the `LocationSet` that the `Choreography` can operate on.

## Choreographic Operators

Inside the `run` method, you can use the `op` parameter to access choreographic operators.
Expand All @@ -33,7 +36,8 @@ The `locally` operator is used to perform a computation at a single location. It
#
# struct HelloWorldChoreography;
# impl Choreography for HelloWorldChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Alice);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
op.locally(Alice, |_| {
println!("Hello, World!");
});
Expand All @@ -48,7 +52,8 @@ The closure can return a value to create a located value. Located values are val
#
# struct HelloWorldChoreography;
# impl Choreography for HelloWorldChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Alice);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
// This value is only available at Alice
let num_at_alice: Located<i32, Alice> = op.locally(Alice, |_| {
42
Expand All @@ -64,7 +69,8 @@ The computation closure takes `Unwrapper`. Using the `Unwrapper`, you can get a
#
# struct HelloWorldChoreography;
# impl Choreography for HelloWorldChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Alice);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
let num_at_alice: Located<i32, Alice> = op.locally(Alice, |_| {
42
});
Expand All @@ -79,12 +85,13 @@ op.locally(Alice, |un| {

Note that you can unwrap a located value only at the location where the located value is available. If you try to unwrap a located value at a different location, the program will fail to compile.

```rust,compile_fail
```rust, compile_fail
{{#include ./header.txt}}
#
# struct HelloWorldChoreography;
# impl Choreography for HelloWorldChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Alice, Bob);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
// This code will fail to compile
let num_at_alice = op.locally(Alice, |_| { 42 });
op.locally(Bob, |un| {
Expand All @@ -106,7 +113,8 @@ The `comm` operator is used to perform a communication between two locations. It
#
# struct HelloWorldChoreography;
# impl Choreography for HelloWorldChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Alice, Bob);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
// This value is only available at Alice
let num_at_alice: Located<i32, Alice> = op.locally(Alice, |_| {
42
Expand All @@ -131,7 +139,8 @@ The `broadcast` operator is used to perform a broadcast from a single location t
#
# struct HelloWorldChoreography;
# impl Choreography for HelloWorldChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Alice);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
// This value is only available at Alice
let num_at_alice: Located<i32, Alice> = op.locally(Alice, |_| {
42
Expand All @@ -144,10 +153,33 @@ let num: i32 = op.broadcast(Alice, num_at_alice);

Because all locations receive the value, the return type of the `broadcast` operator is a normal value, not a located value. This means that the value can be used for control flow.

```rust,ignore
```rust, ignore
if num == 42 {
println!("The number is 42!");
} else {
println!("The number is not 42!");
}
```

### Note on invalid values for Choreography::L

You'll get a compile error if you try to work with a `ChoreographyLocation` that is not a member of `L`.

```rust, compile_fail
# {{#include ./header.txt}}
# // 1. Define a struct
# struct HelloWorldChoreography;
# // 2. Implement the `Choreography` trait
// ...
impl Choreography for HelloWorldChoreography {
type L = LocationSet!(Alice);
fn run(self, op: &impl ChoreoOp<Self::L>) {
// this will fail
op.locally(Bob, |_| {
println!("Hello, World!");
});
}
}
```

24 changes: 15 additions & 9 deletions chorus_book/src/guide-colocally.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ This protocol can be implemented as follows:
struct DemoChoreography;

impl Choreography for DemoChoreography {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!(Alice, Bob, Carol);
fn run(self, op: &impl ChoreoOp<Self::L>) {
let x_at_alice = op.locally(Alice, |_| {
get_random_number()
});
Expand Down Expand Up @@ -51,7 +52,8 @@ struct BobCarolChoreography {
x_at_bob: Located<u32, Bob>,
};
impl Choreography for BobCarolChoreography {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!(Bob, Carol);
fn run(self, op: &impl ChoreoOp<Self::L>) {
let is_even_at_bob: Located<bool, Bob> = op.locally(Bob, |un| {
let x = un.unwrap(&self.x_at_bob);
x % 2 == 0
Expand All @@ -68,7 +70,7 @@ impl Choreography for BobCarolChoreography {
}
```

Notice that the `BobCarolChoreography` only describes the behavior of Bob and Carol. Since Alice does not appear in this choreography, we can use the `colocally` operator in the main choreography to execute the sub-choreography only on Bob and Carol.
Notice that `BobCarolChoreography` only describes the behavior of Bob and Carol (see its location set `L`). `colocally` is an operator to execute a choreography only at locations that is included in the location set. In this case, if we invoke `BobCarolChoreography` with `colocally` in the main choreography, it will only be executed at Bob and Carol and not at Alice.

```rust
{{#include ./header.txt}}
Expand All @@ -79,7 +81,8 @@ Notice that the `BobCarolChoreography` only describes the behavior of Bob and Ca
# x_at_bob: Located<u32, Bob>,
# };
# impl Choreography for BobCarolChoreography {
# fn run(self, op: &impl ChoreoOp) {
# type L = LocationSet!(Bob, Carol);
# fn run(self, op: &impl ChoreoOp<Self::L>) {
# let is_even_at_bob: Located<bool, Bob> = op.locally(Bob, |un| {
# let x = un.unwrap(&self.x_at_bob);
# x % 2 == 0
Expand All @@ -96,12 +99,13 @@ Notice that the `BobCarolChoreography` only describes the behavior of Bob and Ca
# }
struct MainChoreography;
impl Choreography for MainChoreography {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!(Alice, Bob, Carol);
fn run(self, op: &impl ChoreoOp<Self::L>) {
let x_at_alice = op.locally(Alice, |_| {
get_random_number()
});
let x_at_bob = op.comm(Alice, Bob, &x_at_alice);
op.colocally(&[Bob.name(), Carol.name()], BobCarolChoreography {
op.colocally(BobCarolChoreography {
x_at_bob,
});
}
Expand Down Expand Up @@ -131,7 +135,8 @@ struct BobCarolChoreography {
};

impl Choreography<BobCarolResult> for BobCarolChoreography {
fn run(self, op: &impl ChoreoOp) -> BobCarolResult {
type L = LocationSet!(Bob, Carol);
fn run(self, op: &impl ChoreoOp<Self::L>) -> BobCarolResult {
let is_even_at_bob: Located<bool, Bob> = op.locally(Bob, |un| {
let x = un.unwrap(&self.x_at_bob);
x % 2 == 0
Expand All @@ -154,15 +159,16 @@ impl Choreography<BobCarolResult> for BobCarolChoreography {
struct MainChoreography;

impl Choreography for MainChoreography {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!(Alice, Bob, Carol);
fn run(self, op: &impl ChoreoOp<Self::L>) {
let x_at_alice = op.locally(Alice, |_| {
get_random_number()
});
let x_at_bob = op.comm(Alice, Bob, &x_at_alice);
let BobCarolResult {
is_even_at_bob,
is_even_at_carol,
} = op.colocally(&[Bob.name(), Carol.name()], BobCarolChoreography {
} = op.colocally(BobCarolChoreography {
x_at_bob,
});
// can access is_even_at_bob and is_even_at_carol using `locally` on Bob and Carol
Expand Down
10 changes: 6 additions & 4 deletions chorus_book/src/guide-higher-order-choreography.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ When you implement the `Choreography` trait, you have access to the `sub_choreo`
# struct HigherOrderChoreography<C: Choreography> {
# sub_choreo: C,
# };
impl<C: Choreography> Choreography for HigherOrderChoreography<C> {
fn run(self, op: &impl ChoreoOp) {
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);
}
}
Expand All @@ -45,8 +46,9 @@ struct HigherOrderChoreography<C: Choreography<Located<bool, Alice>> + SubChoreo
_marker: PhantomData<C>,
};

impl<C: Choreography<Located<bool, Alice>> + SubChoreography> Choreography for HigherOrderChoreography<C> {
fn run(self, op: &impl ChoreoOp) {
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
});
Expand Down
35 changes: 22 additions & 13 deletions chorus_book/src/guide-input-and-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ struct DemoChoreography {
}

impl Choreography for DemoChoreography {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!();
fn run(self, op: &impl ChoreoOp<Self::L>) {
println!("Input: {}", self.input);
}
}
Expand All @@ -32,15 +33,17 @@ You can construct an instance of the choreography with the input and pass it to
# input: String,
# }
# impl Choreography for DemoChoreography {
# fn run(self, op: &impl ChoreoOp) {
# 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, transport);

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

Expand All @@ -55,7 +58,8 @@ struct DemoChoreography {
}

impl Choreography for DemoChoreography {
fn run(self, op: &impl ChoreoOp) {
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);
Expand All @@ -81,14 +85,15 @@ To run the sample choreography above at Alice, we use the `local` method to cons
# }
#
# impl Choreography for DemoChoreography {
# fn run(self, op: &impl ChoreoOp) {
# 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, transport);
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
Expand All @@ -107,14 +112,15 @@ For Bob, we use the `remote` method to construct the located value.
# }
#
# impl Choreography for DemoChoreography {
# fn run(self, op: &impl ChoreoOp) {
# 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, transport);
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
Expand All @@ -135,7 +141,8 @@ To do so, we specify the output type to the `Choreography` trait and return the
struct DemoChoreography;

impl Choreography<String> for DemoChoreography {
fn run(self, op: &impl ChoreoOp) -> String {
type L = LocationSet!();
fn run(self, op: &impl ChoreoOp<Self::L>) -> String {
"Hello, World!".to_string()
}
}
Expand All @@ -148,12 +155,13 @@ impl Choreography<String> for DemoChoreography {
# struct DemoChoreography;
#
# impl Choreography<String> for DemoChoreography {
# fn run(self, op: &impl ChoreoOp) -> String {
# 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, transport);
let projector = Projector::new(Alice, alice_transport);
let output = projector.epp_and_run(choreo);
assert_eq!(output, "Hello, World!".to_string());
```
Expand All @@ -167,14 +175,15 @@ You can use the `Located<V, L1>` as a return type of the `run` method to return
struct DemoChoreography;

impl Choreography<Located<String, Alice>> for DemoChoreography {
fn run(self, op: &impl ChoreoOp) -> Located<String, Alice> {
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, transport);
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());
Expand Down
3 changes: 2 additions & 1 deletion chorus_book/src/guide-location-polymorphism.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ struct LocationPolymorphicChoreography<L1: ChoreographyLocation> {
}

impl<L1: ChoreographyLocation> Choreography for LocationPolymorphicChoreography<L1> {
fn run(self, op: &impl ChoreoOp) {
type L = LocationSet!(L1);
fn run(self, op: &impl ChoreoOp<Self::L>) {
op.locally(self.location, |_| {
println!("Hello, World!");
});
Expand Down
19 changes: 18 additions & 1 deletion chorus_book/src/guide-locations.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ The `ChoreographyLocation` trait provides the `name` method, which returns the n
# #[derive(ChoreographyLocation)]
# struct Bob;
#
let name = Alice.name();
let name = Alice::name();
assert_eq!(name, "Alice");
```

## Location Set

A `LocationSet` is a special type representing a set of `ChoreographyLocation` types. It's used to ensure type safety within the system, and you'll see its application in future sections. To build a `LocationSet` type, you can use the `LocationSet` macro from the `chorus_lib` crate.

```rust
# extern crate chorus_lib;
# use chorus_lib::core::ChoreographyLocation;
# #[derive(ChoreographyLocation)]
# struct Alice;
#
# #[derive(ChoreographyLocation)]
# struct Bob;
use chorus_lib::core::LocationSet;

type L = LocationSet!(Alice, Bob);
```
Loading

0 comments on commit 286794d

Please sign in to comment.