Question on combining sealed classes with data cla...
# announcements
d
Question on combining sealed classes with data classes, in thread.
Copy code
sealed class Animal {
    abstract val weight: Int
}

data class Dog(
    override val weight: Int,
    private val barkSound: String
) : Animal()

data class Cat(
    override val weight: Int,
    private val meowSound: String
) : Animal()


fun main() {
    val dog : Animal = Dog(weight = 1, barkSound = "woof")
    
    // what's the most elegant way to allow the following?
    val heavierDog = dog.copy(weight = weight + 1) 
}
so far this is the cleanest way i can find. if there's something better, please let me know:
Copy code
sealed class Animal {
    abstract val weight: Int
    abstract fun copy(
        weight: Int = this.weight
    ): Animal
}

data class Dog(
    override val weight: Int,
    private val barkSound: String
) : Animal() {
    override fun copy(weight: Int): Animal {
        return copy(weight = weight, barkSound = this.barkSound)
    }
}

data class Cat(
    override val weight: Int,
    private val meowSound: String
) : Animal() {
    override fun copy(weight: Int): Animal {
        return copy(weight = weight, meowSound = this.meowSound)
    }
}


fun main() {
    val dog : Animal = Dog(weight = 1, barkSound = "woof")
    val heavierDog = dog.copy(weight = 2)
    println(heavierDog)
}
k
I think that's about the best you're going to get.
d
thanks. in converting from my simple example to my real code i discovered another hiccup. If a subtype of the sealed class doesn't add any additional properties, it results in a compilation issue due to the generated copy function being a duplicate of the sealed class. e.g.
Copy code
sealed class Animal {
    abstract val weight: Int
    abstract fun copy(
        weight: Int = this.weight
    ): Animal
}

data class BasicAnimal(
    override val weight: Int
) : Animal()
k
Name it
clone
simple smile
d
results in
Function 'copy' generated for the data class has default values for parameters, and conflicts with member of supertype 'Animal'
so far the best i can think of for that situation is to add in an unused/ignored property. vestigial animal tail 🙂
k
As you've noticed data classes and inheritance don't play that well together. I think the
copy
function for data classes was a mistake.
d
😕 yeah they don't. thanks for the tips
s
This is likely overkill, but the ‘Lensing’ of functional programming could help 🙂 : https://arrow-kt.io/docs/optics/lens/#composition Not sure if the lensing module of Arrow works for non-data classes, though….
👍 1
g
Why copy is a mistake? Do you have better solution? Because it would be very verbose to use immutable data classes
k
Couple of things: • This might just be me, but I very rarely use them, I can't actually remember the last time and I searched my recently opened projects, nothing. • Clashes with other functions named
copy
• "Private data class constructor is exposed via the generated
copy
method", this basically makes private data class constructors useless.
g
1. Than you never modify them, which of course possible in some use cases, but when you need it it's becoming huge problem, especially for classis with a lot of properties, and I just wouldn't use data classes if there wouldn't be
copy
and keep using AutoValue or similar solution 2. Not really a big problem, imo, but would be nice to fix 3. I agree that it is unfortunate, would be nice to have a way to make copy private or disable it
k
Interesting, I'll be on the lookout for a copy use-case in the future. Most of my data classes are really small, 2-3 properties so maybe that's why. I'd still be happy with a way to disable them though.
I guess I meant to say that copy being there by default is weird,
hashcode
and
equals
make sense,
component
a bit less and
copy
even less. Some modularity would be nice but then that's more syntax. It's a balance simple smile
g
I wouldn't say it's weird, it's just has another use case in mind, that it's pure data so it doesn't expose implementation details etc
d
It's a class to hold data, it makes perfect sense to me that the data can be copied. What gives? I don't hse it that often but it's definitely convenient at times
k
Especially the private constructor thing is annoying, the copy function just breaks it and there's nothing you can do about it.
g
which is also considered by many as incorrect usage of pure data class (to have data validation on constuctor)
k
Maybe, but my common use case is that I just want
equals
and
hashcode
to be generated.
d
Does it not make sense for a copy function to exist for a class that has equals and hash code generated based on its properties?
k
No, sometimes I want equals and hashcode but not a public constructor.
d
That's fair
Would you be happy with an annotation that disables the generation of the copy function?
k
Yes that would be perfect.
d
Or maybe it can be disabled for data classes with a private constructor, but that may be problematic
k
That would be a breaking change, there may be code out there that actually uses copy to work around a private constructor.
But in hindsight that would have been a good idea.
e
copy
is perfect for
data class
as a data class is something you should not extend. Don’t worry to duplicate properties in two `data class`es as this is just where DRY is not applicable
1