https://kotlinlang.org logo
Title
m

Matt Nelson

04/30/2022, 11:30 AM
Heyo! I've got a KMP project an am using value classes, but cannot for the life of me figure out how to require their use in Java from an interface method.
sealed interface Key {
    fun descriptor(address: Address): String
}

@JvmInline
value class Address(@JvmField val value: String)
Calling From Java:
public class SomeClass {
    public void someFunction(Key key) {
        // Key.descriptor accepts a String, not an Address
        String descriptor = key.descriptor("");

        Address address = new Address("someaddress");

        // key.descriptor does not accept an Address and IDE shows error
        String descriptor2 = key.descriptor(address);
    }
}
Any Ideas?
g

George

04/30/2022, 3:47 PM
Hello, probably this is what you are looking for https://kotlinlang.org/docs/inline-classes.html#calling-from-java-code
m

mike.holler

05/02/2022, 2:59 PM
In Java, you would call
SomeClass().someFunction("someaddress")
-- value types are not enforced by the JVM
m

Matt Nelson

05/02/2022, 9:21 PM
With some ingenuity, I was able to remove value classes from my public APIs using sealed interfaces and operator function
sealed interface Port {
    val value: Int

    companion object {
        @JvmStatic
        @Throws(IllegalArgumentException::class)
        operator fun invoke(port: Int): Port {
            return RealPort(port)
        }
    }
}

@JvmInline
private value class RealPort(override val value: Int): Port {
    init {
        require(value in 0..65535) {
            "Invalid port range"
        }
    }
}
And calling from Java:
Port port = Port.invoke(65536); // Will throw IllegalArgumentException
This way, all my interfaces that take a
Port
now compile to the sealed interface, instead of the primitive type.
Ty George and Michael for your responses!
:kotlin-intensifies: 1
m

mike.holler

05/02/2022, 9:23 PM
@Matt Nelson interesting approach. Worth noting that this has two hits against it, in addition to the obvious positives: 1) far less efficient 2) difficult to read
At this point, why not do it like this:
data class Port(val value: Int)
?
There will still be a boxing/unboxing on the JVM side for all of this, so doing
data class Port(...)
would have the same affect I'd think.
m

Matt Nelson

05/02/2022, 9:26 PM
Yeah, I'm still playing with things to get it right. Performance wise, data classes are very bad. See my test results HERE
m

mike.holler

05/02/2022, 9:26 PM
What application are you writing that is using so many port numbers you have to worry about inefficiency?
m

Matt Nelson

05/02/2022, 9:26 PM
It's a wrapper for Tor
m

mike.holler

05/02/2022, 9:27 PM
You know, that's just about the one valid answer you could have given to that question right?
m

Matt Nelson

05/02/2022, 9:27 PM
lol, yes.
m

mike.holler

05/02/2022, 9:27 PM
I still think it'd be fully wrapped on the java side, and maybe on the kotlin side too. It might lose the perceived benefit. You should check out the bytecode.
Perhaps throw an example together that calls it from the java side and measure the time?
m

Matt Nelson

05/02/2022, 9:32 PM
Decompiled byte code shows it's boxed.
m

mike.holler

05/02/2022, 9:33 PM
What are your memory constraints? Caching every instance is only 0.5 MB
m

Matt Nelson

05/02/2022, 9:34 PM
As minimal as possible, as running Tor on Android is a hog.
m

mike.holler

05/02/2022, 9:35 PM
It does bear asking the question -- why not just use ints straight up? Maybe create a
typealias
if you're feeling curious.
I'm all for kotlinic code but here it feels like a good place to optimize
m

Matt Nelson

05/02/2022, 9:41 PM
For sure, but Tor is very picky about the strings that it receives via it's control port. Using value classes is a very low cost way to offload knowledge library consumers have to have when interacting with the APIs (how many people know that a v3 onion address is a 56 character base32 encoded lowercase string?) by handling all of that and returning a wrapper, it can be passed around everywhere straight from the controller response all the way down to the DB. Very low friction on library consumer side as the APIs "guide" things before even making it to the request being sent to the controller, so.
m

mike.holler

05/06/2022, 10:24 PM
@Matt Nelson my friend, I am forever grateful to you. I have used this to create a
WrappedLong
implementation that is compatible with JS but performs very quickly on JVM. You are a genius.
🔥 1
m

Matt Nelson

01/11/2023, 2:01 PM
Created a library for this specific issue of Kotlin
value class
not compiling to platform code. https://github.com/05nelsonm/component-value-clazz