Generics & Enums


#1

I tried to port the following Java code to Kotlin and I didn’t succeed

(class names are changed)

interface Action<E extends Enum<? extends Action<E>>> {
   ...
}

enum Actions implements Action<Actions> {
    ...
}

<E extends Enum<? extends Action<E>>> void iterate(Class<E> actions) {
    ...
}

...
iterate(Actions.class);
...

Could someone help me to port such code to Kotlin?


#2

A simple interface Action<E : Enum<E>> will do the trick for your case with fun <E : Enum<out Action<E>>> iterate(actions: Class<E>)


#3

The thing is that <E extends Enum<? extends Action<E>>> enters an additional claim to the enum: it must implemet Action interface.

Look at the example

class Processor<E extends Enum<? extends Action<E>>> {
    public Processor(Class<E> actions) {
    }
}

Can I port it keeping the key constraint of type parameter?


#4

How would you use this additional claim? Can you also add a piece of code, please, that fails to compile with a simpler Processor<E extends Enum<E>>? I’m trying to understand what is the exact constraint on E that you are trying to satisfy.


#5

OK. Look at the following code (simplified case)

Declare the action descriptor

class Action<E extends Enum<? extends Action.Const<E>>> {

    final E constant;
    final Object extra;

    Action(E constant, Object extra) {
        this.constant = constant;
        this.extra = extra;
    }

    E getConstant() {
        return constant;
    }

    Object getExtra() {
        return extra;
    }

    interface Const<E extends Enum<? extends Action.Const<E>>> {
        Action<E> action();
    }
}

Then declare a class that operates on an action set

class API<E extends Enum<? extends Action.Const<E>>> {

    API(E actionSet) {
       ...
    }

    Action<E> actionOf(Object extra) {
       ...
    }
}

Declare some action sets

enum ActionSet1 implements Action.Const<ActionSet1> {

    func1("extra1"),
    func2("extra2"),

    ;

    final Action<ActionSet1> action;

    ActionSet1(Object extra) {
        this.action = new Action<>(this, extra);
    }

    @Override
    public Action<ActionSet1> action() {
        return action;
    }
}

enum ActionSet2 ...
enum ActionSet3 ...

And use them

val api = API(ActionSet1::class.java)
val action = api.actionOf(...)

val rep = when(action.getConstant()) {
    ...
}

But you cannot do this in Kotlin

class Processor<E : Enum<out Action.Const<E>>> (val api: API<E>)

#6

Thanks for details. However, it does not explain why you have to have these baroque type constraints in Java. Your Java code is type-safe and compiles properly with simpler type constraints in declarations:

class Action<E extends Enum<E>> {
    ...
   interface Const<E extends Enum<E>> {
        Action<E> action();
   }
}

class API<E extends Enum<E>> { ... }

(Note, that in your Java code API constructor takes parameter that is named actionSet but has the type E - an instance of a particular instance set, while in the subsequent code you try to pass a class of actions there. I’ve left it as is).

The above type constrains translate to Kotlin without problems (I’ve made it more idiomatic by converting Action.Const.action() to property):

class Action<E : Enum<E>>(val constant: E, val extra: Any) {
   interface Const<E : Enum<E>> {
        val action: Action<E>
    }
}

class API<E : Enum<E>>(actionSet: E) {
    fun actionOf(extra: Any): Action<E> {
        //...
    }
}

enum class ActionSet1 private constructor(extra: Any) : Action.Const<ActionSet1> {
    func1("extra1"),
    func2("extra2");

    override val action: Action<ActionSet1> = Action(this, extra)
}

and you can nicely use them from Kotlin:

    val api = API(ActionSet1.func1) // you can also replace API constructor will a version that takes class or set of values
    val action = api.actionOf(...)

    val rep = when (action.constant) {
       //...
    }

#7

Yes, this works. And I see several other WORKAROUNDS, but not solutions. Kotlin looks like a more stronger language than Java (null checks, mutable/unmutable collections and so on). But here you suggest that I should drop an existing check in Java code that obstruct porting it to Kotlin. The problem is that in your example you can pass an enumeration to the constructor of API calss even if that enumetation doesn’t implement the Action.Const interface. This error will be detected in runtime which makes debugging more expensive.

But what if we cannot port Action/API classes because they are part of a legacy library? Such Java classes are not fully supported by Kotlin. That is the problem!


#8

Kotlin is a pragmatic language. It does not have features just because other languages have them, but because there are compelling use-cases for those features. That is why I’ve asked for examples. The fact that this code is possible to write in Java is not compelling enough to substantiate its inclusion into Kotlin. Use-cases are needed. Kotlin had purposely removed a few Java features because they are either rarely used or hard to understand and use.

By the way, you can further constrain generic type E in Kotlin code in the way you’d like it to be constrained using where clause in the following way:

class API<E>(actionSet: E) where E : Enum<E>, E: Action.Const<E>

This would lead to a type signature that is easier to reason about than a nested constraint in your original Java code, while providing similar guarantees. You could have expressed this constrain in Java, too:

class API<E extends Enum<E> & Action.Const<E>>

#9

Thanks a lot!