Is it possible to have stacked value classes, whil...
# getting-started
z
Is it possible to have stacked value classes, while keeping the ability to use the primitive type?
Copy code
value class NonEmptyString(private val raw: String) {
    init {
        require(raw.isNotEmpty())
    }
}

value class Digits(private val raw: NonEmptyString) {
    init {
        require(raw.all(Char::isDigit))
    }
}

val nes = NonEmptyString("asdf")
val digits = Digits("123")// doesn't work, but I want it to
val nesDigits = Digits(NonEmptyString("123"))// works, but feels clunky
e
Copy code
value class Digits(private val raw: NonEmptyString) {
    constructor(raw: String) : this(NonEmptyString(raw))
❤️ 1
z
Damn that's sleek, and it feels super obvious too. Actually lol'd. Want a different constructor? Make a different constructor.
I tried it out, but it seems like it confuses the JVM Platform...
e: file///F/github/Zymus/ideas/libraries/vcard/vcard-model/src/commonMain/kotlin/games/studiohummingbird/vcard/primitives/Digits.kt167 Platform declaration clash: The following declarations have the same JVM signature (constructor-impl(Ljava/lang/String;)Ljava/lang/String;):
fun `constructor-impl`(raw: NonEmptyString): Digits defined in games.studiohummingbird.vcard.primitives.Digits
fun `constructor-impl`(s: String): Digits defined in games.studiohummingbird.vcard.primitives.Digits
e
oh no that seems like a bug, one should be name mangled…
you could always make a constructor-like free function,
Copy code
value class Digits(private val raw: NonEmptyString)
fun Digits(raw: String): Digits = Digits(NonEmptyString(raw))
z
Here's the code for mvc, I'll try that function next
Copy code
package games.studiohummingbird.vcard.primitives

import arrow.core.Predicate
import kotlinx.serialization.Serializable

/**
 * Not all Unicode Digits, just ASCII
 */
private val DIGIT_RANGE = '0'..'9'

fun NonEmptyString.allDigits() = all(DIGIT_RANGE::contains)

fun NonEmptyString.digits() = Digits(this)

@Serializable
@JvmInline
value class NonEmptyString(private val raw: String)
{
    init {
        require(raw.isNotEmpty()) { "must not be empty" }
    }

    fun all(match: Predicate<Char>): Boolean = raw.all(match)
}

@Serializable
@JvmInline
value class Digits(private val raw: NonEmptyString)
{
    constructor(s: String) : this(NonEmptyString(s))

    init {
        require(raw.allDigits()) { "must contain only digit" }
    }
}
Yeah, function route compiled and the tests pass now.
This is in a multiplatform project, so maybe a subtlety between JVM and MP/JVM?
e
seems like a compiler bug or design flaw to me. not sure if there's a solution but I think it would make sense to open a youtrack issue
z
Yeah, I think I'll do that next. When having both constructor and function, the compiler complains that there's a conflicting signature (being eachother) so some portion of it definitely knows its the same, and different. Thanks again for the help!
Looks like the recommendation there was an
operator fun invoke
on the
companion object
a
the official Kotlin style guide allows 'factory functions', as an alternative to constructors. I like that IntelliJ doesn't complain about the function name starting with a capital letter, so long as the name matches the name of the returned object
❤️ 1
💯 1
🔥 1