Select in Scheme

Our example demonstrates how to wait on multiple channel-like operations in Scheme. We’ll use threads and message passing to simulate Go’s select functionality.

(import (rnrs) (rnrs threads))

(define (main)
  ; For our example we'll select across two channels.
  (define c1 (make-channel))
  (define c2 (make-channel))

  ; Each channel will receive a value after some amount
  ; of time, to simulate e.g. blocking RPC operations
  ; executing in concurrent threads.
  (thread
   (lambda ()
     (thread-sleep! 1)
     (channel-put! c1 "one")))
  
  (thread
   (lambda ()
     (thread-sleep! 2)
     (channel-put! c2 "two")))

  ; We'll use a custom select function to await both of these values
  ; simultaneously, printing each one as it arrives.
  (do ((i 0 (+ i 1)))
      ((= i 2))
    (let ((result (select c1 c2)))
      (display "received ")
      (display result)
      (newline))))

; Custom select function to simulate Go's select
(define (select . channels)
  (let ((result #f))
    (let loop ()
      (for-each
       (lambda (ch)
         (let ((msg (channel-get-nb ch)))
           (when msg
             (set! result msg))))
       channels)
      (when (not result)
        (thread-sleep! 0.1)
        (loop)))
    result))

; Channel implementation
(define (make-channel)
  (let ((mutex (make-mutex))
        (condition (make-condition))
        (queue '()))
    (lambda (op . args)
      (mutex-lock! mutex)
      (case op
        ((put!)
         (set! queue (append queue args))
         (condition-signal! condition))
        ((get)
         (if (null? queue)
             (begin
               (mutex-unlock! mutex condition)
               (mutex-lock! mutex)
               (car queue))
             (let ((item (car queue)))
               (set! queue (cdr queue))
               item)))
        ((get-nb)
         (if (null? queue)
             #f
             (let ((item (car queue)))
               (set! queue (cdr queue))
               item))))
      (mutex-unlock! mutex))))

(define (channel-put! ch item)
  (ch 'put! item))

(define (channel-get ch)
  (ch 'get))

(define (channel-get-nb ch)
  (ch 'get-nb))

(main)

This Scheme code simulates the behavior of Go’s select statement using threads and a custom channel implementation. Here’s a breakdown of the code:

  1. We define two channels c1 and c2 using our custom make-channel function.

  2. We create two threads that will send messages to these channels after a delay, simulating concurrent operations.

  3. The select function is implemented to check multiple channels for available messages, similar to Go’s select statement.

  4. In the main loop, we use our select function to wait for messages from either channel and print them as they arrive.

  5. The channel implementation uses mutexes and condition variables to ensure thread-safe operations.

To run this program, you would save it to a file (e.g., select.scm) and run it with a Scheme interpreter that supports the SRFI-18 threading library, such as Chez Scheme:

$ chez --script select.scm
received one
received two

Note that the exact timing and order of messages may vary due to the nature of concurrent execution, but you should see both “one” and “two” printed.

This example demonstrates how to implement concurrent operations and channel-like communication in Scheme, providing functionality similar to Go’s select statement.