Destructuring Declarations with getter and setter


#1

While I am coding the wrapper for Fuel I have to write in definition of the class:

private var _roundTrip = lazy {
    toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
}

val request: Request
    get() = _roundTrip.value.first

val response: Response
    get() = _roundTrip.value.second

val result: Result<out Any, out Exception>
    get() = _roundTrip.value.third

The problem here is that the lazy declaration returns the Triple, which I would like to store as 3 separate properties.

My suggestion to support follow syntax:

val (request, response, result) = lazy {
    toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
}

The first call to any of the properties would execute the lazy closure and assign values to all 3 properties.

FYI The call to one of String.response*(...) functions return Triple<Request, Response, Result<T, FuelError>>


#2

Firstly, you have unorthodox use of lazy. lazy is usually used as delegate, not as function.

Your use case seems to be very narrow and suggested syntax is definitely not possible since it overlaps with “normal” destructuring declaration. For example, this code:

val (request, response, result) = lazy {
    toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
}.value

will produce valid result.


#3

Your example isn’t lazy though. You could just replace lazy {...}.value with run in that case.


#4

I know. It is just a demonstration of the fact, that syntax is already occupied.


#5

I think what he meant to put for the code is:

val (request, response, result) by lazy {
    toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
}

For the purpose of the suggested change, here’s a simpler case:

val (request, response, result) = toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())

I think the suggestion should be shown with an even simpler code example to avoid confusion:

class Foo {
    val (x, y, z) = listOf(1, 2, 3)
}

#6

The code you wrote already works like charm in kotlin. At least if you replace listOf by Triple.

The question was about magically replacing lazy initialization by syntetic properties with getters instead of lazies.


#7

Currently, it only works for local variables. I thought @cabman was suggesting to support member variables for destructuring declarations.

Here’s the error:

class Foo {
    val (x, y, z) = listOf(1, 2, 3)
}

#8

Indeed, I did not know about that. Since I come from Java background, I almost never use destructuring declarations.


#9

Note that in cabman’s case, simple class level destructuring declarations is not enough–delegates would also have to support destructuring.


#10

It’s an interesting idea. I wonder how this would work with read/write delegates. Probably this would only be allowed for read only delegates but it’s worth thinking about.
I can see how this feature could be useful but I’m not sure how often it would be used. I use destructing declarations rarely and I have not used delegates often. I wonder how often the combination of those 2 features would actually be used.


#11

Thanks everyone.

I might be using not correct syntax, but my problem is that the lazy code I hope will be used once for each instance of the class. It is a single call which returns 3 values and I don’t want to call it for each value separately. Also I cannot call it at the time of instantiation of the instance hanse lazy.

I guess my original code should look like:

private val _roundTrip by lazy {
    toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
}

val request: Request
    get() = _roundTrip.first

val response: Response
    get() = _roundTrip.second

val result: Result<out Any, out Exception>
    get() = _roundTrip.third

//    val (request, response, result) by lazy {
//                toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
//    }

The commented code shows I would like to use instead.

Now I explained my reasons (and corrected code) does it make more sense?


#12

One of the confusion I guess raised from the fact that I try to use destructuring declaration in class definition. This is why I mentioned properties in destructuring and need to use lazy delegation. Apparently desctructuring declaration only allowed for local variables/values. I added a bit in the original text to emphasize that fact.

Actually I have another example (just to show it might be not as narrow as @darksnake think).

The Result in Fuel I am using is actually a class with component1() and component2() functions, so it can be used with destructuring declaration. However because the value is not available at construction time I need to use laze {...} delegation here too.

How should I destruct the value of type Triple<Request, Response, Result<T, FuelError>> into 4 values of types Request, Response, T and FuelError?

May be support “deep” destruction like:

val (request, response, (body, exception)) by lazy {
    toRequest.responseObject(jacksonDeserializerOf<Map<String, Any?>>())
}

It may be 2 separate features. Seams conversion compiler should do is not very difficult.

It may be not only for lazy delegation, but also for getters and setters (as I mentioned in the title). If we need to do the same work while changing any of few properties. Instead of copying the same code or write a separate function to be called from each property getter/setter we could use destructuring declaration with single getter (and if needed setter) to be called in all cases.

For example:

class Fraction {
    private var _value
    val value: Long
        get() = _value
   
    var (nominator, denominator)
        get() = Pair(nominator, denominator)
        set() {
            _value = nominator / denominator
        }
}

The question is what type to use to return from getter. The setter would take any type which can be used in regular destructuring declaration.


#13

I find myself using destructuring declarations and delegation more and more.
Separating the idea’s, I get these:

1. Destructuring property declarations
class Foo {
  val (x, y) = Pair(1, 2) // Class level properties with destructuring
}
2. Delegation to destructured variables
fun main(args: Array<String>) {
  val (a, b) by lazy { Pair(1, 2) } // Delegating with destructure
}
3. Nesting destructuring
// (I'm not a fan)

#1 and #2 are the most interesting to me. Still, I’m not sure there are no good alternatives to these cases. IMHO, in order to justify an addition like #1 or #2, there should be no good alternative.

Here’s the simplified workaround by @cabman. The downside here is that one has to save the lazy delegate (or alternatively, the raw things) to a private variable and must add custom getters to each property:

fun threeThings() = Triple(1, 2, 3)

class Foo {
    private val things by lazy { threeThings() }
    val x: Int get() = things.first
    val y: Int get() = things.second
    val z: Int get() = things.third
}

@cabman, As far as getters and setters for #1, I’m not sure I like the idea. But I do think it’s worth considering #1, getters and setters come after.


#14

@arocnies, Thank you for formulating my points.

Your simplified workaround is the same as my code, just written in more compact format (which may be not always possible).

I am not sure that the design of Kotlin only includes features for which “there are no good alternatives”. It actually in most cases just hiding the boilerplate code into what someone may call “syntax sugar”. This definitely would be useful one requiring single line of code instead of 4 (in this real life example) and logically connecting properties together for better readability.

About #3 I am sure myself. Just added it in case people find it useful.