Mutexes in Elm

In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a model similar to a mutex to safely access data across multiple threads.

import Dict exposing (Dict)
import Process
import Task exposing (Task)

type alias Container =
    { counters : Dict String Int
    }

type Msg
    = Increment String
    | GetCounters (Dict String Int -> Cmd Msg)

update : Msg -> Container -> ( Container, Cmd Msg )
update msg model =
    case msg of
        Increment name ->
            let
                updatedCounters =
                    Dict.update name (Maybe.map ((+) 1) >> Maybe.withDefault 1 >> Just) model.counters
            in
            ( { model | counters = updatedCounters }, Cmd.none )

        GetCounters callback ->
            ( model, callback model.counters )

inc : String -> Container -> Task Never Container
inc name container =
    Task.succeed (Increment name)
        |> Task.andThen (\msg -> Task.succeed (update msg container))
        |> Task.map Tuple.first

doIncrement : String -> Int -> Container -> Task Never Container
doIncrement name n container =
    List.foldl
        (\_ acc ->
            acc |> Task.andThen (inc name)
        )
        (Task.succeed container)
        (List.range 1 n)

main : Program () Container Msg
main =
    Platform.worker
        { init =
            ( { counters = Dict.fromList [ ( "a", 0 ), ( "b", 0 ) ] }
            , Cmd.batch
                [ doIncrement "a" 10000 { counters = Dict.empty }
                    |> Task.andThen (\_ -> doIncrement "a" 10000 { counters = Dict.empty })
                    |> Task.andThen (\_ -> doIncrement "b" 10000 { counters = Dict.empty })
                    |> Task.perform (\_ -> GetCounters (Dict.toList >> Debug.toString >> Debug.log "Counters" >> always Cmd.none))
                ]
            )
        , update = update
        , subscriptions = always Sub.none
        }

In this Elm program, we create a Container type that holds a dictionary of counters. The update function handles two types of messages: Increment to increase a counter and GetCounters to retrieve the current state.

The inc function increments a named counter, and doIncrement repeatedly calls inc in a loop.

In the main function, we set up the initial state and start three tasks that increment the counters concurrently. After all increments are done, we log the final state of the counters.

Note that Elm, being a functional language, handles concurrency differently from imperative languages. Instead of using mutex locks, we use the Elm Architecture and its built-in message passing to ensure safe concurrent updates to our state.

Running this program will output the final state of the counters, which should show that “a” was incremented 20000 times and “b” 10000 times.

This example demonstrates how to manage complex state in a concurrent environment using Elm’s approach to handling side effects and state updates.