[plt-scheme] another game: pacman

From: Matt Jadud (mcj4 at kent.ac.uk)
Date: Thu Aug 17 22:05:39 EDT 2006

Hi Jon,

Jon Rafkind wrote:
> That sounds interesting and I think I understand it but can you provide
> a small example so I can analyze it more deeply?


I'm not sure where I'm mixing CSP and occam, but here's a few CSPish 
primitives knocked together, and a trivial producer-consumer example.

Cheers,
Matt

;; MzScheme has omnidirectional channels. Although
;; not strictly necessary, I'm going to make the channels
;; unidirectional. This way, I can't accidentally send
;; data in the wrong direction in a process network.
(define-struct ch! (e))
(define-struct ch? (e))

;; make-csp-channel : void -> (values ch? ch!)
;; Returns two values, the input end of a channel,
;; and the output end of a channel.
(define (make-csp-channel)
   (let ([channel (make-channel)])
     (values (make-ch? channel)
             (make-ch! channel))))

;; par : syntax
;; Allows me to easily run several expressions in
;; parallel, and the entire 'par' expression will
;; block the parent thread until all subexpressions
;; have finished executing.
(define-syntax (par stx)
   (syntax-case stx ()
     [(par exp exp* ...)
      #`(begin
         (let ([thread-descriptors
                (list
                 #,@(map (lambda (e)
                           #`(thread (lambda () #,e)))
                         (cons #`exp
                               (syntax->list #`(exp* ...)))))])
           (for-each
            (lambda (thread-descriptor)
              (thread-wait thread-descriptor))
            thread-descriptors)))]))

;; ! : ch! any -> void
;; CSP uses the '!' to denote sending data down a channel.
;; I'll use that, too, and do some rudimentary checking to make
;; sure that I'm sending data down the channel in the
;; correct direction.
(define (! ch val)
   (cond
     [(not (ch!? ch)) (error '! "First argument to '!' must be a channel.")]
     [(ch?? ch) (error '! "Cannot send down receiving end of channel.")]
     [else
      (channel-put (ch!-e ch) val)]))

;; ? : syntax
;; This is a syntax, because it mutates the variable expression.
;; Could obviously be done in some other manner.
;; The '?' operator reads data from the receiving end of a channel.
;; Again, borrowing from the CSP notation. Rudimentary error
;; checking to keep me from waiting on a channel end that
;; will never receive data.
(define-syntax (? stx)
   (syntax-case stx ()
     [(? ch var)
      #`(cond
          [(not (ch?? ch)) (error '? "First argument to '?' must be a 
channel.")]
          [(ch!? ch) (error '? "Cannot receive from the receiving end of 
channel.")]
          [else
           (set! var (channel-get (ch?-e ch)))])
      ]))

;; A trivial example; prints some combination of A, B, and C.
(define (ex1)
   (par (printf "A~n")
        (printf "B~n")
        (printf "C~n")))

;; Still trivial, but should spell CAB.
(define (ex2)
   (par (begin (sleep 3)
               (printf "A"))
        (begin (sleep 5)
               (printf "B"))
        (begin (sleep 1)
               (printf "C"))))

;; forever : syntax
;; Creates a loop that executes until broken
;; by some cataclysmic event.
(define-syntax (forever stx)
   (syntax-case stx ()
     [(forever body bodies ...)
      #`(let loop ()
          body
          bodies ...
          (loop))]))

;; producer-consumer : void -> ...
;; This example is still simple, but it demonstrates
;; channel communication across thread boundaries.
;; In parallel, I have two loops that don't terminate
;; save for under exceptional circumstances. In the first
;; loop, I increment 'x', and then send it down the channel.
;; Execution on this loop blocks until the corresponding read
;; operation takes place.
;;
;; In the second loop, I forever read and print the value
;; send over the channel. The loop blocks indefinitely until
;; a value is sent.

(define (producer-consumer)
   (let-values ([(in? out!)
                 (make-csp-channel)])
     (par (let ([x 0])
            (forever
             (set! x (add1 x))
             (! out! x)))
          (let ([y 0])
            (forever
             (? in? y)
             (printf "~a~n" y)
             )))
     ))

;; The producer-consumer example is poorly behaved.
;; Normally, you'd want some way of gracefully
;; shutting down your concurrent processes, as the current
;; example doesn't terminate when you press "Stop" in
;; the DrScheme REPL. Instead, you need to hit "Run"
;; to flush the environment. However, it gives you a
;; quick look at some CSPish primitives. Note that in CSP
;; there are rules about shared variable access across
;; processes (as in, there is none), as all sharing of data
;; is handled explicitly through channel communications.
;;
;; For more on CSP, see http://www.usingcsp.com/


Posted on the users mailing list.