Enum limitations


#1

I’m wondering about a couple of enum limitations I encountered and whether they can be fixed in the future :slight_smile:

1. It is impossible to access functions/properties which are particular to an enum object:

enum class X {
    A {
        fun a() {}
    },
    B,
    C;
}

// This does not compile, function is inaccessible
X.A.a()

In this case, a() is specific to A and I would only like to access it through X.A

2. It is impossible to declare a variable whose type is enum object:

// X defined as above

interface I {
    val x: X
}

class Y: I {
    // This does not compile
    override val x: X.A = X.A
}

In this case, I want x to be specifically object A for class Y

Both limitations go away if I use interface + objects for each literal (vs. enum), but in that case I lack exhaustiveness in when, and that carries risks of its own.

I believe also that in a case of a sealed class (in other words, many objects vs a single object) no such limitations exist, so there is no “feature parity” between the two…

Thoughts?


#2

Well yeah, that’s the point. They are 2 different concepts with different limitations. Both of your examples can be explained with one simple fact. Each value of a enum is of the same type, therefor they all have the same methods and properties. Also you can not declare a value of type X.A as that would be the same as saying I want this argument to have the type 5. For what you are describing you want to use sealed classes as you pointed out.


#3

You may consider a solution like this one to address the first one of your points

enum class X { 
    A {
        override fun doSomething() {
            println("Doing something cool");
        }   
    },
    B {
        override fun doSomething() {
            println("Doing something else");
        }   
    }, 
    C;

    open fun doSomething() {};
}

fun main(args : Array<String>) {
    X.A.doSomething();
    X.B.doSomething();
    X.C.doSomething();
}

#4

Indeed I was able to solve this with sealed classes:

sealed class X {}

object A: X() {
  fun a() {}
}

object B: X() 

object C: X()

interface I {
  val x: X
}

class Y: I {
  override val x: A = A
}

This has the best of both worlds (fits my model and exhaustiveness). My faith in Kotlin is restored! :slight_smile:

@Wasabi375 There is no such thing saying that 5 can’t be a type… e.g. in my example, A is both a value and a type. Unit is another example. This is only a limitation of the type system


#5

I think you are confusing 3 different concepts: Type, Class and Object. In your enum A is an object and if I understand the way the jvm handles it also a class but A does not generate it’s own type. The type of A when you use it is still X. That’s why you can’t access members specific to A only outside of A and also why you can’t have a field of type X.A.


I sadly don’t have any links to the way enums work internally. So that’s just my own understanding after years using Java and now Kotlin.


#6

@Wasabi375 Check my solution with sealed class. A is an object, but it is used as a type too (in class Y). What you’re talking about are internals of the JVM which, while imposing a limitation on Kotlin, are not a definite authority over what can be and can’t be a type. 5 can be used as a type in the languages with very flexible type systems e.g. TypeScript


#7

I was talking about enums. I know that you can do all this with sealed classes. I just tried to explain, why you can’t using enums.


#8

Got it, thanks! It was confusing cause it is possible to add a function, and it is public, but it’s still inaccessible from the outside… without knowing the internals it is a reasonable expectation to me for the function to be callable


#9

I guess it is. I never thought about it as I don’t usually use enums often with different functions. I like to use sealed classes once they get more complicated than storing a few variables.