Reputation: 2674
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
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