I was pointed out that it's not a good pattern to ...
# announcements
a
I was pointed out that it's not a good pattern to use
copy()
constructor of a data class as a kind of builder, essentially calling it multiple times to re-create an object adding some data inside in a pattern like:
Copy code
data class TrackingEvent(
        val eventType: String = "",
        val eventFeature: String = "",
        val eventVariant: String = ""
)

fun TrackingEvent.click() = copy(eventType = "click")

fun TrackingEvent.onArticleFromStage(stage: String): TrackingEvent {
    return copy(
            eventFeature = "article_stage",
            eventVariant = "click on article from stage $stage"
    )
}

TrackingEvent().click().onArticleFromStage("stage")
On one hand I can understand why creating new object would result in GC cleaning up the old one. On the other hand, we have an immutable class and I think I read somewhere about compiler optimizations on this, not creating a new pointer a memory for properties and rather reusing old pointers? Am I wrong? Should I bring back builder back from Java days instead if I want similar functionality?
r
2 different use cases here: 1. If you're using the builder pattern to create an object in a single place, then just using a constructor or dedicated factory functions with default and named arguments should be enough, not creating intermediate objects all over the place while still keeping readability. 2. If you want to pass around the object because multiple different places are involved in creating the object, then it's a matter of individual judgement - Can you refactor the code to actually create the code in just one place? If not, just having one or two calls of
copy
for a creation might be fine regarding your individual performance requirements. But if you're in a tight loop or need lots of different places where "mutation" happens, then you should probably indeed consider using a dedicated Builder for that.
a
So basically 2 questions I still have: • how does JVM handle creation of immutable objects where values match? Any optimisations there? • Any Kotlin compiler optimisations on merging multiple subsequent copy calls on data classes?
f
1. no 2. no
a
And I assume same applies when calling
map
on a list
f
Exactly, use sequences instead, they are optimized for that.
Your example code could be written in multiple idiomatic ways without the copy calls, depending on the goal. 🙂
a
I know, but all this would be extra code I didn't want to write 😃
And main question is if I really have to, because then sounds like data classes could benefit from a generated builder, if copy is so expensive
f
Is it?
Named arguments make builders obsolete.
Copy code
data class TrackingEvent(val type: String, val feature: String, val variant: String) {
    companion object {
        fun articleStage(type: String, stage: String) =
            TrackingEvent(type, "article_stage", "click on article from stage $stage")

        fun articleStageClick(stage: String) =
            articleStage("click", stage)
    }
}

fun main() {
    TrackingEvent.articleStageClick("stage")
}
k
It's often quite hard to judge how much overhead there is on the JVM. Short functions might be inlined, and if it sees an object is created in a scope and never leaves the scope, it can skip allocating memory on the heap.