syndicate-2017/racket/syndicate/examples/two-buyer-protocol-monolith...

221 lines
8.5 KiB
Racket

#lang syndicate/monolithic
(require racket/set)
(require syndicate/comprehensions)
(struct book-quote (title price) #:prefab)
(struct order (title price id delivery-date) #:prefab)
(struct split-proposal (title price contribution accepted) #:prefab)
(define (while-relevant-assert P #:noisy? [noisy #f])
(define name (format "(while-relevant-assert ~a)" P))
(actor/stateless
#:name name
(lambda (e)
(match e
[(scn t)
(when noisy
(log-info "~v receives ~v" name (trie->pretty-string t)))
(if (trie-empty? (trie-project t (observe P)))
#f
(quit))]
[_ #f]))
(list (scn/union (assertion (observe (observe P)))
(assertion P)))))
;; AssertionSet
(define seller-interests
(assertion-set-union (assertion (observe (observe (book-quote ? ?))))
(assertion (observe (observe (order ? ? ? ?))))))
;; Event Inventory -> AssertionSet
(define (answer-quotes e inv)
(match e
[(scn t)
(for-trie/fold ([quotes trie-empty])
([(observe (book-quote $title _)) t])
(assertion-set-union quotes (assertion (book-quote title (hash-ref inv title #f)))))]
[_ trie-empty]))
;; Event Inventory -> (Values Inventory (ListOf actor))
(define (answer-orders e inv)
(match e
[(scn t)
(define-values (actions new-inv)
(for-trie/fold ([actions '()]
[inv inv])
([(observe (order $title $offer-price _ _)) t])
(define asking-price (hash-ref inv 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.
;; (make sure this is retracted somewhere)
(values (cons (while-relevant-assert (order title offer-price #f #f)) actions)
inv)]
[else
;; Allocate an order ID.
;;
(define order-id next-order-id)
(set! next-order-id (+ order-id 1))
;; Remove the book from our shelves.
;;
(define new-inv (hash-remove inv title))
;; Tell the ordering party their order ID and delivery date.
(values (cons (while-relevant-assert (order title offer-price order-id "March 9th"))
actions)
new-inv)])))
(values new-inv actions)]
[_ (values inv '())]))
;; Seller
;; Respond to requests for quotes
;; Respond to proposed offers
;; STATE: (Hashof string -> number)
(define next-order-id 10001483)
(define (seller-behavior e inv)
(cond
[(scn? e)
(define quotes (answer-quotes e inv))
(define-values (new-inv actions) (answer-orders e inv))
(transition new-inv (cons (scn/union seller-interests quotes) actions))]
[else #f]))
(define (seller inv)
(actor #:name 'seller
seller-behavior
inv
(scn seller-interests)))
(define (buyer-a titles budget)
(define (log-offer title contrib)
(log-info "A makes an offer to split the price of ~v, contributing ~a"
title
contrib))
(define (try-to-buy titles budget)
(match titles
['() (log-info "A has bought everything they wanted!") (scn trie-empty)]
[(cons title remaining-titles)
(actor/stateless
#:name (format "(try-to-buy ~a)" title)
(lambda (e)
(match e
[(scn t)
(define maybe-price (set-first (trie-project/set/single t (book-quote title (?!)))))
(cond
[maybe-price
(log-info "A learns that the price of ~v is ~a" title maybe-price)
(quit (negotiate-split title maybe-price remaining-titles budget))]
[else
(log-info "A learns that ~v is out-of-stock." title)
(quit (try-to-buy remaining-titles budget))])]
[_ #f]))
(list (scn (assertion (observe (book-quote title ?))))))]))
(define (negotiate-split title price remaining-titles budget)
(define initial-offer (min budget (/ price 2)))
(cond
[(> initial-offer budget)
;; Don't have enough money
(log-info "A does not have enough money for ~v." title)
(try-to-buy remaining-titles budget)]
[else
(log-offer title initial-offer)
(actor
#:name (format "(negotiate-split ~a ~a)" title price)
(lambda (e my-contribution)
(match e
[(scn (and t (? (compose not trie-empty?))))
(define accepted?
(set-first (trie-project/set/single t (split-proposal title price ? (?!)))))
(cond
[accepted?
(define remaining-budget (- budget my-contribution))
(log-info "A learns that the split-proposal for ~v was accepted, leaving them with ~v remaining" title remaining-budget)
(quit (try-to-buy remaining-titles remaining-budget))]
[else
(log-info "A learns that the split-proposal for ~v was rejected" title)
(let ([my-contribution (+ my-contribution (/ (- price my-contribution) 2))])
(cond
[(> my-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)
(quit (try-to-buy remaining-titles budget))]
[(> my-contribution budget)
;; Don't have enough money
(log-info "A does not have enough money for ~v." title)
(quit (try-to-buy remaining-titles budget))]
[else
(log-offer title my-contribution)
(transition my-contribution
(list (scn (assertion (observe (split-proposal title price my-contribution ?))))))]))])]
[_ #f]))
initial-offer
(list (scn (assertion (observe (split-proposal title price initial-offer ?))))))]))
(try-to-buy titles budget))
(define (buyer-b funds)
(define (complete-purchase title price contrib)
(actor/stateless
#:name (format "(complete-purchase ~a ~a ~a)" title price contrib)
(lambda (e)
(match e
[(scn t)
(match-define (list order-id delivery-date)
(set-first (trie-project/set #:take 2 t (order title price (?!) (?!)))))
(log-info "The order for ~v has id ~a, and will be delivered on ~a"
title
order-id
delivery-date)
(quit)]
[_ #f]))
(list (scn/union (assertion (split-proposal title price contrib #t))
(assertion (observe (order title price ? ?)))))))
(actor
#:name 'buyer-b
(lambda (e funds)
(match e
[(scn t)
(define-values (remaining-funds actions)
(for-trie/fold ([funds funds]
[actions (list)])
([(observe (split-proposal $title $price $contrib _)) t])
(define my-contribution (- price contrib))
(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)
(values funds
(cons (while-relevant-assert (split-proposal title price contrib #f))
actions))]
[else
(define remaining-funds (- funds my-contribution))
(log-info "B accepts the offer, leaving them with ~a remaining funds"
remaining-funds)
(values remaining-funds
(cons (complete-purchase title price contrib)
actions))])))
(transition remaining-funds actions)]
[_ #f]))
funds
(list (scn (assertion (observe (observe (split-proposal ? ? ? ?))))))))
(seller (hash "The Wind in the Willows" 3.95
"Catch 22" 2.22
"Candide" 34.95))
(buyer-a (list "Catch 22"
"Encyclopaedia Brittannica"
"Candide"
"The Wind in the Willows")
35.00)
(buyer-b 5.00)