Mark Karavan
Mark Karavan

Reputation: 2674

Dynamic records in Elm 0.18

The model in my Elm app has some nested records. I am currently setting them with normal immutable functions.

Types.elm

type alias Model =
    { settings : Settings
    ...
    }

type alias Settings = 
    { username : String
    , password : String
    ...
    }

App.elm

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        SetUsername username ->
            ( model |> setUserName username, Cmd.none )

        SetPassword password ->
            ( model |> setPassword password, Cmd.none )

Setters.elm

setUserName : String -> Model -> Model
setUserName username model =
    let
        oldUserSettings =
            model.userSettings

        newUserSettings =
            { oldUserSettings | username = username }
    in
        { model | userSettings = newUserSettings }


setPassword : String -> Model -> Model
setPassword password model =
    let
        oldUserSettings =
            model.userSettings

        newUserSettings =
            { oldUserSettings | password = password }
    in
        { model | userSettings = newUserSettings }

I'd like to generalize the Setters so that I can set them dynamically. Something like this:

setUserSettings : String -> String -> Model
setUserSettings field variable model =
    let
        oldUserSettings =
            model.userSettings

        newUserSettings =
            { oldUserSettings | field = variable }
    in
        { model | userSettings = newUserSettings }

setUserName : String -> Model -> Model
setUserName value model =
  setUserSettings username value

setPassword : String -> Model -> Model
setPassword value model =
  setUserSettings password value

What is the most elm-like way to do this?

Upvotes: 0

Views: 362

Answers (1)

Igor Drozdov
Igor Drozdov

Reputation: 15045

As already have been mentioned, there is no built-in syntax for setting a field of a record.

Due to this limitation, I think, your current code is fine. But if some more fields appear in the Settings, it may be useful to extract the logic of access to a nested field into a separate function:

setUserName : String -> Model -> Model
setUserName username model =
    setUserSettings model (\r -> { r | username = username })


setPassword : String -> Model -> Model
setPassword password model =
    setUserSettings model (\r -> { r | password = password })


setUserSettings : Model -> (Settings -> Settings) -> Model
setUserSettings model updateSettings =
    let
        oldUserSettings =
            model.userSettings

        newUserSettings =
            updateSettings oldUserSettings
    in
        { model | userSettings = newUserSettings }

In case a deeper nesting appears, you might find focus library useful. Here is an example of its usage.

In your current case, the library is going to modify your code to this:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        userSettings =
            create .userSettings (\f r -> { r | userSettings = f r.userSettings })

        username =
            create .username (\f r -> { r | username = f r.username })

        password =
            create .password (\f r -> { r | password = f r.password })

    in
        case msg of
            SetUsername value ->
                ( set (userSettings => username) value model, Cmd.none )

            SetPassword value ->
                ( set (userSettings => password) value model, Cmd.none )

But both solutions are not perfect since you need to define a custom setter for each field.

Upvotes: 2

Related Questions