Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get rid of this boilerplate code in this sealed class hierarchy?

Suppose I've got a sealed class hierarchy like that:

sealed class A {
    abstract val x: Int
    abstract fun copyX(x1: Int): A
}

data class A1(override val x: Int, val s1: String) : A() {
    override fun copyX(x1: Int): A {
        return this.copy(x = x1)
    }
}

data class A2(override val x: Int, val s2: String) : A() {
    override fun copyX(x1: Int): A {
        return this.copy(x = x1)
    }
}

All the data classes have field x and should provide method copyX(x1: Int) to copy all the fields but x and override x with x1. For instance,

fun foo(a: A): A { a.copyX(100) }

The definitions above probably work but the repeating copyX across all the data classes seem very clumsy. How would you suggest get rid of this repeated copyX ?

like image 396
Michael Avatar asked Aug 27 '19 08:08

Michael


People also ask

Can we inherit sealed class in Kotlin?

2.1. One of the advantages of sealed interfaces over sealed classes is the ability to inherit from multiple sealed interfaces. This is impossible for sealed classes because of the lack of multiple inheritance in Kotlin.

What is a sealed class and give an example where to use them?

The main purpose of a sealed class is to take away the inheritance feature from the class users so they cannot derive a class from it. One of the best usage of sealed classes is when you have a class with static members. For example, the Pens and Brushes classes of the System.

Can sealed class extend another class?

This means the heir of the sealed class can have as many as any number of instances and can store states, but the enum class cannot. Sealed classes also offer a restricted number of hierarchies. This means if you have a different class defined in another file in your project, you cannot extend the class Menu .

How do you instantiate a sealed class?

Sealed classes cannot be instantiated directly. Sealed classes cannot have public constructors (The constructors are private by default). Sealed classes can have subclasses, but they must either be in the same file or nested inside of the sealed class declaration.


1 Answers

First, you can implement copyX as an extension (or even A's member) so as to concentrate the code in one place and avoid at least duplicating the copyX function in the sealed class subtypes:

sealed class A {
    abstract val x: Int
}

fun A.copyX(x1: Int): A = when (this) {
    is A1 -> copy(x = x1)
    is A2 -> copy(x = x1) 
}

data class A1(override val x: Int, val s1: String) : A()

data class A2(override val x: Int, val s2: String) : A()

If you have a lot of sealed subtypes and all of them are data classes or have a copy function, you could also copy them generically with reflection. For that, you would need to get the primaryConstructor or the function named copy from the KClass, then fill the arguments for the call, finding the x parameter by name and putting the x1 value for it, and putting the values obtained from component1(), component2() etc. calls or leaving the default values for the other parameters. It would look like this:

fun A.copyX(x1: Int): A {
    val copyFunction = this::class.memberFunctions.single { it.name == "copy" }
    val args = mapOf(
        copyFunction.instanceParameter!! to this,
        copyFunction.parameters.single { it.name == "x" } to x1
    )
    return copyFunction.callBy(args) as A
}

This works because callBy allows omitting the optional arguments.

Note that it requires a dependency on kotlin-reflect and works only with Kotlin/JVM. Also, reflection has some performance overhead, so it's not suitable for performance-critical code. You could optimize this by using the Java reflection (this::class.java, getMethod(...)) instead (which would be more verbose) and caching the reflection entities.

like image 62
hotkey Avatar answered Sep 29 '22 17:09

hotkey