https://kotlinlang.org logo
Title
n

Nino

04/18/2023, 8:42 PM
I'm trying to find a way to better express the difference between 2 primitives (ie, latitude and longitude). Possibly have a compilation error if I would use a latitude for a longitude. So far, wrapping the primitives in data classes like so is "okay" but quite verbose :
data class Latitude(val value: Double)

fun doStuffWithLatitude(latitude: Latitude): Double = latitude.value + 1.0
I checked typealiases which are rather useless for type protection. For inline / value classes, I see no difference with my basic idea of a data class wrapping only one primitive. But value classes seem rather useless as a whole if you can't do something like :
@JvmInline
value class Latitude(private val d: Double)

fun doStuffWithLatitude(latitude: Latitude): Double = latitude + 1.0 // <-- Doesn't compile, wtf ?!
Particularily, I'm quite lost when the documentation mentions
Inline class instances can be represented at runtime either as wrappers or as the underlying type
... but it's not possible, as seen with example ☝️ I guess I'm missing something with inline / value classes that may help me. Or maybe there's another way to prevent mismatching 2 primitives of the same type ?
c

Chris Lee

04/18/2023, 8:45 PM
Value classes are the right solution here. For example:
@JvmInline
public value class Latitude(public val value : Double)
…allows Kotlin to have the type knowledge/safety, and inline this to the extent possible.
n

Nino

04/18/2023, 8:47 PM
You're allowed to have a public property in a value class ? The documentation doesn't even mention it wtf
b

Benoît Liessens

04/18/2023, 8:47 PM
Can custom operators be declared for value classes too?
c

Chris Lee

04/18/2023, 8:47 PM
I have explicit API mode on in my test project, it makes me explicitly specify the visibility.
Yes, custom operators can be declared:
@JvmInline
public value class Latitude(public val value : Double)

public operator fun Latitude.unaryMinus(): Latitude = Latitude(-value)
n

Nino

04/18/2023, 8:50 PM
Ok so value class is only about "performances" by inlining when possible, and nothing more ?
c

Chris Lee

04/18/2023, 8:51 PM
value class is about creating an almost-zero-cost type to provide type safety.
differs from type aliases that are just different names for the same types (and are interchangeable), generally used when you have conflicting imports. No added type safety there.
at all the usage points of the value class the compiler will inline the value where possible or use the wrapper. and, of course, validate the type safety.
n

Nino

04/18/2023, 8:53 PM
So basically there's no better usage than this ?
@JvmInline
value class Latitude(val value: Double)

fun doStuffWithLatitude(latitude: Latitude): Double = latitude.value + 1.0
c

Chris Lee

04/18/2023, 8:53 PM
you could override the plus operator to further simplify that
n

Nino

04/18/2023, 8:54 PM
I see ! In my usecase, I'd rather have to call a lib that I don't control and wants a double as latitude, so I would want the unboxed value there
c

Chris Lee

04/18/2023, 8:55 PM
Something like this, though wouldn’t use Double, rather create a Coordinate type (that you can then use in Lat / Long types).
public operator fun Latitude.plus(b : Double): Latitude = Latitude(value + b)
for those edge cases (talking to other APIs) you can reference the value directly e.g.
someCall(lat.value)
n

Nino

04/18/2023, 8:56 PM
That pesky
.value
lol
I have no idea why I'm so bothered about it haha
Why Kotlin designers didn't allow
someCall(lat)
, I have no idea
c

Chris Lee

04/18/2023, 8:57 PM
because that would be an implicit type conversion which opens the door to all manner of evil.
j

JasonB

04/18/2023, 8:57 PM
I suspect that is waiting on Project Valhalla where inline types will get official JVM support
c

Chris Lee

04/18/2023, 8:58 PM
yea, that will make them even-lighter-weight abstractions.
n

Nino

04/18/2023, 8:58 PM
I really don't understand this documentation's sentence then :
Inline class instances can be represented at runtime either as wrappers or as the underlying type
It's false isn't it ?
c

Chris Lee

04/18/2023, 8:59 PM
no, but it is confusing at first glance. as noted earlier the compiler will try and inline value types where possible; where not possible the wrapper will be used. but you don’t see any of that.
n

Nino

04/18/2023, 9:07 PM
Ok they say at runtime like "post compilation". You can't do something like that
val latitudeValue: Double = latitude
, but when code is compiled, it's basically a double so performance is preserved...
fun main() {
    val latitude = Latitude(Random.nextDouble(90.0))

    doStuff(latitude)
}

private fun doStuff(latitude: Latitude) {
    println(latitude.value)
}

@JvmInline
value class Latitude(val value: Double)
becomes
public final class MainKt {
   public static final void main() {
      double latitude = Latitude.constructor-impl(Random.Default.nextDouble(90.0));
      doStuff-yvxom7k(latitude);
   }

   private static final void doStuff_yvxom7k/* $FF was: doStuff-yvxom7k*/(double latitude) {
      System.out.println(latitude);
   }
}
c

Chris Lee

04/18/2023, 9:09 PM
yes, it’s inlined for explicit unboxed uses such as that one; some use cases (generics, interfaces, nullable) require boxing (wrapping). Details in docs.
n

Nino

04/18/2023, 9:10 PM
They never use
val myInt: Int = f.i
in this part (this is quite literraly the only "final usage" of value classes anyway), so I expected it to be impossible, even more so when the first example declares a private property like
value class Password(private val s: String)
c

Chris Lee

04/18/2023, 9:11 PM
it’s of course not possible when the val is private; when it’s public you can reference and use it as needed.
as in
public val _foo_ : Double = Latitude(0.5).value
n

Nino

04/18/2023, 9:11 PM
Why would you want a private property in a value class ?
c

Chris Lee

04/18/2023, 9:12 PM
you may rely solely on methods - overloaded operators or other domain-specific methods.
n

Nino

04/18/2023, 9:12 PM
I see, you can still expose the value from any public function inside the class itself
Crazy
c

Chris Lee

04/18/2023, 9:12 PM
instead of exposing the raw value you could have a
toExternalRepresentation()
as a level of indirection.
n

Nino

04/18/2023, 9:13 PM
I see
Thanks a lot for your time ! And I can't wait for whatever Project Valhalla to come to life so I can skip the
.value
on my value classes 😄
y

Youssef Shoaib [MOD]

04/19/2023, 8:14 AM
I'm quite sure that the holdup isn't project Valhalla, it's that the whole point of value classes is that they are separated from their underlying type. It allows you to create type-safe wrappers that aren't considered Ints or Doubles or anything like that. I think what you might be looking for is Restricted types, which are currently just a potential feature
n

Nino

04/19/2023, 8:20 AM
Feels weird to me to see
typealias
thrown there as they don't provide any compilation type safety ATM, but would if the design makes it... Or am I missing something ?
y

Youssef Shoaib [MOD]

04/19/2023, 8:28 AM
I see what you mean. I think it's more that it defines a typealias that is a restricted or refined version of a different type. You can imagine it as if Kotlin had "satisfies" as an operation you can apply on any type, and hence the typealias just provides a convenient shortcut for that. In a way, the
satisfies
operator is the key feature here, not the
typealias
part, but it's probably just more convenient to restrict
satisfies
to only appear in typealias declarations from a language design POV