A record-update function operator

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 persons into a list of names 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.

2 Likes

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