I'm trying to find a way to better express the dif...
# getting-started
n
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 :
Copy code
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 :
Copy code
@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
Value classes are the right solution here. For example:
Copy code
@JvmInline
public value class Latitude(public val value : Double)
…allows Kotlin to have the type knowledge/safety, and inline this to the extent possible.
👍 1
n
You're allowed to have a public property in a value class ? The documentation doesn't even mention it wtf
b
Can custom operators be declared for value classes too?
c
I have explicit API mode on in my test project, it makes me explicitly specify the visibility.
Yes, custom operators can be declared:
Copy code
@JvmInline
public value class Latitude(public val value : Double)

public operator fun Latitude.unaryMinus(): Latitude = Latitude(-value)
👍 2
n
Ok so value class is only about "performances" by inlining when possible, and nothing more ?
c
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
So basically there's no better usage than this ?
Copy code
@JvmInline
value class Latitude(val value: Double)

fun doStuffWithLatitude(latitude: Latitude): Double = latitude.value + 1.0
c
you could override the plus operator to further simplify that
n
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
Something like this, though wouldn’t use Double, rather create a Coordinate type (that you can then use in Lat / Long types).
Copy code
public operator fun Latitude.plus(b : Double): Latitude = Latitude(value + b)
👍 1
for those edge cases (talking to other APIs) you can reference the value directly e.g.
someCall(lat.value)
n
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
because that would be an implicit type conversion which opens the door to all manner of evil.
1
j
I suspect that is waiting on Project Valhalla where inline types will get official JVM support
c
yea, that will make them even-lighter-weight abstractions.
n
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
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
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...
Copy code
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
Copy code
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
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
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
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
Why would you want a private property in a value class ?
c
you may rely solely on methods - overloaded operators or other domain-specific methods.
n
I see, you can still expose the value from any public function inside the class itself
Crazy
c
instead of exposing the raw value you could have a
toExternalRepresentation()
as a level of indirection.
n
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 😄
👍 1
y
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
👀 1
n
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
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