Global constants/Reader pattern

I’m looking into Elm as a possible replacement for the frontend of the app I work on at my day job. It’s currently a messy pile of mostly-jQuery, with a lot of interpolation of values generated and written to the page by flask+jinja2. I understand that I can use init to pass these interpolated values into my Elm model, but I’m unsure how to go about handling them beyond that.

As a for instance, we write the user id to the page, and then pass it in a header on AJAX requests. This user id won’t–and shouldn’t–change based on user activity, but it’s used everywhere. Given that I have more experience in Haskell than in Elm, my thoughts immediately go to the Reader monad, which allows config data like this to be effectively passed around without worrying about it getting messed with.

I realize that I could just put the user id etc. into my model and just nobody accidentally messes with it, but ‘hope nobody makes a boo-boo’ seems contrary to the spirit of Elm and similar languages. Is there a way to encode the immutability of these data in a way that the compiler would fuss if someone wrote a function that replaced them?

I don’t think you can stop somebody with sufficient determination from changing them, but you could prevent accidental/forgetful changing of the data, by doing all you update work in a function that has access to the data but isn’t allowed to return a copy of it. Something like this (replacing flags with your actual flags data type)…


-- PROGRAM


main : Program flags Application Msg
main = 
    Browser.element 
        { init = initApp
        , view = viewApp
        , update = updateApp
        , subscriptions = subApp
        } 


type Application
    = Application Model ReadOnly


initApp : flags -> ( Application, Cmd Msg )
initApp flgs =
    let
        readOnly =
            readOnlyFromFlags flgs

        ( initialModel, initialCmd ) =
            init readOnly
    in
    ( Application initialModel readOnly, initialCmd )


updateApp : Msg -> Application -> ( Application, Cmd Msg )
updateApp msg app =
    case app of
        Application model readOnly ->
            let
                ( newModel, cmd ) =
                    update msg model readOnly
            in
            ( Application newModel readOnly, cmd )


viewApp : Application -> Html Msg
viewApp app =
    case app of
        Application model readOnly ->
            view model readOnly


subApp : Application -> Sub Msg
subApp app =
    case app of
        Application model readOnly ->
            subscriptions model readOnly



-- READ ONLY DATA


type alias ReadOnly =
    { -- STORE YOUR READONLY DATA HERE
    }


readOnlyFromFlags : flags -> ReadOnly
readOnlyFromFlags flgs =
    Debug.todo "Initialize your read only data here!"



-- MODEL


type alias Model =
    { -- STORE YOUR MAIN (MUTABLE) MODEL DATA HERE
    }


init : ReadOnly -> ( Model, Cmd Msg )
init readOnly =
    Debug.todo "Initialize your model here!"



-- UPDATE

type Msg
    = -- YOUR Msg TYPE HERE AS USUAL

update : Msg -> Model -> ReadOnly -> ( Model, Cmd Msg )
update : msg model readOnly =
    Debug.todo "Main update logic goes here, can access readOnly but can't return it (to change it)"



-- SUBSCRIPTIONS


subscriptions : Model -> ReadOnly -> Sub Msg
subscriptions model readOnly =
    Debug.todo "Your subscriptions here as usual."



-- VIEW


view : Model -> ReadOnly -> Html Msg
view model readOnly =
    Debug.todo "Your view here as usual."


Instead of creating subsidiary update etc. functions you could even create a custom Program instead to further hide the details from all but the most determined, but I feel that would be overkill!

1 Like

Alternatively you could also store read only data in an opaque type (you’d need to create a separate module to initialize it) and then force people to use functions from that module to access the data. However, depending on your use case that might be more or less cumbersome.

e.g.

module ReadOnly exposing (ReadOnly, data, initialize)


type alias Data =
    { --- READ ONLY DATA GOES HERE
    }


type ReadOnly =
    ReadOnly Data


initialize : flags -> ReadOnly
initialize flgs =
    let
        initData = 
            { -- Initialize your read-only data here
            }
    in
    ReadOnly initData


data : ReadOnly -> Data
data readOnly =
    case readOnly of
        ReadOnly innerData ->
            innerData

First, welcome to the Elm community. :slight_smile:

In Elm, all values are immutable. You have to explicitly return an updated value from update. That is the only place where a value can be updated in Elm.

If you have a bunch of data that you want passed around, you can use the shared-state pattern.

You can then discuss the code with your colleagues and take ownership of that module. (i.e. ask them to confirm with you if they want something changed in that module or just plain not touch it and just assign change requests to you)

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.