Concurrency
This example showcases asynchronous programming and variant types.
from comm import Channel
struct StoreItem {
variant Missing {
var name: string
}
variant Available {
var name: string
var price: dec
}
}
struct ItemLookupRequest {
var item_name: string
var respond_to: Channel(@type: StoreItem)
}
struct ShoppingListItem {
variant Missing {
var store_name: string
var name: string
}
variant Available {
var store_name: string
var name: string
var price: dec
}
}
struct StoreResult {
variant Empty {}
variant MissingItems {
var name: string
var total: dec
}
variant FullyStocked {
var name: string
var total: dec
}
}
array ShoppingList(@type: string)
struct StoreStruct {
variant NotListening {
var name: string
var prices: map(@key_type: string, @value_type: dec)
var channel: Channel(@type: ItemLookupRequest)
}
variant Listening {
var name: string
var prices: map(@key_type: string, @value_type: dec)
var channel: Channel(@type: ItemLookupRequest)
var listener: Thread
}
}
type Store(StoreStruct) {
fn add_item!(name: string, price: dec) {
self.prices[name] = price
}
fn check_price(item_name: string): StoreItem {
with (price: self.items[item_name]) {
return StoreItem(available: true, price: price)
}
return StoreItem(available: false)
}
async listen() {
loop {
with ((rx: self.requests.rx),
(req: rx.next()),
(tx: req.respond_to.tx)) {
tx.send(self.check_price(req.name))
}
}
}
}
dynarray StoreList(@type: Store)
type Stores(StoreList) {
fn append!(store: *Store) {
if (store->NotListening) {
iferr(store->Listening(store.listener: store.listen()) {
echo("Store failed to start listening: ${e.msg}")
return
}
}
StoreList.append(*store)
}
fn check_price(item_name: string): StoreItem {
for (store in self.stores) {
with (tx: store.store.channel.tx) {
var channel: Channel(StoreItem)
with (rx: channel.rx) {
tx.send(ItemLookupRequest(
item_name: item_name,
respond_to: &rx
))
with (item: rx.next(timeout: 10)) {
return item
}
return StoreItem.Missing(available: false)
}
}
}
}
fn find_best_store(shopping_list: &ShoppingList): StoreResult {
var result: StoreResult->Empty()
with (stores: &self.stores) {
range store_indices: [0..stores.count())
for (i in store_indices) {
var channel: Channel(StoreItem)
var store: &stores[i]
var store_result = StoreResult->FullyStocked(
name: store.name,
total: 0.0
)
with ((tx: store.channel.tx), (rx: channel.rx)) {
for (item_name in shopping_list) {
tx.send(ItemLookupRequest(
item_name: item_name,
respond_to: &rx # This is bananas
))
with (item: rx.next(timeout: 10)) {
if (item->Available) {
store_result.total += item.price
}
else {
store_result->MissingItems(
name: store_result.name
total: store_result.total
)
}
}
}
}
if (result->Empty) {
if ((store_result->MissingItems) || (store_result->FullyStocked)) {
result = store_result
}
}
else if (result->MissingItems) {
if (store_result->MissingItems) {
if (store_result.total > result.total) {
result = store_result
}
}
else if ((store_result->FullyStocked) && {
result = store_result
}
}
else if (result->FullyStocked) {
if (store_result.total > result.total) {
result = store_result
}
}
}
}
return result
}
}
fn main() {
var shopping_list: ShoppingList(["chocolate", "doll", "bike"])
var stores: Stores()
var rmart: *Store("R-mart")
var bullseye: *Store("Bullseye")
var woolmart: *Store("Woolmart")
rmart.add_item("chocolate", 5.0)
rmart.add_item("doll", 22.0)
rmart.add_item("bike", 150.0)
rmart.add_item("broccoli", .8)
rmart.add_item("tomato", .28)
stores.add_store(*rmart)
bullseye.add_item("chocolate", 2.0)
bullseye.add_item("doll", 23.0)
bullseye.add_item("bike", 145.0)
bullseye.add_item("broccoli", 1.2)
bullseye.add_item("tomato", .6)
stores.add_store(*bullseye)
woolmart.add_item("chocolate", 5.0)
woolmart.add_item("doll", 23.0)
woolmart.add_item("bike", 146.0)
woolmart.add_item("broccoli", .69)
woolmart.add_item("tomato", .19)
stores.add_store(*woolmart)
stores.start_up()
var best_store: stores.find_best_store(&shopping_list)
echo("Best store: ${best_store.name}")
}
# vi: set et sw=4 ts=4 tw=79: