https://kotlinlang.org logo
#random
Title
s

Sam

06/01/2023, 10:59 AM
I’m looking for a nice way to express an enum-like set of values where there’s a set of well-known values, but arbitrary new values can also be created. Normally I’d use a value class, with companion constants for the well-known instances. But in this case, I also want each well-known instance to have an associated well-known name, which means I end up with a lot of redundancy. Any suggestions for how to improve on the code in the thread?
Copy code
@JvmInline
value class Status(private val code: Int) {
    private constructor(status: KnownStatus) : this(status.code)

    val name: String? get() = KnownStatus.values().find { it.code == code }?.name

    companion object {
        val Good = Status(KnownStatus.Good)
        val Bad = Status(KnownStatus.Bad)
        val Ugly = Status(KnownStatus.Ugly)
    }

    private enum class KnownStatus(val code: Int) {
        Good(1), Bad(2), Ugly(3)
    }
}
I considered having an enum that implements an interface, but then I lose the nice property of being able to guarantee that two statuses with the same code are always equal (and identical)
a

Adam S

06/01/2023, 11:25 AM
did you consider using a sealed interface? They are basically like enums with state.
☝️ 1
s

Sam

06/01/2023, 12:27 PM
I’ll give that some thought, thanks 👍. I think a sealed interface can give me some of what I want, but perhaps not all of it.
t

Tobias Suchalla

06/01/2023, 12:35 PM
Until we have value classes with more than one field, how about a data class?
Copy code
data class Status(
	val code: Int, 
	val name: String? = null
) {
	companion object {
		val Good = Status(1, "Good")
		val Bad = Status(2, "Bad")
		val Ugly = Status(3, "Ugly")
	}
}
Or if you want exhaustive
when
over the known values:
Copy code
sealed class Status(open val code: Int, val name: String?) {
    object Good : Status(1, "Good")
    object Bad : Status(2, "Bad")
    object Ugly : Status(2, "Ugly")
    data class Arbitrary(override val code: Int) : Status(code, null)
}
s

Sam

06/01/2023, 12:41 PM
Nice suggestions, thanks 👍. One thing that I would still like to achieve is to ensure that any instances that have a well-known code always compare equal to the original well-known instance. For example, I want
Status(1) == Status.Good
to always be true. I’d like to avoid a situation where I can have
Status(1, "foo") != Status.Good
🤔
I think I’m just over-engineering my code 😂
Maybe I just need a custom
equals
function. I’m never a fan of those, though 😬
t

Tobias Suchalla

06/01/2023, 1:15 PM
Unfortunately that doesn't work that well with sealed classes because you compare two different types... Unless you have a custom equals on the sealed class and cast the children:
(Status.Arbitrary(1) as Status) == (Status.Good as Status)
But it works perfectly with data classes: https://pl.kotl.in/7shbgVZN8
m

Matteo Mirk

07/04/2023, 10:13 AM
If you use a data class, the compiler generates a consistent equals/hashcode only for the properties declared in the constructor but not for those in the body, so you could try to exploit this feature to make
Status(1, "foo") == Status.Good
work ref: https://kotlinlang.org/docs/data-classes.html#properties-declared-in-the-class-body