Stateful Goroutines in Idris
Our example demonstrates how to manage state using processes in Idris. This approach aligns with Idris’s functional programming paradigm and its support for concurrent programming through processes.
import Control.Concurrent
import Data.IORef
import System.Random
-- In this example, our state will be owned by a single process.
-- This ensures that the data is never corrupted with concurrent access.
-- Other processes will send messages to the owning process to read or write state.
-- Define message types for read and write operations
data ReadOp = MkReadOp Int (Channel Int)
data WriteOp = MkWriteOp Int Int (Channel Bool)
main : IO ()
main = do
-- We'll count how many operations we perform
readOps <- newIORef 0
writeOps <- newIORef 0
-- Create channels for read and write requests
reads <- makeChannel
writes <- makeChannel
-- Start the process that owns the state
_ <- fork $ do
state <- newIORef (the (List (Int, Int)) [])
forever $ do
select
[ caseChannel reads $ \(MkReadOp key resp) -> do
s <- readIORef state
case lookup key s of
Just val -> channelPut resp val
Nothing -> channelPut resp 0
, caseChannel writes $ \(MkWriteOp key val resp) -> do
modifyIORef state (\s => (key, val) :: (filter (\(k, _) -> k /= key) s))
channelPut resp True
]
-- Start 100 processes to issue reads
replicateM_ 100 $ fork $ forever $ do
key <- randomRIO (0, 4)
resp <- makeChannel
channelPut reads (MkReadOp key resp)
_ <- channelGet resp
modifyIORef readOps (+1)
sleepMillis 1
-- Start 10 processes to issue writes
replicateM_ 10 $ fork $ forever $ do
key <- randomRIO (0, 4)
val <- randomRIO (0, 99)
resp <- makeChannel
channelPut writes (MkWriteOp key val resp)
_ <- channelGet resp
modifyIORef writeOps (+1)
sleepMillis 1
-- Let the processes work for a second
sleepMillis 1000
-- Capture and report the op counts
readOpsFinal <- readIORef readOps
putStrLn $ "readOps: " ++ show readOpsFinal
writeOpsFinal <- readIORef writeOps
putStrLn $ "writeOps: " ++ show writeOpsFinal
-- Helper function to sleep for a given number of milliseconds
sleepMillis : Int -> IO ()
sleepMillis ms = threadDelay (ms * 1000)
This Idris program demonstrates state management using processes and channels. Here’s a breakdown of the key components:
We define
ReadOp
andWriteOp
types to encapsulate read and write requests.The main function sets up channels for reads and writes, and starts a process to manage the state.
The state-owning process uses a
select
statement to handle incoming read and write requests.We start 100 processes for reads and 10 for writes, each sending requests through the appropriate channels.
After letting the processes run for a second, we report the number of read and write operations performed.
Running this program would show the number of read and write operations completed in one second. The exact numbers may vary, but you’d see output similar to:
readOps: 71708
writeOps: 7177
This process-based approach in Idris provides a way to manage shared state safely in a concurrent environment. It’s particularly useful when dealing with complex state management scenarios or when working with multiple channels. The choice between this approach and other concurrency patterns depends on the specific requirements of your program and what feels most natural for ensuring correctness.