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
andUpdateWriteOps
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.