Record creation shortcut

I find myself typing thing’s like this all the time:

myString = "Hello"
myInt = 2
myRecord = { myString = myString, myInt = myInt }

I really miss the shortcut from Rust (and JS iirc) where, if your variable names match your field names, you can create a record like this:

myString = "Hello"
myInt = 2
myRecord = { myString, myInt }

I don’t like using the record constructors that are automatically created by an alias, as I always mix up arguments of the same type, which means I end up typing things like the first example quite often.

I imagine this has been discussed before, are there any downsides to this syntax that I’m missing?

2 Likes

In my experience, having one single way of assigning values makes code much easier to recheck in the future. you can do global search of assignments looking for myString = globally in your project. In javaScript there is no easy way to find all the places were you might be assigning a value to a variable.
This helps a lot to address potential problems you might have in your logic.

11 Likes

I’ve had the same experience as @francescortiz. It’s one of my favorite qualities of Elm that it has a very small syntax, and it has a minimal number of ways to express things.

I also find that using that shorthand syntax in JS often takes a little longer for my brain to process. And it’s harder to change if you ever want to change a variable name, so it creates some churn.

If it’s a question of saving some typing, then Elm often opts for more minimal and explicit syntax. That said, this could potentially be an elm-format feature. There are some cases like this (try leaving off the exposing line and see what elm-format does and you’ll see what I mean). So elm-format could potentially unfold expressions like this from { myString } -> { myString = myString }. I don’t see any downside to that (except some effort to code and maintain it). Could be a handy feature.

8 Likes

I also don’t like the fact that record constructors use positional parameters for the same reasons you stated.

What advantage do you think that

myString = "Hello"
myInt = 2
myRecord = { myString, myInt }

has over

myRecord = { myString = "Hello", myInt = 2 }

which is already possible in elm today?

This can grow very long though…

I totally agree with you, the small syntax is one of its big strengths, but I think that the record constructor shorthand is becoming quite familiar now, so is maybe a bit less jarring than it would have been a few years ago, and it isn’t a huge departure away from the existing syntax. Also, we could help this compared to JS by having a nice compiler message explains the shorthand, just in case people make an error and don’t understand what’s happened.

I think it has the advantage that it encourages people to avoid using record constructors, which, when I think about it, is the main reason I would like this syntax. Here’s a stupid example, when you have types like this:

type Msg
    = ChangedName String String String String

They’re difficult to read and it’s easy to introduce bugs. So the obvious place to go is this:

type alias NameUpdate =
    { firstName : String
    , lastName : String
    , previousFirstName : String
    , previousLastName : String
    }

type Msg
    = ChangedName NameUpdate

So this is kind of encouraged, because you even get the benefit of the Record constructor and it saves you some typing:

el [onClick <|
    ChangedName <|
        NameUpdate firstName lastName previousFirstName previousLastName 
    ]
    ()

The problem is that with the constructor you get bugs when you forget the correct order of the strings, and on top of that the type definition of Msg is less readable. By that being the easiest route, we’re kind of encouraging it, but I think this is much better:

type Msg
    = ChangedName
        { firstName : String
        , lastName : String
        , previousFirstName : String
        , previousLastName : String
        }

And if you can write this:

el [ onClick <|
     ChangedName <|
       {firstName, lastName, previousFirstName, previousLastName}
   ]
   ()

it even saves some typing over the constructor. So it ends up being compact, readable and robust, giving less reasons to use the record constructor (which should maybe be removed…). People, including myself, tend to follow the route of least resistance, so we could make the more robust way have a bit less resistance!

As a data point I would love this feature. I think it is great in js and also great in rust!

1 Like

PureScript has it, too.

1 Like

Well, isn’t that the case for any function call? You would need to fix that with named parameters for those cases too to be consistent.

But in my opinion the real problem is possibly too many arguments, or not clear why arguments are ordered this way. If you address that, suddenly you have much less need of named parameters.

2 Likes

In my experience in JS, this has a somewhat undesirable effect of having the names be more informative than they should/could.

Imagine I have something like this code here:

let
  sizeOfForest = SquareKm 3

  -- Loads of other properties

  typeOfTree = Elm
in
Forest.create
  { size = sizeOfForest
  , trees = Tree.build typeOfTree
  -- otherProperties...
  }

Now, with the availability of this syntax, people will (more) likely change the names of the variables to something more generic and less information, in order to be able to use this syntax.

let
  size = SquareKm 3

  -- Loads of other properties

  typeOfTree = Elm
