Destructuring with named properties instead of componentN

val (name, _, city) = getUser() has a few problems:

  1. Unnecessary _ because I needed the first and third properties
  2. getUser returns a User, which is a data class. If I reorder or insert properties, “city” could now actually be the “street” field. I got burned many times on this if I forget to use intellijs refactoring (which won’t help if data class is in a separate library.

Instead of componentN functions, I would prefer something like ES6 Javascript Destructuring. For example, the above code could be:
val (:name, :city) = or val ({ name, city }) or possibly val { name, city }. Want to use a different variable name? val { name: userName, city }

This would help if you need just one property from a class and it’s not component1.

It would turn
val (_,_,_,result) = doThing()
into
val { result } = doThing()

No underscores, no risk of a variable getting reassigned to an unintended property after you rearrange your data class props. Lots of edge cases and semantics discussions to be had, but I would use this feature daily.

4 Likes

The link doesn’t make it entirely clear what’s going on there. From your example, it looks like it’s equating two unrelated things: the name of a property, and the name of a local variable (and then assigning the value of one to the other). Is that right?

If so, it seems overly restrictive (removing any choice about the local variable name), and almost as fragile as the positional notation you’re working around: if any of the properties get renamed, you’ll have an even bigger problem with refactoring.

And having to add further (unwieldy) syntax just to work around that suggests to me it’s not a good fit.

My experience with Kotlin suggests that destructuring isn’t used that often, and that it fits best in simple cases which have an obvious ordering (e.g. map keys and values, or string splits) and so aren’t likely to change. In other cases, maybe destructuring isn’t the best approach?

There was a previous topic of why positional based destructuring is dangerous but JetBrains Team members refused to acknowledge that there is a better method of destructuring. The previous discussion it seemed that the JetBrains Team got it in their head the the ask was to remove positional destructuring for named destructuring, however, both can exist without issue.

Name based destructuring would be an incredible add to the language. Arguments that were made by the JetBrains Team as to why positional destructuring is going to stay, but there were zero good arguments made as to why named destructuring shouldn’t be added/considered.

@gidds you argument about it seeming overly restrictive by removing any choice about the local variable name does not actually apply to ES as it allows you to remap the properties to new local names, and, in the cases where named destructuring is actually helpful, you are likely just using the property name as the local name anyway, so even if you weren’t able to choose the local name, it is a pretty minor issue. Then there is the “issue” that you bring up with renaming properties, which in my opinion isn’t an issue at all. Compared to positional destructuring, sure, it is more of an issue because renaming properties doesn’t break positional destructuring, but changing order of them breaks positional destructuring worse than renaming breaks named destructuring. Changing the name of a property that is being accessed via named destructuring can and should result in compiler errors (unless you rename or add another property to the same name), but also, because it can be determined explicitly which properties are tied to which destructured local variables, the IDE should be able to rename them all (or if you desire the local names to be kept the same, it can adjust the destructuring to account for that).

Now, as for the syntax, being able to provide both an explicit type and mapped name in a named destructuring declaration is perhaps clunky, but we already have import aliases using as, which could be the way that the name mapping is done while also still allowing for explicit type declarations.

val (_, _, _, result) = doThing() could become val { result : Result as localResult } = doThing() or val { result as localResult } doThing()

it may make more sense for the local name to go first and perhaps use by instead of as otherwise it could look more like a type cast which it isn’t.

at the very least, it could possibly be supported as a compiler extension.