Select in Haskell

Our example demonstrates the use of select to wait on multiple channel operations. Combining concurrent operations with select is a powerful feature of this language.

import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.Chan
import Control.Monad (forM_)

main :: IO ()
main = do
    c1 <- newChan
    c2 <- newChan

    -- Each channel will receive a value after some amount
    -- of time, to simulate e.g. blocking operations
    -- executing concurrently.
    forkIO $ do
        threadDelay 1000000  -- 1 second
        writeChan c1 "one"

    forkIO $ do
        threadDelay 2000000  -- 2 seconds
        writeChan c2 "two"

    -- We'll use `select` to await both of these values
    -- simultaneously, printing each one as it arrives.
    forM_ [1..2] $ \_ -> do
        select
            [ (c1, \msg -> putStrLn $ "received " ++ msg)
            , (c2, \msg -> putStrLn $ "received " ++ msg)
            ]

-- Implement a simple select function
select :: [(Chan a, a -> IO ())] -> IO ()
select chans = do
    result <- waitForFirst chans
    case result of
        Just (_, action, value) -> action value
        Nothing -> return ()

waitForFirst :: [(Chan a, a -> IO ())] -> IO (Maybe (Chan a, a -> IO (), a))
waitForFirst [] = return Nothing
waitForFirst ((chan, action):rest) = do
    value <- tryReadChan chan
    case value of
        Just v  -> return $ Just (chan, action, v)
        Nothing -> waitForFirst rest

In this example, we create two channels and start two concurrent operations that will write to these channels after a delay. We then use a select-like function to await values from both channels simultaneously, printing each value as it arrives.

To run the program:

$ ghc -threaded select.hs
$ ./select
received one
received two

Note that the total execution time is only ~2 seconds since both the 1 and 2 second delays execute concurrently.

It’s worth noting that Haskell doesn’t have a built-in select construct like some other languages. The select function implemented here is a simplified version that demonstrates the concept. In practice, for more robust concurrent programming in Haskell, you might use libraries like async or stm which provide more sophisticated tools for managing concurrent operations.