Scala3 library for deep cloning objects

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.

Thanks

1 Like

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

2 Likes

I can’t figure out how to import it, what the correct repository url or version.

build.sc

trait Shared extends ScalaModule {
  def scalaVersion = "3.7.0"
}
object foo extends Shared {
  def ivyDeps = Agg(
    ivy"io.github.kostaskougios:cloning:1.12.0"  )
}

foo/src/main.scala

package foo

import com.rits.cloning.Cloner
// import com.rits.cloning.Cloner

What errors are you hitting?

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:

libraryDependencies += "io.github.kostaskougios" % "cloning" % "1.12.0"

(Java dependencies are just like Scala ones, but with only a single %.)

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

given [A: DeepCopy, B: DeepCopy] => DeepCopy[(A, B)]:
  def deepcopy(ab: (A, B)) = (ab._1.deepcopy, ab._2.deepcopy)

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.

1 Like

Isn’t that the notation for sbt dependency declarations? I’m using Mill

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.

1 Like