Closing Channels in F#

open System

// In this example we'll use a `jobs` channel to communicate work to be done 
// from the main function to a worker function. When we have no more jobs for
// the worker we'll close the `jobs` channel.

let main() =
    let jobs = new System.Collections.Concurrent.BlockingCollection<int>(5)
    let done = new System.Threading.ManualResetEvent(false)

    // Here's the worker function. It repeatedly receives from `jobs`.
    // We use TryTake to check if the collection is completed and all
    // values have been received. We use this to notify on `done` when 
    // we've worked all our jobs.
    let worker() =
        let mutable more = true
        while more do
            match jobs.TryTake() with
            | true, j -> 
                printfn "received job %d" j
            | false, _ -> 
                printfn "received all jobs"
                done.Set() |> ignore
                more <- false

    // Start the worker
    Async.Start(worker)

    // This sends 3 jobs to the worker over the `jobs` channel, then closes it.
    for j in 1..3 do
        jobs.Add(j)
        printfn "sent job %d" j
    
    jobs.CompleteAdding()
    printfn "sent all jobs"

    // We await the worker using the synchronization approach
    done.WaitOne() |> ignore

    // Checking if the channel is closed
    let moreJobs = not jobs.IsCompleted
    printfn "received more jobs: %b" moreJobs

main()

This F# code demonstrates the concept of closing channels using BlockingCollection<T>, which is similar to Go’s channels. Here’s a breakdown of the translation:

  1. We use BlockingCollection<int> to represent the jobs channel. This collection allows adding and taking items in a thread-safe manner.

  2. Instead of Go’s done channel, we use a ManualResetEvent for synchronization.

  3. The worker function is defined as worker(). It uses TryTake() to attempt to retrieve jobs from the collection. If TryTake() returns false, it means the collection is completed and empty.

  4. We start the worker as an asynchronous operation using Async.Start(worker).

  5. Jobs are added to the collection using jobs.Add(j).

  6. After sending all jobs, we call jobs.CompleteAdding() to indicate that no more items will be added. This is equivalent to closing the channel in Go.

  7. We wait for the worker to finish using done.WaitOne().

  8. Finally, we check if the collection is completed using jobs.IsCompleted, which is similar to checking if a channel is closed in Go.

To run this program, save it as ClosingChannels.fs and use the F# compiler:

$ fsharpc ClosingChannels.fs
$ mono ClosingChannels.exe

This example demonstrates how to use BlockingCollection<T> in F# to achieve similar behavior to Go’s channel closing. The concept of closed channels leads naturally to iterating over collections, which we’ll explore in the next example.