Deconstruction with * operator, similar to vararg

I often want to call functions with an object, but I need to specifiy the parameters explicitly:

class Circle(val centerX: Int, val centerY: Int, val radius: Float)

val point = IntOffset(100,200)

val circle = Circle(point.width, point.height, 70f)

but I’d like to avoid writing .width .height all over my code. I suggest to do this:

val circle = Circle(*point, 70f)

This is very similar to how * operator works for the vararg, and it seems natural to me.

What do you think?

I see a lot of problems with this approach, e.g. with named arguments. Or what is when the “spreaded” class changes, e.g. if a color value is added to the point class, or even worse, when width and height is being flipped? What if one argument of the “spreaded” class is a vararg? IMHO, that’s just too much confusion potential…

Honestly, just add a secondary constructor:

class Circle(val centerX: Int, val centerY: Int, val radius: Float) {
    constructor(point: IntOffset, radius: Float) : 
        this(point.width, point.height, radius)
}

Or write a helper method:

fun IntOffset.circle(radius: Float) = Circle(width, height, radius)

You can also avoid repeating point (maybe not useful here, but when used more often):

val circle= with(point) {
    Circle(width, height, 70f)
}

In conclusion, there are already many ways to deal with this kind of repetition.

4 Likes

Similar to Landei, I think this can’t be done non-confusingly with respect to other language features, unless you make it so restricted that it is only very rarely useful (e.g., you would require that there is exactly one field named like each named argument).

Furthermore, you can very simply do almost the same thing with a one-liner. In general, rather than a constructor to Circle, I’d just make a method invoking it :

fun Circle(offset : IntOffset, radius : Float) = Circle(offset.width, offset.height, radius)

…and you’d have the same caller syntax, save for one character.

If you want to do this with many classes, you could even do something like

interface Sizable {
val width : Int
val height : Int
}
fun Cicrle(pos : Sizable, radius : Float) = ...

…and then you can have any class with width and height implement the interface. This would be facilitated by structural typing but Kotlin doesn’t have that, so you’d have to add two overrides and the implementation clause to each class, but that’s not the end of the world, and if you have such a concept in Circle then it would probably be helpful elsewhere in your code.

I expect that this feature (or something very similar to it) will be added alongside dataarg, but there is no timeline for that yet.

1 Like