When expression with sealed classes, abstract and object subclasses

I have the following sealed class hierarchy:

sealed class SC(open val connector: String) {

    abstract class NABC(override val connector: String) : SC(connector)
    abstract class ABC(override val connector: String) : SC(connector)

    object No : SC(" ")

    object And : ABC("&")
    object Or : ABC("|")

    object Iff : NABC("<=>")
    object Implies : NABC("=>")

    companion object {
        fun values() = SC::class.nestedClasses.map { it.objectInstance as SC }
        fun forString(connector: String) = values().firstOrNull { it.connector == connector }
    }

}

The following when expression requires an else but it seems reasonable that it should not:

    when (sc) {
        SC.No ->
            println()
        SC.Or ->
            println()
        SC.And ->
            println()
        SC.Iff ->
            println()
        SC.Implies ->
            println()
        else ->
            error("Should not be possible.")
    }

I’ve just tried your code and it works without else:

fun main(args: Array<String>) {
    show(SC.Implies)
}

fun show(sc: SC) {
     when (sc) {
        SC.No ->
            println()
        SC.Or ->
            println()
        SC.And ->
            println()
        SC.Iff ->
            println()
        SC.Implies ->
            println()
    }
}

sealed class SC(open val connector: String) {

    abstract class NABC(override val connector: String) : SC(connector)
    abstract class ABC(override val connector: String) : SC(connector)

    object No : SC(" ")

    object And : ABC("&")
    object Or : ABC("|")

    object Iff : NABC("<=>")
    object Implies : NABC("=>")

    companion object {
        fun values() = SC::class.nestedClasses.map { it.objectInstance as SC }
        fun forString(connector: String) = values().firstOrNull { it.connector == connector }
    }
}

Try it yourself!

@medium That’s because you are not using the “when” as an expression

The ‘when’ is missing:

is SC.NABC -> TODO()
is SC.ABC -> TODO()

You can however make NABC and ABC sealed classes too:

sealed class SC(open val connector: String) {

    sealed class NABC(override val connector: String) : SC(connector) {
        object Iff : NABC("<=>")
        object Implies : NABC("=>")
    }
    sealed class ABC(override val connector: String) : SC(connector){
        object And : ABC("&")
        object Or : ABC("|")
    }

    object No : SC(" ")

    companion object {
        fun values() = SC::class.nestedClasses.map { it.objectInstance as SC }
        fun forString(connector: String) = values().firstOrNull { it.connector == connector }
    }
}

and use it:

val x = when(sc) {
    SC.NABC.Iff -> TODO()
    SC.NABC.Implies -> TODO()
    SC.ABC.And -> TODO()
    SC.ABC.Or -> TODO()
    SC.No -> TODO()
}
2 Likes

I think the problem is that ABC and NABC classes are not sealed themselves.
I tried to mark them as sealed classes, but the compiler then doesn’t let me have inner objects extending them.
However, this works:

sealed class SC(open val connector: String)
sealed class NABC(override val connector: String) : SC(connector)
sealed class ABC(override val connector: String) : SC(connector)

object No : SC(" ")

object And : ABC("&")
object Or : ABC("|")

object Iff : NABC("<=>")
object Implies : NABC("=>")

fun test(sc: SC) = when (sc) {
    No ->
        println()
    Or ->
        println()
    And ->
        println()
    Iff ->
        println()
    Implies ->
        println()
}

So I believe it is a language/compiler limitation. Not sure if it’s intended or not.

1 Like

Making my abstract classes sealed is a fine solution. I do think it would be reasonable to use abstract here, instead, though.

The thing is, there could be classes outside the compiler’s knowledge (in a project that uses your code, for example) that inherit from NABC or ABC, which would break the when-expression as no branch would match them. The sealed keyword is made for exactly that purpose, to state “This class can’t be subclassed except for inside this file”.

Are you sure about that? They subclass a sealed class.

Yes, absolutely sure. It’s not generally forbidden to inherit from sealed classes, just direct inheritance is forbidden. You can easily try it, create a sealed class A and an abstract class B inheriting from A. Now create another file with a third class C. Trying to let that class inherit from A won’t work, but it will be fine inheriting from B.

Well, then. That explains it! Thanks!