Stateful Goroutines in Elm

import Html exposing (Html, div, text)
import Process
import Task
import Random
import Dict exposing (Dict)
import Time

type Msg
    = ReadOp Int (Int -> Msg)
    | WriteOp Int Int (Bool -> Msg)
    | UpdateReadOps Int
    | UpdateWriteOps Int
    | Tick

type alias Model =
    { state : Dict Int Int
    , readOps : Int
    , writeOps : Int
    }

init : () -> (Model, Cmd Msg)
init _ =
    ( { state = Dict.empty
      , readOps = 0
      , writeOps = 0
      }
    , Cmd.batch
        [ startReaders 100
        , startWriters 10
        , Process.sleep 1000 |> Task.perform (\_ -> Tick)
        ]
    )

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        ReadOp key respond ->
            ( model
            , Task.perform respond (Task.succeed (Dict.get key model.state |> Maybe.withDefault 0))
            )

        WriteOp key value respond ->
            ( { model | state = Dict.insert key value model.state }
            , Task.perform respond (Task.succeed True)
            )

        UpdateReadOps count ->
            ( { model | readOps = model.readOps + count }, Cmd.none )

        UpdateWriteOps count ->
            ( { model | writeOps = model.writeOps + count }, Cmd.none )

        Tick ->
            ( model, Cmd.none )

view : Model -> Html Msg
view model =
    div []
        [ text ("readOps: " ++ String.fromInt model.readOps)
        , text ("\nwriteOps: " ++ String.fromInt model.writeOps)
        ]

startReaders : Int -> Cmd Msg
startReaders count =
    List.range 1 count
        |> List.map (\_ -> startReader)
        |> Cmd.batch

startReader : Cmd Msg
startReader =
    Random.generate (\key -> ReadOp key (\_ -> UpdateReadOps 1)) (Random.int 0 4)

startWriters : Int -> Cmd Msg
startWriters count =
    List.range 1 count
        |> List.map (\_ -> startWriter)
        |> Cmd.batch

startWriter : Cmd Msg
startWriter =
    Random.map2 (\key value -> WriteOp key value (\_ -> UpdateWriteOps 1))
        (Random.int 0 4)
        (Random.int 0 99)

main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Time.every 1 (\_ -> Tick)
        }

In this example, we’re using Elm to create a stateful application that simulates concurrent reads and writes to a shared state. Elm doesn’t have built-in concurrency primitives like goroutines, so we’re using Elm’s architecture and commands to simulate concurrent operations.

The Model contains our state (a Dict), and counters for read and write operations.

We define message types for read and write operations, as well as for updating the operation counters.

The init function sets up our initial state and starts our “readers” and “writers”.

In the update function:

  • For ReadOp, we retrieve the value from the state and respond.
  • For WriteOp, we update the state and respond.
  • UpdateReadOps and UpdateWriteOps increment our operation counters.

The view function simply displays our operation counts.

We use Random.generate to simulate random reads and writes, and Process.sleep to add a delay between operations.

To run this program, you would typically compile it with the Elm compiler and then open the resulting HTML file in a browser. The program will run for about a second, performing reads and writes, and then display the total number of operations performed.

This Elm version provides a similar functionality to the original Go program, but it’s important to note that Elm’s approach to concurrency is fundamentally different from Go’s. Elm uses a single-threaded model with asynchronous commands, while Go uses goroutines and channels for true concurrency.