Should kotlin support nested deconstruction?


#1

In certain situations I end up wanting nested deconstruction like below (simplified example):

val ((x, y), z) = (1 to 2) to 3

This doesn’t work for me and I have to rewrite the code as such:

val (tmp, z) = (1 to 2) to 3
val (x, y) = tmp

Especially for lambdas nested deconstructing would improve the code:

f { ((x, y) , z) -> x + y + z }

Over:

f { ((tmp , z) -> 
  val (x,y) = tmp
  x + y + z }

#2

I think it is a bad idea.

  1. kotlin usese componentX methods for destructuring declaration. Doing something more complicated is imposible in current model. Chaging model could ruin a lot of other things in the language.
  2. I do not see any reason to do so. If you have a complex return type, then create a complex return type like
data class Tmp(val x: Double, val y: Double)
data class Result(val tmp: Tmp, val z: Double)

And now you can accept return value like this:

val res = Result(Tmp(x,y), z)

It is not much more complicated, but solves a lot of potential problems like type checks and IDE support.


#3

By the way, your last example could be rewritten as

f { (tmp, z) -> tmp.key + tmp.value + z }

or

f { (tmp, z) -> tmp.x + tmp.y + z }

if you use object with appropriate properties for tmp.


#4

Why is it impossible? It doesn’t break the grammar, just change

variableDeclarationEntry
  : SimpleName (":" type)?
  ;

to

variableDeclarationEntry
  : SimpleName (":" type)?
  : multipleVariableDeclarations
  ;

Seems like any argument against nested deconstruction would also apply to first-level deconstruction. Kotlin might only have allowed:

    f { result -> result.tmp.x + result.tmp.y + r.z }

As it is, deconstruction destructuring seems half-done.


#5

I did not said that it is impossible in principle, I said it is a bad idea.
I am not talking about grammar, but about backing entity. Say, you want to introduce a new class that could be used in destructuring declaration. For now you just have to implement componentN methods. Now how can one implement proposed construct? Probably it is possible to find componentN methods in resulting objects. But what if resulting object is generic? It requires a whole layer of compile-time type analysis.

It is of course my own opinion, but I think that one should not turn kotlin into scala by adding tons of language features. If one really need nested destructuring declarations, he can write a compiler plugin for that. In my experience, requirement for such structures either mean that you need to used dynamic language (like groovy), or it is just a bad design.


#6

Probably it is possible to find componentN methods in resulting objects. But what if resulting object is generic?

Then perhaps it can’t be done for generics. Kotlin can’t fix type erasure.

If one really need nested destructuring declarations, he can write a compiler plugin for that.

It’s not a need, its an opportunity for better clarity of expression:

data class Person(val id: Int, val name: String)
data class Employee(val person: Person, val store: String)

val f1: (Employee) -> String = { (person, store) -> "${person.name}, $store" }
val f2: (Employee) -> String = { ((_, name), store) -> "$name, $store" }

f2 is cleaner because it removes irrelevant symbols from the namespace.

I think that one should not turn kotlin into scala by adding tons of language features

Then let’s push to remove destructuring.


#7

Declaration destructuring is a compile-time feature and type erasure appears only in runtime, so it is probably could be done, but that does not mean it should be done.


#8

Cool. Thanks.

So what is your argument that Kotlin should destructure Employee but not Person in the example above?


#9

Because it is needless complication of the compiler? Could you show a use case where this type of destructuring is clearly needed? In the example you showed above, the f1 is in my opinion better than f2. It has better readability and allows for explicit type definition (and therefore check).

If you will say, that it is just more beautiful, then I will say that beauty itself is subjective and if you just try to invent beautiful constructs, sooner or later someone will propose to designate code blocks by spaces.


#10

Here’s simple example:

  for ((id, point) in points) {
    val (x, y) = point
  }

It would be better to write

  for ((id, (x, y)) in points) {
  }

I don’t really see any problems with this suggestion. Not the most important feature, of course, but it fits nicely with language.


#11

Because it is needless complication of the compiler

If you want to reduce compiler work, you should write .class files by hand.

In the example you showed above, the f1 is in my opinion better than f2.

The person object is not relevant to the function’s behavior so there is no need for it to be passed in.

and allows for explicit type definition (and therefore check).

The outermost type must already be supplied in Kotlin. But if you wanted to supply additional types, you could supply them for the compiler to double-check, similar to what it already does for 1st level destructuring: ((id: Int, name: String): Person, store: String): Employee.

If you will say, that it is just more beautiful

I did not invoke aesthetics. My reason for preferring f2 is practical: fewer irrelevant symbols means better readability.


#12

Sometimes it’s not as easy or not desirable to create new types.

For exampe I use a home-grown Result type that is essentially a more advanced Maybe type (haskell) in that it supports error messages in addition to a value or nothing.

My code looks like this:

protocol and serverName and serverPort then { p ->
    doSomething (p.first.first, p.first.second, p.second) 
  }

This combines the results and iff the aggregated result is good it will invoke the lambda.

In these cases I don’t find it compelling to create types to hold the nested tuple data. In addition p.first.first isn’t readable.

With nested deconstructing I could write:

protocol and serverName and serverPort then { ((protocol, serverName), serverPort) -> 
    doSomething (protocol, serverName, serverPort) 
  }

Which I would prefer.

Nested deconstruction has been part of ML-like languages for a long time so there is prior art. As a developer with some ML experience I find frustrating that nested deconstruction is not supported in kotlin.

I haven’t considered implementation detail as I think the first question that should answered is if nested deconstruction is something that should be supported.


#13

Looks like you’re trying to bring back tuples, which Kotlin intentionally jettisoned. I think a strong case could be made that destructuring is a better fit for tuple-like data, and so maybe both features really should have been removed together.


#14

The code doesn’t intend to create n-tuples instead and creates nested pairs (2-tuple). As we conveniently can create nested pairs I would like a convenient way to deconstruct them.

A “trick” to avoid nested tuples is to implement the Applicative functional pattern but that relies on partial functions which AFAIK kotlin doesn’t support.

What I can do in kotlin to fix the code above is creating a bunch of overloads for combinations of 1,2,3,4,5 … N results. (Like in C++ before variadic templates but unlike C++ we don’t have macros that simplify creating the overloads).

I agree that deconstruction as is today in kotlin only really makes good sense when dealing with tuples. If one supports nested tuples (like pairs) it make sense to me to support nested deconstruction of those.

With that said in ML like languages nested deconstruction is also used in pattern matching for union types and tuples and is very useful there.