I have objects with mutable variables that may also be other objects with mutable variables and so on. I need a function that can take any object and produce a deep copy; a fresh instance of the object with fresh instances of all nested objects downwards. I cannot manually create this method as in my use case I don’t know what the shape of the objects will be, the problem territory is highly generalized.
I found GitHub - kostaskougios/cloning: deep clone java objects but it looks like it does not work for Scala3. Or maybe I just couldn’t figure out how to import it, would appreciate if someone can confirm. If not, is there another library I can do this for?
I understand this is an expensive operation and should be used rarely, and I also understand how shallow copies would suffice if I chose to keep data immutable; I’ve considered this, and immutability is too impractical for my use case, so I must have a deep copy instead.
I don’t see anything special about this lib that might not work with Scala in general.
I don’t know what it actually does under the hood, but probably it’s using reflection to perform actual cloning. It probably does modify some internal state of Scala collection leading to strange head of empty list errors at runtime, but for normal data types it seems to work correctly.
//> using dep "io.github.kostaskougios:cloning:1.12.0"
import com.rits.cloning.Cloner
case class Address(street: String, city: String){
var refs = List.empty[Person]
}
case class Person(name: String, age: Int, address: Address)
@main def cloneExample(): Unit =
val original = Person("Alice", 30, Address("123 Main St", "Springfield"))
original.address.refs = List(original)
val cloner = Cloner()
val cloned = cloner.deepClone(original)
println(s"Original: $original") // Original: Person(Alice,30,Address(123 Main St,Springfield))
println(s"Cloned: $cloned") // Cloned: Person(Alice,30,Address(123 Main St,Springfield))
val origRef = original.address.refs.head
val copyRef = cloned.address.refs.head
println(origRef == copyRef) // true
println(origRef eq copyRef) // false
println(cloned.address.refs) // Exception in thread "main" java.util.NoSuchElementException: head of empty list
I’m not aware of any Scala library that might help for your requirements
And is that how you are normally declaring your dependencies, with an explicit ivyDeps? I would expect to just declare it in libraryDependencies as usual:
Since no-one else has mentioned it yet, this is a deeply dangerous operation if used on any data with shared mutable contents or for which there are any resources or anything being kept track of (e.g. caching).
Because this is inherently fraught with peril, the idiomatic way to do this would be via something like
trait DeepCopy[A] { def deepcopy(a: A): A }
extension [A](a: A)
def deepcopy(using dc: DeepCopy[A]): A = dc.deepcopy(a)
or somesuch. You’d then have a lot of things that look like
This seems infuriating and pointless until you run into val io = (new InputStream(p), new OutputStream(q)); ...; io.deepcopy, at which point it seems indispensable.
Most of the time, idiomatic Scala tries to avoid patterns that explode at runtime unless everyone handles them with velvet gloves.
Ah, okay – my bad. I’ve only used Mill once, a fair number of years ago, so I failed to recognize the syntax.
It would still be helpful to know what errors you’re getting, though: as it is, you haven’t provided enough information for us to help much with the import problem.