in
Forest.create
  { size
  , trees = Tree.build typeOfTree
  -- otherProperties...
  }

I find that size is less informative now, especially if there are a lot of other names introduced in the same scope.
I’m not saying that this renaming always goes in the wrong direction. But I think that in general, the names will become less informative than before. (The names in my example are obviously debatable, as they generally are in most contexts)

I also agree with @dillonkearns that renaming things leads to more tedious work. If you rename the variable, then you need to replace {name} by {name = newName} everywhere, and if you rename the field name for the function/record, you will also need to add {newName = name} everywhere it was used (maybe also rename name in that context too, and so on).

Also, it then becomes quite annoying if you have several fields with the same name but that should not have the same value.

let
  typeOfTree = Elm
  sizeOfTrees = Meter 1
  size = SquareKm 3 -- should be sizeOfForest to be clearer
  trees =
    Tree.build
      { size = sizeOfTrees
      , typeOfTree
      }
in
Forest.create
  { size
  , trees
  }

I imagine people (as I have myself) thinking “Oh no, I couldn’t apply the shortcut to size for Tree.build. How can I refactor this to be able to?”. And I don’t think that kind of refactor would necessarily be helpful.

I have enjoyed my time using this shortcut in JS, and I still use it when it makes sense, but I do feel like it pulls me towards less informative code. I have to actively resist in order to make my code more readable.

10 Likes

Absolutely, I find myself doing this all the time, which is exactly what prompted me to post this, as it is a bit cleaner looking (IMO) and it’d save me a bit of typing.

Intuitively, I was assuming that it pulls you in the opposite direction. For example:

{ veryLongFieldNameForDemostrationPurposes = veryLongFieldNameForDemostrationPurposes
, anotherVeryLongFieldNameForDemostrationPurposes = anotherVeryLongFieldNameForDemostrationPurposes
}

The fact this code looks so unwieldy would be a good reason to avoid more descriptive field names, but this:

{ veryLongFieldNameForDemostrationPurposes
, anotherVeryLongFieldNameForDemostrationPurposes
}

Is a bit easier to parse. Of course all this is subjective!

@jfmengels I totally agree with this. In fact, I had a draft of a post explaining the same thing. So, @ChrisWellsWood, I guess that having uniform code is an advantage in the long term.

1 Like

@ChrisWellsWood You’re right that when the field and the name are the same, the shortcut version is more readable (to some, not everybody agrees, as they mentioned in this post).

The problem is more when the field represents something more general than what you have in the function.

For instance:

let
  userNames = List.map .name model.users
in
[ text <| "Present in chat room: " ++ String.join ", " userNames
, Dropdown.view { options = userNames, onSelect = UserSelectedOption }
]

vs

let
  options = List.map .name model.users
in
[ text <| "Present in chat room: " ++ String.join ", " options
, Dropdown.view { options, onSelect = UserSelectedOption }
]

In this case, I think the code is a bit more confusing/less informative in the latter version.
DropDown.view is quite general and options is a good name for the field. But in the context of the function, options is a bad name for the list of user names, as we are in a specific case where we know what the options refer to, and we use that field for other things too, making those usages more confusing.

From experience, I believe that you will be pushed towards this same latter version should the shortcut syntax be available.

Alternatively, you could do something like

let
  userNames = List.map .name model.users
  options = userNames
in
...

but I don’t think this is great.

3 Likes

You’re right about this, but adding the shortcut would not remove the expanded syntax so users could choose the most appropriate form.

I think that some people might be, but there’s nothing stopping them from picking uninformative variable names either! So in the end, we’d be in the same position we are now, where people need to think about what’s best to make their code readable, but at least we’d have an extra tool to do this.

@ChrisWellsWood Don’t think only of specific cases where it could be better. The strongest argument towards not having shortcuts is that it allows to have a uniform language where assignments can only done in one way, varName = value. If you have to review old code or code written by some else, having a uniform language helps a lot. Long lines are not comfortable to read, but having vague variable names spread all around the code sucks.

This can be annoying, but scope and purpose are clear.

{ veryLongFieldNameForDemostrationPurposes = veryLongFieldNameForDemostrationPurposes
, anotherVeryLongFieldNameForDemostrationPurposes = anotherVeryLongFieldNameForDemostrationPurposes
}

This could look nicer, but the line is meaningless:

