Closing Channels in Haskell

In Haskell, we can implement a similar concept of closing channels using the Control.Concurrent.Chan module. However, Haskell doesn’t have a built-in mechanism for closing channels. Instead, we’ll use a special value to indicate the end of communication.

import Control.Concurrent
import Control.Concurrent.Chan
import Control.Monad (forM_, when)

main :: IO ()
main = do
    jobs <- newChan
    done <- newChan

    -- Here's the worker thread. It repeatedly receives from `jobs`.
    -- We use `Nothing` as a sentinel value to indicate that all jobs have been processed.
    forkIO $ do
        let loop = do
                job <- readChan jobs
                case job of
                    Just j -> do
                        putStrLn $ "received job " ++ show j
                        loop
                    Nothing -> do
                        putStrLn "received all jobs"
                        writeChan done True

        loop

    -- This sends 3 jobs to the worker over the `jobs` channel, then sends the sentinel value.
    forM_ [1..3] $ \j -> do
        writeChan jobs (Just j)
        putStrLn $ "sent job " ++ show j
    
    writeChan jobs Nothing
    putStrLn "sent all jobs"

    -- We await the worker using channel synchronization.
    _ <- readChan done

    -- In Haskell, we don't have a direct equivalent to checking if a channel is closed.
    -- Instead, we can try to read from the channel and see if it's empty.
    empty <- isEmptyChan jobs
    putStrLn $ "jobs channel is empty: " ++ show empty

To run this program, save it to a file (e.g., closing_channels.hs) and use the following commands:

$ ghc closing_channels.hs
$ ./closing_channels
sent job 1
received job 1
sent job 2
received job 2
sent job 3
received job 3
sent all jobs
received all jobs
jobs channel is empty: True

In this Haskell version:

  1. We use Control.Concurrent.Chan to create channels (newChan).
  2. Instead of closing the channel, we send a Nothing value to indicate the end of jobs.
  3. The worker thread runs in a loop, processing jobs until it receives Nothing.
  4. We use forkIO to create a new thread, which is similar to goroutines in concept.
  5. The isEmptyChan function is used to check if the channel is empty, which is the closest equivalent to checking if a channel is closed in this context.

This example demonstrates how to implement a similar pattern of communication and synchronization between threads in Haskell, even though the language doesn’t have a direct equivalent to closing channels.