Worker Pools in PureScript

Our example demonstrates how to implement a worker pool using PureScript’s asynchronous capabilities.

module Main where

import Prelude

import Effect (Effect)
import Effect.Console (log)
import Effect.Aff (Aff, launchAff_, delay)
import Effect.Class (liftEffect)
import Data.Array (range)
import Data.Int (toNumber)
import Control.Parallel (parallel, sequential)

-- Here's the worker function, of which we'll run several
-- concurrent instances. These workers will receive
-- work on the `jobs` array and return the corresponding
-- results. We'll use `delay` to simulate an expensive task.
worker :: Int -> Int -> Aff Int
worker id job = do
  liftEffect $ log $ "worker " <> show id <> " started  job " <> show job
  delay $ 1000.0 -- Simulate work with a 1-second delay
  liftEffect $ log $ "worker " <> show id <> " finished job " <> show job
  pure $ job * 2

-- This function creates a worker that processes multiple jobs
createWorker :: Int -> Array Int -> Aff (Array Int)
createWorker id jobs = sequential $ map (parallel <<< worker id) jobs

main :: Effect Unit
main = launchAff_ do
  let 
    numJobs = 5
    jobs = range 1 numJobs
    
  -- This starts up 3 workers, each processing a subset of the jobs
  results <- sequential $ 
    [ createWorker 1 [1, 4]
    , createWorker 2 [2, 5]
    , createWorker 3 [3]
    ]
  
  -- Print the results
  liftEffect $ log $ "Results: " <> show (join results)

In this PureScript version, we use the Aff monad for asynchronous operations. The worker function simulates work with a delay, and createWorker processes multiple jobs for a single worker.

We create three workers, each handling a subset of the jobs. The parallel and sequential functions from Control.Parallel are used to run the workers concurrently.

To run the program, you would typically use the PureScript compiler (purs) and Node.js:

$ purs compile Main.purs
$ node -e "require('./output/Main').main()"
worker 1 started  job 1
worker 2 started  job 2
worker 3 started  job 3
worker 1 finished job 1
worker 1 started  job 4
worker 2 finished job 2
worker 2 started  job 5
worker 3 finished job 3
worker 1 finished job 4
worker 2 finished job 5
Results: [2,8,4,10,6]

The program shows the 5 jobs being executed by various workers. It takes about 2 seconds to complete despite doing about 5 seconds of total work because there are 3 workers operating concurrently.

Note that PureScript’s approach to concurrency is different from traditional multithreading. It uses asynchronous operations and the Aff monad to handle concurrent tasks, which is more similar to JavaScript’s event loop model than to OS-level threads.