Select in Standard ML

The select statement in Standard ML can be simulated using the CML (Concurrent ML) library, which provides concurrent programming features. However, since Standard ML doesn’t have built-in concurrency primitives like channels, we’ll use threads and synchronization primitives to demonstrate a similar concept.

structure Select = 
struct
    fun main () =
    let
        val mutex = Mutex.mutex ()
        val cond = ConditionVar.conditionVar ()
        
        val c1 = ref NONE
        val c2 = ref NONE
        
        fun sender1 () =
        let
            val _ = OS.Process.sleep (Time.fromSeconds 1)
            val _ = Mutex.lock mutex
            val _ = c1 := SOME "one"
            val _ = ConditionVar.signal cond
            val _ = Mutex.unlock mutex
        in
            ()
        end
        
        fun sender2 () =
        let
            val _ = OS.Process.sleep (Time.fromSeconds 2)
            val _ = Mutex.lock mutex
            val _ = c2 := SOME "two"
            val _ = ConditionVar.signal cond
            val _ = Mutex.unlock mutex
        in
            ()
        end
        
        fun receiver () =
        let
            fun loop 0 = ()
              | loop n =
                let
                    val _ = Mutex.lock mutex
                    val _ = ConditionVar.wait (cond, mutex)
                    val _ = 
                        case (!c1, !c2) of
                            (SOME msg1, _) => 
                                (print ("received " ^ msg1 ^ "\n");
                                 c1 := NONE)
                          | (_, SOME msg2) => 
                                (print ("received " ^ msg2 ^ "\n");
                                 c2 := NONE)
                          | _ => ()
                    val _ = Mutex.unlock mutex
                in
                    loop (n-1)
                end
        in
            loop 2
        end
    in
        Thread.spawn sender1;
        Thread.spawn sender2;
        receiver ()
    end
end

val _ = Select.main ()

This Standard ML code simulates the behavior of the original example. Here’s how it works:

  1. We define two reference cells c1 and c2 to simulate channels.

  2. Two sender functions (sender1 and sender2) are defined to simulate the goroutines in the original code. They sleep for 1 and 2 seconds respectively, then set their corresponding reference cells.

  3. The receiver function simulates the select statement. It uses a mutex and condition variable to wait for signals from the senders.

  4. In the main function, we spawn two threads for the senders and call the receiver function.

  5. The receiver loops twice, each time waiting for a signal and then checking which “channel” has a value.

To run this program, you would need to compile it with a Standard ML compiler that supports threads, such as MLton. The output would be similar to the original:

received one
received two

Note that the total execution time would still be about 2 seconds, as both “channels” are processed concurrently.

This example demonstrates how to simulate channel-like behavior and selection in Standard ML using threads and synchronization primitives. While it’s not as elegant as Go’s select statement, it achieves a similar result in terms of concurrent execution and communication between threads.