Dropdown.view { options, selected }
```
1 Like

The repetition here give no additional information, but makes it harder to read. What if, for example, someone does something like this:

{ veryLongFieldNameForDemostrationPurposes = veryLongFieldNameForDemostrationPurposesAlt
, anotherVeryLongFieldNameForDemostrationPurposes = anotherVeryLongFieldNameForDemostrationPurposesAlt
}

You could be fooled easily into miss reading this if there are lots of similarly named variables, but this wouldn’t compile:

{ veryLongFieldNameForDemostrationPurposesAlt
, anotherVeryLongFieldNameForDemostrationPurposesAlt
}

So you know precisely what values are being used to construct the record.

I’m not sure why this is meaningless as this line will be in some context which will explain what options and selected are, but if the author is worried that people might be confused, or view the line in isolation, they could use the long form:

Dropdown.view { options=userList, selected=selectedUser }

This would give you even more information if the shortcut syntax is possible, as the author has opted to be explicit, so it might inform you that Dropdown.view is generic.

But that’s not true though right? We already have more ways to assign to a variable:

myFunc : Option -> { x : Float, y : float } -> Float
myFunc option { x, y } = -- assignment
    case option of
        MultiplyBy scalar -> -- assignment
            x * y * scalar
        Double ->
            let
                scalar = 2  -- assignment
            in
                x * y * 2

So I don’t think that’s a very compelling argument, especially as the record shortcut is logically consistent with record de-structuring.

Well, I’m pretty happy that it wouldn’t compile in this case.

Let’s say you have this shortcut syntax, and that you will rename a field. You start with:

displayUserName : { userName : String } -> Html msg

something : String -> Html msg
something name =
  let
    userName = String.toLowerCase name -- just a random operation really
  in
  displayUserName { userName }

and you then rename the userName field

displayUserName : { userName : String } -> String
-->
displayUserName : { name : String } -> String

Well if you don’t pay enough attention, or you do this refactor using a “replace text” operation in your editor, you will end up with

something : String -> Html msg
something name =
  let
    userName = String.toLowerCase name -- just a random operation really
  in
  displayUserName { name }

which unfortunately does compile! It was not as safe a refactor as you might have expected. And renaming things at the moment is one of the safest refactors you can have in Elm.

but if the author is worried that people might be confused, or view the line in isolation, they could use the long form

Yes, of course. My point is that you are nudged toward not having the best name possible. I think the language should nudge you towards good practices.

But that’s not true though right? We already have more ways to assign to a variable

Technically, two of those 3 would be destructuring, or “extracting” data. You don’t really assign/give an arbitrary value to a variable, even though destructuring is syntactic sugar for assignment (well, maybe not for pattern matching). So I think that calling that an assignment is debatable.

2 Likes

This is the most compelling argument that I’ve seen against it, but I suppose this could be ameliorated with good tooling like renaming with a language server etc. It also falls into the same class of bugs as accidentally using the wrong variable, which is probably the most common type of bug I introduce e.g using model when I meant to used updatedModel or something like that. I think this situation is quite rare, but it definitely would be an annoying bug if you came across it. Edit: This type of bug would get picked up by a linter in the example you gave, as a variable is defined but not used.

I totally agree with you, this suggestion comes out of a desire to nudge people to good practices too i.e using named arguments with functions. It’s hard to think of good names though, so I bet there’s plenty of examples of {options=options, selected=selected} out there anyway, even if they aren’t very information rich names. Both approaches require people to engage their brain!

A variable is being assigned somewhere! :smiley: I might be wrong, but I think that de-structuring is thought of as assignment in Python and Rust too, but this is definitely how it’s thought of in JS: Destructuring assignment - JavaScript | MDN

1 Like

I suppose this could be ameliorated with good tooling like renaming with a language server etc

Definitely! Those would probably introduce the non-shortcut version just to be safe (but I can tell you the tooling authors will be disappointed they have to do that :stuck_out_tongue: )

This type of bug would get picked up by a linter in the example you gave, as a variable is defined but not used.

Definitely, and I find this kind of tooling really useful for problems like this (and I therefore wrote those rules for elm-review). But if the variable was used in some other place(s), then you are out of luck.

I bet there’s plenty of examples of {options=options, selected=selected} out there anyway

Sure enough. It is a nudge, not a requirement, towards good naming. And sometimes those names are the ones that make the most sense.

A variable is being assigned somewhere! :smiley: I might be wrong, but I think that de-structuring is thought of as assignment in Python and Rust too, but this is definitely how it’s thought of in JS

Don’t confuse destructuring with the destructuring assignment:

// destructuring assignment: destructuring used in the context of assignment
const {a, b} = foo

// destructuring, but not destructuring assigment
const foo = ({a, b}) => a + b
1 Like