Atomic Counters in PureScript

Here’s the translation of the atomic counters example from Go to PureScript:

Our primary mechanism for managing state in PureScript is through pure functions and immutable data structures. However, there are situations where we need to manage shared mutable state. In this example, we’ll look at using the Effect.Ref module for creating mutable references that can be safely accessed and modified in concurrent environments.

module Main where

import Prelude

import Effect (Effect)
import Effect.Console (log)
import Effect.Ref as Ref
import Control.Parallel (parallel, sequential)
import Data.Array (replicate)
import Data.Foldable (traverse_)

main :: Effect Unit
main = do
  -- We'll use a mutable Ref to represent our counter
  ops <- Ref.new 0

  -- We'll start 50 concurrent operations that each increment the
  -- counter exactly 1000 times.
  let incrementers = replicate 50 $ parallel do
        traverse_ (\_ -> Ref.modify_ (_ + 1) ops) (1 .. 1000)

  -- Run all incrementers concurrently and wait for them to finish
  _ <- sequential $ sequence incrementers

  -- Now it's safe to read the final value
  finalCount <- Ref.read ops
  log $ "ops: " <> show finalCount

In this PureScript version:

  1. We use Effect.Ref to create a mutable reference ops that will serve as our counter.

  2. Instead of goroutines, we use PureScript’s parallel function to create concurrent operations.

  3. We create an array of 50 concurrent operations using replicate, where each operation increments the counter 1000 times.

  4. The Ref.modify_ function is used to atomically increment the counter.

  5. We use sequential $ sequence incrementers to run all the incrementers concurrently and wait for them to finish.

  6. Finally, we read the value of the counter using Ref.read and log it.

To run this program, you would typically compile it with the PureScript compiler and then run it with Node.js:

$ spago build
$ node -e "require('./output/Main').main()"
ops: 50000

We expect to get exactly 50,000 operations. The use of Ref ensures that our counter operations are atomic, preventing race conditions that could occur with non-atomic operations.

This approach provides a way to manage shared mutable state in PureScript, which is typically oriented towards immutable data and pure functions. However, it’s important to use these mutable references judiciously, as excessive use can make code harder to reason about and potentially introduce concurrency-related bugs.