Is there a way to call the `values()` function on generic Enum type without too much code repetition?

I’m trying to implement a Command class. The idea is that a Command can have no additional arguments or some additional arguments, and I’d like the additional arguments to be Enums in order to be compile-time defined. Here is what I came up with:

interface Arguments {
    fun commandList() : List<Arguments>
}

enum class Args1 : Arguments {
    On, Off;
    
    override fun commandList() = Args1.values().asList()
}

enum class Args2 : Arguments {
    On, Off;
    
    override fun commandList() = Args2.values().asList()
}

sealed class Command(val code: Int) {
    sealed class WithArgs<T>(code: Int) : Command(code) where T: Enum<T>, T: Arguments {
        object CommandArgs1 : WithArgs<Args1>(1)
        object CommandArgs2 : WithArgs<Args2>(2)
    }
    
    sealed class NoArgs(code: Int) : Command(code) {
        object Command1 : NoArgs(1)
        object Command2 : NoArgs(2)
    }
}

This works quite well, but I find a bit annoying having to override the commandlList function for every enum class. But despite knowing perfectly well that every type used for the the command arguments are enums, which have the handy values() function, I cannot find a way to call it generically over T. I know about reified types, but since T is a type argument on a class, I’ve found no way to be able to use the reified keyword. I’ve found some people overloading the invoke method on the class, but I don’t seem to be able to do so on a sealed class. And I need the class to be sealed in order to take advantage of the compiler checks (it’s the whole point of this endeavour after all). Is there some less verbose approach to the situation?

Perhaps something like this might work for you?

interface Arguments {
    fun commandList(): List<Arguments>
}

inline fun <reified T> deriveFromEntries(): Arguments where T : Enum<T>, T : Arguments = object : Arguments {
    override fun commandList(): List<Arguments> = enumValues<T>().asList()
}

enum class Args1 : Arguments by deriveFromEntries<Args1>() { On, Off }

enum class Args2 : Arguments by deriveFromEntries<Args2>() { On, Off }

sealed class Command(val code: Int) {
    sealed class WithArgs<T>(code: Int) : Command(code) where T: Enum<T>, T: Arguments {
        object CommandArgs1 : WithArgs<Args1>(1)
        object CommandArgs2 : WithArgs<Args2>(2)
    }
    
    sealed class NoArgs(code: Int) : Command(code) {
        object Command1 : NoArgs(1)
        object Command2 : NoArgs(2)
    }
}
3 Likes

This works pretty well. I’m now facing a problem which is also present in my original code: I can’t call commandList on the type itself, it is present only on constructed enum variants because Kotlin needs an instantiate object in order to call the function.

I am not sure if there is a good way (currently) to express what your looking for while keeping the general structure of your code.

If one lets WithArgs implement Arguments, you could come up with something like this:

interface Arguments<T> {
    fun commandList(): List<T>
}

inline fun <reified T> deriveFromEntries(): Arguments<T> where T : Enum<T> = object : Arguments<T> {
    override fun commandList(): List<T> = enumValues<T>().asList()
}

enum class Args1 { On, Off }

enum class Args2 { On, Off }

sealed class Command(val code: Int) {
    sealed class WithArgs<T>(code: Int) : Command(code), Arguments<T> {
        object CommandArgs1 : WithArgs<Args1>(1), Arguments<Args1> by deriveFromEntries<Args1>()
        object CommandArgs2 : WithArgs<Args2>(2), Arguments<Args2> by deriveFromEntries<Args2>()
    }
    
    sealed class NoArgs(code: Int) : Command(code) {
        object Command1 : NoArgs(1)
        object Command2 : NoArgs(2)
    }
}

This would allow you to call commandList like this:

Command.WithArgs.CommandArgs1.commandList() // [On, Off] 

Alternatively you could try giving your enum classes a companion object which implements Arguments, though this is likely no longer less verbose than your original approach.

I’ve tinkered with your suggestions a bit. I’ve found out that having the type argument in the sealed class is quite annoying when using the when keyword. I’ve experimented a bit and here is the solution I’ve settled on:

sealed class Command(val code: Int) {
    sealed class NoArgs(code: Int) : Command(code) {
        object NoArgs1 : NoArgs(1)
        object NoArgs2 : NoArgs(2)
    }
    
    sealed class WithArgs(code: Int) : Command(code), WithArgsInterface {
        object WithArgs1 : WithArgs(3), WithArgsInterface by delegate<CommandArgs1>()
        object WithArgs2 : WithArgs(4), WithArgsInterface by delegate<CommandArgs2>()
    }
    
    private enum class CommandArgs1 : EnumArgs { On1, Off1 }
    private enum class CommandArgs2 : EnumArgs { On2, Off2 }
    
    interface EnumArgs
    
    interface WithArgsInterface {
        val args: List<EnumArgs>
    }
    
    companion object {
        inline fun <reified T> delegate() : WithArgsInterface where T: Enum<T>, T: EnumArgs {
            return object : WithArgsInterface {
                override val args: List<EnumArgs> = enumValues<T>().asList()
            }
        }
    }
}