Waitgroups in Standard ML

(* This is the function we'll run in every thread. *)
fun worker id =
    let
        val _ = print ("Worker " ^ Int.toString id ^ " starting\n")
        (* Sleep to simulate an expensive task. *)
        val _ = OS.Process.sleep (Time.fromSeconds 1)
        val _ = print ("Worker " ^ Int.toString id ^ " done\n")
    in
        ()
    end

(* Main function *)
fun main () =
    let
        (* This list will hold our threads *)
        val threads = ref []
        
        (* Launch several threads *)
        val _ = List.tabulate (5, fn i =>
            let
                val t = Thread.create (fn () => worker (i+1))
            in
                threads := t :: !threads
            end)
        
        (* Wait for all threads to finish *)
        val _ = List.app Thread.join (!threads)
    in
        ()
    end

(* Run the main function *)
val _ = main ()

This Standard ML code demonstrates a similar concept to the original Go code, using threads instead of goroutines. Here’s an explanation of the key parts:

  1. The worker function is defined to simulate some work. It prints a message when starting and finishing, with a 1-second sleep in between.

  2. In the main function, we create a list to hold our threads.

  3. We use List.tabulate to create 5 threads, each running the worker function with a unique ID.

  4. Each thread is added to our list of threads.

  5. After creating all threads, we use List.app Thread.join to wait for all threads to finish.

  6. Finally, we call the main function to run our program.

Note that Standard ML doesn’t have a direct equivalent to Go’s WaitGroup. Instead, we’re manually keeping track of our threads and joining them at the end. Also, Standard ML’s concurrency model is different from Go’s, so this example uses threads which are more similar to traditional operating system threads.

When you run this program, you’ll see output similar to this:

$ sml threadgroups.sml
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 2 done
Worker 3 done
Worker 4 done
Worker 5 done

The order of workers starting up and finishing is likely to be different for each invocation, just like in the original example.

Note that error handling in this Standard ML version is implicit. If any exceptions occur in the threads, they will be propagated when the thread is joined. For more advanced error handling, you might need to implement your own error propagation mechanism.