TLDR; I come to no conclusions and simply imagine having a #field
record-update function-operator analogous to the .field
record-access function-operator.
I often find I have something like the following:
case Filters.parseSortBy sortString of
Nothing ->
filters
Just newSortBy ->
{ filters | sortBy = newSortBy }
If you’re not keen on the case, you might prefer to write this something like:
Filters.parseSortBy sortString
|> Maybe.map (\newSortBy -> { filters | sortBy = newSortBy })
|> Maybe.withDefault filters
Perhaps it is just me, but I find the lambda there a bit noisy. I would quite a record-update function-operator which is the equivalent of the record-access function operator. So where as:
.field
equates to (\r -> r.field)
I guess I would quite like:
#field
equating to (\r v -> { r | field = v })
.
With that, the above could be re-written as:
Filters.parseSortBy sortString
|> Maybe.map (#field filters)
|> Maybe.withDefault filters
Arguments Order
However, a major issue is what order to have the arguments. With List.map
and .field
we can turn a list of say person
s into a list of name
s with:
List.map .name persons
One could imagine that I have a list of records and want to set some field on all of them to the same value:
List.map (#field True) records
But that would require the opposite argument-order to the above case, ie. \v r -> { r | field = v}
.
One possibility would be that #field
means \r v -> { f | field = v}
and field#
means \v r -> { r | field = v }
. Hmmm.
The type of #field
I think the type of #field
would be: { b | field : a } -> a -> { b | field : a}
Nested Records
A well known awkwardness in Elm is the updating of nested records. So you might have something like the following in your update
function:
UsernameInput input ->
( { model | loginForm = { model.loginForm | username = input } }
, Cmd.none
)
Except that you cannot have model.loginForm
in that position, so you have to break out a let
:
UsernameInput input ->
let
loginForm =
model.loginForm
in
( { model | loginForm = { loginForm | username = input } }
, Cmd.none
)
I know that it is a matter of some debate as to whether or not it would be an improvement to allow model.loginForm
in that position in the grammar, and if so, what exactly should be allowed there. However, I think it’s relatively uncontroversial to suggest that the loginForm = model.loginForm
assignment is a bit noisy. Anyway I think using #field
could make this a little easier, such as:
UsernameInput input ->
( input
|> #username model.loginForm
|> #loginForm model
, Cmd.none
)
I personally tend to use a couple of helpers for tupling up the model and command so it would be:
UsernameInput input ->
input
|> #username model.loginForm
|> #loginForm model
|> Return.noCommand
Alternatively, if you don’t like |>
it could be written as:
UsernameInput input ->
( #loginForm model <| #username model.loginForm input
, Cmd.none
)
Multiple fields
Hmm, annoyingly this kind of requries the reverse argument order, so I’ll use field#
as per my suggestion above (even if I don’t like it):
loginForm
|> username# username
|> password# password
There is, of course, already a perfectly good way to update multiple fields but I’m thinking of this in the context of nested record fields above. The upshot as I see it here is that the two do not mesh especially well together. Since one kind of wants to use #field
and the other field#
.
Drawbacks
I could see people finding this too “syntaxy-magic”, or just plain difficult to read.
I could also see people immediately wanting:
#field
to mean \r f -> { r | field = f r.field }
Introduces more than one way to do the same thing.
Some may suggest that nested record update is deliberately awkward to nudge people into not using nested records and instead use extensible records on function types.
No conclusions
As I said I have no real conclusions and I’m not proposing this, I just thought it might make interesting discussion.