When expression with sealed classes, abstract and object subclasses


#1

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.")
    }

#2

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!


#3

@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()
}

#4

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.


#5

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


#6

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”.


#7

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


#8

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.


#9

Well, then. That explains it! Thanks!