syndicate-2017/racket/syndicate/examples/actor/two-buyer-protocol.rkt

242 lines
11 KiB
Racket

#lang syndicate
;; An extended two-buyer book-purchase protocol, based loosely on that
;; given in Honda/Yoshida/Carbone 2008, "Multiparty Asynchronous
;; Session Types".
;; SAMPLE OUTPUT:
;;---------------------------------------------------------------------------
;; A learns that the price of "Catch 22" is 2.22
;; A makes an offer to split the price of "Catch 22", contributing 1.11
;; B is being asked to contribute 1.11 toward "Catch 22" at price 2.22
;; B accepts the offer, leaving them with 3.8899999999999997 remaining funds
;; A learns that the split-proposal for "Catch 22" was accepted, leaving them with 33.89 remaining
;; The order for "Catch 22" has id 10001483, and will be delivered on March 9th
;; A learns that "Encyclopaedia Brittannica" is out-of-stock.
;; A learns that the price of "Candide" is 34.95
;; A makes an offer to split the price of "Candide", contributing 17.475
;; B is being asked to contribute 17.475 toward "Candide" at price 34.95
;; B hasn't enough funds (3.8899999999999997 remaining)
;; A learns that the split-proposal for "Candide" was rejected
;; A makes an offer to split the price of "Candide", contributing 26.212500000000002
;; B is being asked to contribute 8.7375 toward "Candide" at price 34.95
;; B hasn't enough funds (3.8899999999999997 remaining)
;; A learns that the split-proposal for "Candide" was rejected
;; A makes an offer to split the price of "Candide", contributing 30.581250000000004
;; B is being asked to contribute 4.368749999999999 toward "Candide" at price 34.95
;; B hasn't enough funds (3.8899999999999997 remaining)
;; A learns that the split-proposal for "Candide" was rejected
;; A makes an offer to split the price of "Candide", contributing 32.765625
;; B is being asked to contribute 2.184375000000003 toward "Candide" at price 34.95
;; B accepts the offer, leaving them with 1.7056249999999968 remaining funds
;; A learns that the split-proposal for "Candide" was accepted, leaving them with 1.1243750000000006 remaining
;; The order for "Candide" has id 10001484, and will be delivered on March 9th
;; A learns that the price of "The Wind in the Willows" is 3.95
;; A makes an offer to split the price of "The Wind in the Willows", contributing 1.1243750000000006
;; B is being asked to contribute 2.8256249999999996 toward "The Wind in the Willows" at price 3.95
;; B hasn't enough funds (1.7056249999999968 remaining)
;; A learns that the split-proposal for "The Wind in the Willows" was rejected
;; A does not have enough money for "The Wind in the Willows".
;; A has bought everything they wanted!
;;---------------------------------------------------------------------------
;; Role: SELLER
;; - when interest in (book-quote $title _) appears,
;; asserts (book-quote title (Option Float)), #f meaning not available,
;; and otherwise an asking-price.
;; - when interest in (order $title $offer-price _ _) appears,
;; asserts (order title offer-price #f #f) for "no sale", otherwise
;; (order title offer-price PositiveInteger String), an accepted sale.
;; Role: BUYER
;; - observes (book-quote title $price) to learn prices.
;; - observes (order title offer-price $id $delivery-date) to make orders.
;; Role: SPLIT-PROPOSER
;; - observes (split-proposal title asking-price contribution $accepted?)
;; to make a split-proposal and learn whether it was accepted or not.
;; Role: SPLIT-DISPOSER
;; - when interest in (split-proposal $title $asking-price $contribution _)
;; appears, asserts (split-proposal title asking-price contribution #t)
;; to indicate they are willing to go through with the deal, in which case
;; they then perform the role of BUYER for title/asking-price, or asserts
;; (split-proposal title asking-price contribution #f) to indicate they
;; are unwilling to go through with the deal.
(struct book-quote (title price) #:prefab) ;; Assertion
(struct order (title price id delivery-date) #:prefab) ;; Assertion
(struct split-proposal (title price contribution accepted) #:prefab) ;; Assertion
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utility syntax. Under consideration for possible addition to actor.rkt.
;;
(define-syntax while-relevant-assert
(syntax-rules ()
[(_ P)
(begin (stop-when (retracted (observe P)))
(assert P))]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SELLER
;;
(define (seller)
(spawn (field [books (hash "The Wind in the Willows" 3.95
"Catch 22" 2.22
"Candide" 34.95)]
[next-order-id 10001483])
;; Give quotes to interested parties.
;;
(during (observe (book-quote $title _))
(assert (book-quote title (hash-ref (books) title #f))))
;; Respond to order requests.
;;
(on (asserted (observe (order $title $offer-price _ _)))
(define asking-price (hash-ref (books) title #f))
(cond
[(or (not asking-price) (< offer-price asking-price))
;; We cannot sell a book we do not have, and we will not sell for less
;; than our asking price.
;;
(react (while-relevant-assert (order title offer-price #f #f)))]
[else
;; Allocate an order ID.
;;
(define order-id (next-order-id))
(next-order-id (+ order-id 1))
;; Remove the book from our shelves.
;;
(books (hash-remove (books) title))
;; Tell the ordering party their order ID and delivery date.
;;
(spawn
(while-relevant-assert
(order title offer-price order-id "March 9th")))]))))
;; Serial SPLIT-PROPOSER
;;
(define (buyer-a)
;; Attempt to entice some SPLIT-DISPOSER to purchase each title in titles, one after another.
;;
(define (try-to-buy titles budget)
(match titles
['()
(log-info "A has bought everything they wanted!")]
[(cons title remaining-titles)
;; First, retrieve a quote for the title, and analyze the result.
;;
(match (react/suspend (yield)
(stop-when (asserted (book-quote title $price)) (yield price)))
[#f
(log-info "A learns that ~v is out-of-stock." title)
(try-to-buy remaining-titles budget)]
[price
(log-info "A learns that the price of ~v is ~a" title price)
;; Next, repeatedly make split offers to a SPLIT-DISPOSER until either one is
;; accepted, or the contribution from the SPLIT-DISPOSER becomes pointlessly small.
;;
(let try-to-split ((contribution (min budget (/ price 2))))
(cond
[(> contribution (- price 0.10))
;; Not worth bothering to split the price. May as well buy it ourselves.
;; TODO: could perform BUYER here
;;
(log-info "A gives up on ~v." title)
(try-to-buy remaining-titles budget)]
[(> contribution budget)
;; Don't have enough money
(log-info "A does not have enough money for ~v." title)
(try-to-buy remaining-titles budget)]
[else
;; Make our proposal, and wait for a response.
;;
(log-info "A makes an offer to split the price of ~v, contributing ~a"
title
contribution)
(react
(stop-when (asserted (split-proposal title price contribution #t))
(define remaining-budget (- budget contribution))
(log-info "A learns that the split-proposal for ~v was accepted, leaving them with ~v remaining" title remaining-budget)
(try-to-buy remaining-titles remaining-budget))
(stop-when (asserted (split-proposal title price contribution #f))
(log-info "A learns that the split-proposal for ~v was rejected" title)
(try-to-split (+ contribution (/ (- price contribution) 2)))))]))])]))
(spawn* (try-to-buy (list "Catch 22"
"Encyclopaedia Brittannica"
"Candide"
"The Wind in the Willows")
35.00)))
;; Serial SPLIT-DISPOSER
;;
(define (buyer-b)
(spawn ;; This actor maintains a record of the amount of money it has to spend.
;;
(field [funds 5.00])
(on (asserted (observe (split-proposal $title $price $their-contribution _)))
(define my-contribution (- price their-contribution))
(log-info "B is being asked to contribute ~a toward ~v at price ~a"
my-contribution
title
price)
(cond
[(> my-contribution (funds))
(log-info "B hasn't enough funds (~a remaining)" (funds))
(react (while-relevant-assert (split-proposal title price their-contribution #f)))]
[else
;; Spawn a small actor (TODO: when we revise actor.rkt's implementation style,
;; this could perhaps be a facet rather than a full actor) to handle the
;; actual purchase now that we have agreed on a split.
;;
(spawn* (define-values (order-id delivery-date)
(react/suspend (yield)
;; While we are in this state, waiting for order confirmation, take
;; the opportunity to signal to our SPLIT-PROPOSER that we accepted
;; their proposal.
;;
(assert (split-proposal title price their-contribution #t))
(stop-when (asserted (order title price $id $date))
;; We have received order confirmation from the SELLER.
;;
(yield id date))))
(log-info "The order for ~v has id ~a, and will be delivered on ~a"
title
order-id
delivery-date))
;; Meanwhile, update our records of our available funds, and continue to wait
;; for more split-proposals to arrive.
;;
(define remaining-funds (- (funds) my-contribution))
(log-info "B accepts the offer, leaving them with ~a remaining funds"
remaining-funds)
(funds remaining-funds)]))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Starting configuration:
;;
(seller)
(buyer-a)
(buyer-b)