Dick Lucas
Dick Lucas

Reputation: 12639

How to name components of a Pair

Given the Pair val coordinates = Pair(2, 3), is it possible to name each value so I can do something like coordinates.x to return 2? Or is coordinates.first the only way to access that first value?

Upvotes: 29

Views: 22348

Answers (6)

Willi Mentzel
Willi Mentzel

Reputation: 29874

The Kotlin documetation says:

The standard library provides Pair and Triple. In most cases, though, named data classes are a better design choice, because they make the code more readable by providing meaningful names for properties.

Since Pair is final you can unfortunately not inherit from it, but I would suggest to create a dedicated class Point:

data class Point<T>(val x: T, val y: T)

to represent a point. This way wherever you use it you are always forced to access it using the meaningful names.

Use it:

val myPoint = Point(1.0, 2.0)
println("This is the horizontal coordinate: ${myPoint.x}")

Upvotes: 5

j2emanue
j2emanue

Reputation: 62549

another thing i enjoy doing here to help code readability, is to use typealias (or more recently inline classes but still experimental at this time)

so you could do this:

typealias Radius = Double?
typealias Area= Float

then do: Pair<Area,Radius> so its much more readable, but for access description i would do data class.

you could take it one step further:

typealias  DimensionsPair = Pair<Area,Radius>

note that i think inline class here would bring better type safety.

Upvotes: 4

hotkey
hotkey

Reputation: 148119

Another solution is to define an object with meaningful extensions for Pair<A, B> and to bring these extensions into the context using with(...) { ... }.

object PointPairs {
    val <X> Pair<X, *>.x get() = first
    val <Y> Pair<*, Y>.y get() = second
}

And the usage:

val point = Pair(2, 3)

with(PointPairs) {
    println(point.x)
}

This allows you to have several sets of extensions for the same type and to use each where appropriate.

Upvotes: 13

s1m0nw1
s1m0nw1

Reputation: 82027

I want to propose an alternative, which should be applied if the renaming is desired only in a certain scope.

You can create a simple extension higher-order function enabling the naming in a scope, passed as a lambda-argument:

fun <F, S> Pair<F, S>.named(block: (F, S) -> Unit) = block(first, second)

Now, a Pair can be called with a lambda, which is invoked with its components that are name-able on caller-site:

val pair = Pair(2, 3)
pair.named { x, y ->
    println("my pair: ($x,$y)")
}

It's even possible to apply the invoke convention:

operator fun<F, S> Pair<F, S>.invoke(block: (F, S) -> Unit) = block(first, second)

Now, the Pair can be invoked directly:

val pair = Pair(2, 3)
pair { x, y ->
    println("my pair: ($x,$y)")
}

Upvotes: 9

msrd0
msrd0

Reputation: 8390

The definition of Pair in the Kotlin stdlib is as follows:

public data class Pair<out A, out B>(
    public val first: A,
    public val second: B
) : Serializable

So, if you have an instance p of type Pair, you can access the first property only as p.first. However, you can use a Destructuring Declaration like this:

val (x, y) = p

This is made possible because Pair is a data class. Every argument of the primary constructor of a data class gets a componentX() method, where X is the index of the parameter. So the above call can be expressed like this:

val x = p.component1()
val y = p.component2()

This makes it absolutely independent from the actual name of the parameter.

If you, however, want to be able to write p.x, you'll have to go ahead and create your own data class (or use a library):

data class Point(val x : Double, val y : Double)

Upvotes: 11

dniHze
dniHze

Reputation: 2232

This is not supported. You should write a wrapper (data) class for that purposes or you could use Kotlin destructuring declarations:

val (x, y) = coordinates
println("$x;$y")

See more here.

Upvotes: 31

Related Questions