Is there a better way to achieve this: ```enum cla...
# getting-started
v
Is there a better way to achieve this:
Copy code
enum class Boo(private val fooDelegate: String? = null) {
    BAZ,
    BAM(fooDelegate = "bar");

    val foo: String
        get() = fooDelegate ?: error("no foo set for ${javaClass.simpleName}.${name}")
}
I want that if
foo
is needed, it must have been set, but if it is not used, it is not necessary to set it. Optimally I like to access it by name
foo
and also set it by name
foo
instead of setting it by name
fooDelegate
or
optionalFoo
. When getting
foo
, I want it to be of type
String
though, not
String?
.
t
My first though is that a sealed class hierarchy might be more appropriate
v
But then you can have multiple instances of each class and that is not intended. It is an enum with properties, I just don't want to set the properties if they are not needed, but use them non-null. Or did I misunderstand what you suggest?
t
They can be implemented as
object
if you only want 1 instance
☝️ 2
v
Hm, but still this feels wrong, I also use
Boo.values()
to iterate over all of them and similar things. I would effectively need to recreate the enum functionality I use, wouldn't I?
b
if you have kotlin-reflect dependency, you can write
Copy code
companion object {
   fun values() = Boo::class.sealedSubclasses.mapNotNull { it.objectInstance }
}
v
Well, it still means reprogramming the enum functionality even if it is slim. 😕
Afair I also need the ordering that enums have naturally
So what you two suggest is basically this, right?
Copy code
sealed class Boo(val ordinal: Int) : Comparable<Boo> {
    val name: String = javaClass.simpleName

    open val foo: String get() = error("no foo set for ${javaClass.superclass.simpleName}.${name}")

    final override fun compareTo(other: Boo) = compareValues(ordinal, other.ordinal)

    object BAZ : Boo(0)

    object BAM : Boo(1) {
        override val foo: String = "bar"
    }

    companion object {
        fun values() = Boo::class.sealedSubclasses.mapNotNull { it.objectInstance }
    }
}
Ah stupid me, when writing the enum I forgot about overwriting properties that I now used in the sealed class. So what I was after is simply
Copy code
enum class Boo {
    BAZ,
    BAM {
        override val foo = "bar"
    };

    open val foo: String get() = error("no foo set for ${javaClass.simpleName}.${name}")
}
What would be the benefit of using a sealed class hierarchy instead @tddmonkey?
t
foo
would only exist in the hierarchy that needs it, so no need for runtime errors
v
That doesn't make much sense, does it? I'm using
foo
outside. So I have a method with parameter
Boo
and I want to get
foo
on the argument. If someone gives a
Boo
to the method that does not have a value defined for
foo
, this is a programming error.
t
it’s hard to give more help without seeing how you use it. From the example you give, if the calling code asks for
foo
from a value that doesn’t have it you get a runtime exception. My preference would be to switch that around so the type system doesn’t allow it
v
Hm, with
Boo
,
BooWithFoo : Boo
and then giving
BooWithFoo
to the class, I see, makes sense of course ... maybe. Thanks for that, I'll think about it.
But for the ordering I then still need to define the ordinals manually
t
yeah - but for something like that I always prefer it being explicit rather than being implicit in code ordering
v
Uh, now it gets ugly.
Copy code
sealed class Boo(val ordinal: Int) : Comparable<Boo> {
    val name: String = javaClass.simpleName
    final override fun compareTo(other: Boo) = compareValues(ordinal, other.ordinal)

    sealed class BooWithFoo(
            ordinal: Int,
            val foo: String
    ) : Boo(ordinal)

    object BAZ : Boo(0)
    object BAM : BooWithFoo(1, "bar")

    companion object {
        fun values() = Boo::class.sealedSubclasses.mapNotNull { it.objectInstance }
    }
}
does not work, BAM needs to be defined inside
BooWithFoo
and
values()
only finds
BAZ
as only direct subclasses are returned 😕
So now it is
Copy code
sealed class Boo(val ordinal: Int) : Comparable<Boo> {
    val name: String = javaClass.simpleName
    final override fun compareTo(other: Boo) = compareValues(ordinal, other.ordinal)

    sealed class BooWithFoo(
            ordinal: Int,
            val foo: String
    ) : Boo(ordinal) {
        object BAM : BooWithFoo(1, "bar")
    }

    object BAZ : Boo(0)

    companion object {
        fun values() = listOf(Boo::class, BooWithFoo::class)
                .flatMap { it.sealedSubclasses }
                .mapNotNull { it.objectInstance }
    }
}
😕
t
I would just put the
values()
in another object so its explicit that way, so something like:
Copy code
object Boos{
   val allBoosInOrder() = listOf(
       Foo
       Bar
   )
}
v
Of course I can throw more boilerplate at it, but Kotlin is meant to reduce boilerplate isn't it? 😄 Besides that, the
values()
is not my main concern, more the ripped apart constants.
Hm, maybe like this:
Copy code
sealed class Boo(val ordinal: Int) : Comparable<Boo> {
    val name: String = javaClass.simpleName
    final override fun compareTo(other: Boo) = compareValues(ordinal, other.ordinal)

    interface WithFoo {
        val foo: String
    }

    object BAZ : Boo(0)
    object BAM : Boo(1), WithFoo {
        override val foo = "bar"
    }

    companion object {
        fun values() = Boo::class.sealedSubclasses.mapNotNull { it.objectInstance }.sorted()
    }
}
But then I still need to receive
Boo
in the method and then check for
is WithFoo
as other classes could implement the
WithFoo
interface and I also need the other properties in the method.
Ah, where clause on generic function parameter and we are fine maybe
Copy code
sealed class Boo(val ordinal: Int) : Comparable<Boo> {
    val name: String = javaClass.simpleName
    final override fun compareTo(other: Boo) = compareValues(ordinal, other.ordinal)

    interface WithFoo {
        val foo: String
    }

    object BAZ : Boo(0)
    object BAM : Boo(1), WithFoo {
        override val foo = "bar"
    }

    companion object {
        fun values() = Boo::class.sealedSubclasses.mapNotNull { it.objectInstance }.sorted()
    }
}

fun <T> hoo(boo: T) where T : Boo, T : Boo.WithFoo {
    println(boo.name)
    println(boo.foo)
}
Ok, so I think this will be the final solution, thanks:
Copy code
sealed class Boo(val ordinal: Int) : Comparable<Boo> {
    val name: String = javaClass.simpleName

    init {
        if (!usedOrdinals.add(ordinal)) {
            throw IllegalArgumentException("${javaClass.superclass.simpleName}.$name: " +
                    "The ordinal $ordinal is already used by another instance")
        }
    }

    final override fun compareTo(other: Boo) = compareValues(ordinal, other.ordinal)

    object BAZ : Boo(0)
    object BAM : Boo(1), WithFoo {
        override val foo = "bar"
    }

    companion object {
        private val usedOrdinals = mutableSetOf<Int>()

        fun values() = Boo::class
                .sealedSubclasses
                .mapNotNull { it.objectInstance }
                .sorted()
    }

    interface WithFoo {
        val foo: String
    }
}

// verify ordinals are unique
private val booValues = Boo.values()

fun <T> hoo(boo: T) where T : Boo, T : Boo.WithFoo {
    println(boo.name)
    println(boo.foo)
}
Hm, no, giving the ordinals manually is tedious, especially when new constants get added in the middle or beginning. 😕
t
Do you need the actual ordinal, or is it just used for ordering?
v
I think only for ordering
t
so for that I would skip having each object know about its ordinal and the complication that comes from that and instead just store them in a list somewhere that maintains the ordering. This removes any problems with duplicate ordering and lists are designed to order things by ordinal
v
But it forces to have each object twice, once for declaring it, once for ordering it. Besides having two long lists this adds the risk of forgetting to adjust one place when editing the other. That's the nice thing with enums that are automatically ordered by declaration order. 😕
t
Perhaps I missed something - I don’t know why you’d have to have it twice
v
This is what I understood you suggested:
Copy code
sealed class Boo() : Comparable<Boo> {
    val name: String = javaClass.simpleName

    final override fun compareTo(other: Boo): Int {
        val indexOfThis = order.indexOf(this)
        if (indexOfThis == -1) {
            throw AssertionError("$name is missing in 'order' list")
        }
        val indexOfOther = order.indexOf(other)
        if (indexOfOther == -1) {
            throw AssertionError("${other.name} is missing in 'order' list")
        }
        return compareValues(indexOfThis, indexOfOther)
    }

    object BAZ : Boo()
    object BAM : Boo(), WithFoo {
        override val foo = "bar"
    }

    companion object {
        private val order = listOf(
                BAZ,
                BAM
        )
        
        fun values() = Boo::class
                .sealedSubclasses
                .mapNotNull { it.objectInstance }
                .sorted()
    }

    interface WithFoo {
        val foo: String
    }
}
So I need each twice and with a list of 60 values it is two long lists
b
I believe Boo::class.sealedSubclasses returns classes in the order from source code
v
But is that guaranteed or a coincidence? I didn't see that defined in the docs or
sealedSubclasses
t
I think you can collapse it down to just this:
Copy code
sealed class Boo() : Comparable<Boo> {
    val name: String = javaClass.simpleName
    object BAZ : Boo()
    object BAM : Boo(), WithFoo {
        override val foo = "bar"
    }

    companion object {
        private val order = listOf(
                BAZ,
                BAM
        )
        fun values() = order
    }
    interface WithFoo {
        val foo: String
    }
}
And then of course you could just remove
values()
and make
order
public
v
Yeah, of course I don't need the
sealedSubclasses
reflection anymore if I define the order explicitly. I don't see how I could remove the
compareTo
method override though.
And this wouldn't change that I have two lists with 60+ entries
t
ah, it doesn’t need to implement
Comparable
anymore - you did that to do the sorting in the old
values()
method.
Again - im not sure why you have 2 lists?
v
So it would be this:
Copy code
sealed class Boo() : Comparable<Boo> {
    val name: String = javaClass.simpleName

    final override fun compareTo(other: Boo): Int {
        val indexOfThis = values.indexOf(this)
        if (indexOfThis == -1) {
            throw AssertionError("$name is missing in 'order' list")
        }
        val indexOfOther = values.indexOf(other)
        if (indexOfOther == -1) {
            throw AssertionError("${other.name} is missing in 'order' list")
        }
        return compareValues(indexOfThis, indexOfOther)
    }

    object BAZ : Boo()
    object BAM : Boo(), WithFoo {
        override val foo = "bar"
    }

    companion object {
        val values by lazy {
            listOf(
                    BAZ,
                    BAM
            )
        }
    }

    interface WithFoo {
        val foo: String
    }
}
Which still has the problem of both lists
> ah, it doesn’t need to implement 
Comparable
 anymore Yes it does, or is not comparable > you did that to do the sorting in the old 
values()
 method. You are assuming too much, I made it comparable because I need it comparable, I just used it in
values
as the list should be sorted > Again - im not sure why you have 2 lists?
@bezrukov hm, do you have another idea on how to get the list of object instances?
kotlin-reflect
is there and I can get the
sealedSubclasses
. But unfortunatley there is a
SecurityManager
in place that denies
("java.lang.RuntimePermission" "accessDeclaredMembers")
, so the
it.objectInstance
fails with
AccessControlException
. :-(
Hm, I think I just switch back to
enum
. What I really would like to have is (https://youtrack.jetbrains.com/issue/KT-39466)
Copy code
enum class Boo {
    BAZ,
    BAM : WithFoo {
        override val foo = "bar"
    };
}

interface WithFoo {
    val foo: String
}
But I guess I'll go with
Copy code
enum class Boo {
    BAZ,
    BAM {
        override val foo = "bar"
    };

    open val foo: String
        get() = error("no foo set for ${javaClass.simpleName}.${name}")
}
for now, that at least removes the
fooDelegate
naming need
t
fair enough!
v
Or maybe
Copy code
enum class Boo {
    BAZ,
    BAM {
        val foo = "bar"
    };
}

fun foo(t: Boo) = "${t.name}: ${t.javaClass.kotlin.memberProperties.find { it.name == "foo" }?.get(t) ?: error("no foo")}"
if that works. But I think the other variant where I only have to do the erroring centrally and do not need reflection
Thanks for your help and patience though 🙂
t
No problem, just sad we couldn’t come up with anything better
v
At least I learned some new things and in the end at least have it a bit nicer and a hope for improvement if my issue gets considered in the language design 🙂
t
If you learned something, we can both walk away happy 🙂
👌 1