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: