Sealed Class

In the kind of application I’m doing, which involves trees and graphs, sealed class helps me a lot. I couldn’t give that up without my code getting messy. I use to make generic nodes that can assume many analogous but not equal types. It’s a kind of union type.

However, I have some small complaints.

a) I cannot (unless I use the clumsy relflection or make custom changes in any sealead class that I declare) scan all subclasses, for instance, when I use copy function. The new sealedSubclasses feature is not enough.

b) I cannot make a dummy initialization with some subclass, because I cannot reassign with other subclass. It’s not a big deal, and if one needs to initializate, one should declare sealed classes instances always using ?, in order to be initialized with null. It’s not cool, because it infringes the null safety feature in Kotlin. Normal usage of sealed classes instances is inside conditional blocks, so I need always make a generic initialization.

Normally Kotlin accepts assign common properties in a sealed class, however with nullable variables, it force to use “!!” operator in a normal assign. Null exception is possible here.

            var mySealed:MySealed?=null
            mySealed.commonProp = 25!!

A reasonable solution here is always to use lateinit in the variable declaration

I don’t understand either of the issues, could you provide some samples of what your code would look like if Kotlin could do what you want it to do?

2 Likes

Suppose

sealed class Person {
  abstract var name:String
 //....
}

data class Doctor( 
  var license:String
 //....
  override var name:String="",         
):Person()

data class Fireman( 
  var experience:Int
 //....
  override var name:String=""
):Person()

a)
Suppose that one needs a general swallow clone procedure

If I declare

   var p:Person
   p = Doctor(...)

In order to use the copy funcion (shallow clone), I need to make a complete when (it’s clumsy in case of 20 types of person)

   when (p) {
     is Doctor  ->   q = copy(p)
     ....
    is Fireman -> q = copy(p)
    }

If I try create an abstract function uCopy

  sealed class Person {
   ....
  abstract fun <T> uCopy
    }

   data class Doctor( .
     ...
   ):VarContent() {
     override fun <VarNorm>uCopy(p:VarNorm):VarNorm = p.copy()
    }

If gives an error in copy call (I don’t know why, because copy is a valid function to a data class type.

So no other way to do thus, unless I use reflexion or it demands my program a function like copy from scratch.

b) My idea was simple,

If one declares

   var p:Person
   p = Doctor(...)

I guess that if Doctor is a subclass from a sealed class maybe it can accept a reassignment, because p=Doctor(..) many times, it just a dummy initialization.

   p = Fireman(...)

The other option is use nullable, but I consider it unsafe

     var p:Person?
     p = Doctor(...)!!

However, on second thought , lateinit is a good option. So It’s ok for me.

  lateinit var p:Person()

  .....
  p=Doctor(...)

In your example, because the compiler has no idea that the generic type VarNorm represents a data class. This could be solved by Kotlin if it had a self type, but there are various reasons why adding a self type would be complicated and maybe not a good idea. Kotlin also doesn’t let data classes implement some interface which specifies a copy method (because the parameters would be dependent on the specific data classes properties).

What I sometimes whish for, is that data classes could override a parameterless copy. This wouldn’t create any conflic with parameters since copy has defaults for all of them. This is not usefull for immutable data classes but, great for mutable ones.

I have found a way to somewhat replicate this from a client perspective using an extension function that invokes the functionality. Basically you have something like:

  class BaseClass {
    internal fun _copy(): BaseClass = copyImpl()
    protected open fun copyImpl() = BaseClass(/*...*/)
  }

  fun <B:BaseClass> B.copy() = _copy() as B

This version is not type safe, you could use a reified version to make it type safe. The bouncing around is because protected is open outside, but is for extenders, and internal because it is not supposed to be used outside this file. Optionally @ExperimentalApi could be used to tighten it up even a bit more.