bartek
07/05/2024, 2:11 PMsealed
types by adding copy
behaviour for when child types share fields and all have copy
behaviour already (e.g. are all data classes)?
sealed class Fruit {
abstract val pricePerKg: Float
data class Apple(override val pricePerKg: Float, val variety: String) : Fruit()
data class Banana(override val pricePerKg: Float) : Fruit()
}
val fruit: Fruit = ...
val aMoreExpensiveFruit = fruit.copy(pricePerKg = fruit.pricePerKg * 2)
a Fruit
-level copy
would be a shortcut for:
fun Fruit.copy(pricePerKg: Float = this.pricePerKg): Fruit =
when (this) {
is Apple -> this.copy(pricePerKg = pricePerKg)
is Banana -> this.copy(pricePerKg = pricePerKg)
}
Alejandro Serrano.Mena
07/05/2024, 2:25 PMcopy
). Maybe for sealed classes this could be more easily done...CLOVIS
07/05/2024, 3:27 PMsealed class Fruit(
val pricePerKg: Float
) {
data class Apple(override val pricePerKg: Float, val variety: String) : Fruit()
data class Banana(override val pricePerKg: Float) : Fruit()
}
although clear and sufficiently explicit as to which properties are "primary", is an anti-pattern because it leads to Apple
and Banana
both storing the price twice, because Fruit
has a private backing-field for pricePerKg
, and thus we cannot encourage this syntax, right?rocketraman
07/05/2024, 3:29 PMrocketraman
07/05/2024, 3:31 PMAlejandro Serrano.Mena
07/05/2024, 3:32 PMCLOVIS
07/05/2024, 3:35 PMsealed interface Fruit {
val pricePerKg: Float
contract {
copy(pricePerKg)
}
}
where contract.copy
takes a vararg fields: Any?
?
Not particularly beautiful, and probably doesn't fit the negative 100 points rule, but…rocketraman
07/05/2024, 3:37 PMabstract val
would be eligible for the sealed class copy?rocketraman
07/05/2024, 3:38 PMCLOVIS
07/05/2024, 3:39 PMabstract val
would be eligible for the sealed class copy?
Counter-example:
sealed class Fruit {
abstract val pricePerKg: Float
abstract val color: Color
}
data class Orange(
override val pricePerKg: Float
) : Fruit {
override val color: Color get() = Color.Orange // duh!
}
Here, color
cannot be an argument of Fruit.copy
.rocketraman
07/05/2024, 3:40 PMCLOVIS
07/05/2024, 3:40 PMcopy
is gone for the parent classrocketraman
07/05/2024, 3:42 PMrocketraman
07/05/2024, 3:42 PMrocketraman
07/05/2024, 3:44 PMCLOVIS
07/05/2024, 3:44 PMcolor
in my example), all usages of Fruit.copy
that use color break.
When exhaustive breaks, it's clear why, and it happens where you're checking for the types, so it's clear that you can add a new option. In this case, though, it would break completely normal code 😕CLOVIS
07/05/2024, 3:45 PMrocketraman
07/05/2024, 3:47 PMCLOVIS
07/05/2024, 3:48 PMrocketraman
07/05/2024, 3:49 PMCLOVIS
07/05/2024, 3:49 PMrocketraman
07/05/2024, 3:50 PMbartek
07/05/2024, 3:58 PMcopy
function cannot be created (e.g. due to potential ambiguity).
e.g. something like you mentioned @CLOVIS
sealed interface Fruit {
val pricePerKg: Float
contract { copy() }
}
if compiler (or a plugin) detected an unclear situation it would just error out on copy()
rocketraman
07/05/2024, 4:00 PMbartek
07/05/2024, 4:01 PMCLOVIS
07/05/2024, 4:02 PMCLOVIS
07/05/2024, 4:04 PMcopy()
already has a meaning in my version: it means "none of the fields are primary", whereas you use it for "all the fields are primary", which is incompatiblebartek
07/05/2024, 4:08 PMeygraber
07/05/2024, 7:50 PMhfhbd
07/10/2024, 6:28 PMhfhbd
07/10/2024, 6:28 PMhfhbd
07/10/2024, 6:34 PM@SealedDataClass
sealed class Fruit(val pricePerKg: Float)
data class Apple(override val pricePerKg: Float, val variety: String) : Fruit(pricePerKg)
data class Banana(override val pricePerKg: Float) : Fruit(pricePerKg)
Generates:
fun Fruit.copy(pricePerKg = this.pricePerKg): Fruit = when (this) {
is Apple -> copy(pricePerKg = pricePerKg)
is Banana -> copy(pricePerKg = pricePerKg)
}
Wout Werkman
07/10/2024, 7:54 PMdata class PricedFruit(val pricePerKg: Float, val fruit: Fruit)
sealed class Fruit
data class Apple(val variety: String): Fruit()
data object Banana: Fruit()
stantronic
07/19/2024, 8:59 AM