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?
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.
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.
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)
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)")
}
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}")
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.
© 2022 - 2024 — McMap. All rights reserved.
.first
and.second
to access each pair part. – Extenuatory