https://kotlinlang.org logo
Title
a

Adrián

05/27/2019, 1:40 PM
Hi, I noticed a common problem when modeling using Sealed classes and Data classes. In my projects, this approach is too common and we noticed a boilerplate problem when we need to modify the content using the copy method of data classes. Currently, Kotlin does not provide a way to restrict the Sealed classes implementations to Data classes, so if you want to use copy method, you need to check for all concrete data classes and it is a pain in the ass. Imagine the next scenario:
sealed class MyModel {
        abstract val name: String
        abstract val title: String
}
data class Model1(
            override val name: String,
            override val title: String,
) : MyModel()
...
data class Model100(
            override val name: String,
            override val title: String,
) : MyModel()
If you want to modify the title, for example, adding the environment where you are running your code could be similar to:
fun MyModel.addEnvironmentToTitle(env: String) = when (this) {
	is MyModel1 -> copy(title = title + env)
	... -> // Same boilerplate here
       is MyModel100 -> copy(title = title + env)
}
It is a simple example but you could have more complex code and it becomes unmanageable. Does anyone know if we will have in the future an option to restrict Sealed classes implementation to Data classes? With that restriction previous method would be:
fun MyModel.addEnvironmentToTitle(env: String) = copy(title = title + env)
d

Dico

05/27/2019, 2:03 PM
Wouldn't this only be possible if all subclasses also have the exact same properties? Just put your
title
as a property on the common class. That's what inheritance is for.
s

streetsofboston

05/27/2019, 2:07 PM
@Dico Alas, no.. MyModel is not a data class and therefore the compiler doesn't know about the auto-generared
copy
method.
Something like a 'data interface' would help in this use-case :-)
l

Lawik

05/27/2019, 2:21 PM
If you don't mind using reflection you could do something like this
inline fun <reified T: MyModel> T.addEnvironmentToTitle(env: String): T =  when {
    T::class.isData -> T::class.memberFunctions.first{ it.name == "copy"}.call(this, this.title, this.title + env) as T
    else -> this
}
a

Adrián

05/27/2019, 2:31 PM
+1 @streetsofboston It would be nice
@Lawik Would be nice does not require reflection for copy a object 😛. Thanks for your suggestion but I do not consider reflection as a option in prod.
@Dico The problem is with copy method as @streetsofboston said
s

streetsofboston

05/27/2019, 2:35 PM
And possibly the
componentXXX
methods as well.
s

stevecstian

05/27/2019, 4:18 PM
We can't have
copy
in
MyModel
due to https://kotlinlang.org/docs/reference/compatibility-guide-13.html#data-class-overriding-copy , but we can have a
copy
-like abstract fun in
MyModel
.
a

Adrián

05/27/2019, 5:52 PM
Yes, that was my approach but you still need to implement it in all implementations...
r

Ruckus

05/28/2019, 2:45 PM
It's important to note that data classes do not generate a no-args
copy
function. The generated function will have the same signature as the constructor, it just conveniently defines defaults for you. As a result, it makes no sense to require classes to be
data
as there is no common code among data classes.