marcin_koss
marcin_koss

Reputation: 5882

Creating a new instance of existing object type in Scala

I'm trying to create a new instance based on instance of some concrete type. I have a set of types {B, C} that are sub-types of type A and I need some utility in a form of a callable that will take any of existing {B, C} instances and allow create new instance (allowing construction) based on that type.

trait A

case class B(name: String) extends A

case class C(name: String) extends A

// I hoped something like this would work, but it doesn't.
def newALike[T <: A](instance: T): T = instance.copy(name = instance.name + "_new") 

Desired usage:

val b = B("B")
val c = C("C")

val newB = newALike(b)
newB.name // B_new

val newC = newALike(c)
newC.name // C_new

Upvotes: 3

Views: 1469

Answers (4)

Leo
Leo

Reputation: 767

You can achieve this through scala macros. It might be a bit overkill though.

scala> trait A { def name: String }
defined trait A

scala> case class B(name: String) extends A; case class C(name: String) extends A
defined class B
defined class C

scala> def newALikeMacro[T <: A : c.WeakTypeTag](c: Context)(instance: c.Tree): c.Tree = {
     | import c.universe._
     | q"""$instance.copy(name = $instance.name + "_new")""" }
newALikeMacro: [T <: A](c: scala.reflect.macros.blackbox.Context)(instance: c.Tree)(implicit evidence$1: c.WeakTypeTag[T])c.Tree

scala> def newALike[T <: A](instance: T): T = macro newALikeMacro[T]
defined term macro newALike: [T <: A](instance: T)T

scala> val b = B("B"); val c = C("C")
b: B = B(B)
c: C = C(C)

scala> val newB = newALike(b)
newB: B = B(B_new)

scala> val newC = newALike(c)
newC: C = C(C_new)

Upvotes: 1

Alexey Romanov
Alexey Romanov

Reputation: 170745

If your definition worked, what would you expect to happen if someone defined class D(val x: Int) extends A { def name = "" } and called newALike(new D(0))?

There is a shorter alternative to @BenKovitz's excellent answer, using F-bounded polymorphism:

trait A { def name: String }

// alternately, add type parameter and the method to A
trait CanCopy[T <: CanCopy[T]] { def copy(newName: String): T }

case class B(name: String) extends A with CanCopy[B] {
  def copy(newName: String) = B(newName)
}
// same for C

def newALike[T <: A with CanCopy[T]](instance: T): T = instance.copy(name = instance.name + "_new")

Upvotes: 2

Ben Kovitz
Ben Kovitz

Reputation: 5020

This will work, but it could require an awful lot of repetitious code, depending on the specifics of your application:

trait A {
  def name: String
}

case class B(override val name: String) extends A

case class C(override val name: String) extends A

trait CanCopy[T] {
  def copy(t: T, newName: String): T
}

implicit object canCopyB extends CanCopy[B] {
  override def copy(b: B, newName: String) = b.copy(name=newName)
}

implicit object canCopyC extends CanCopy[C] {
  override def copy(c: C, newName: String) = c.copy(name=newName)
}

def newALike[T <: A](instance: T)(implicit ev: CanCopy[T]): T =
  ev.copy(instance, instance.name + "_new")

The problem is that trait A can't know the specifics of how to construct an instance of a descendant class. As the Scala compiler sees it, there is no telling what you might define as an extension of trait A, or what arguments its constructor might take. CanCopy and the implicit objects tell the Scala compiler "This is how you construct a B, and this is how you construct a C." The implicit argument's name is ev, to stand for "evidence": it tells the compiler to look for evidence that the type T can be copied, and that evidence is supplied by an object that can do the job.

Depending on your application, you might be able to avoid some repetitious code by defining another trait, which extends A, and which B and C extend, which guarantees that a .copy method with specific arguments will be available. Then you could have a single implicit object, of type CanCopy[ThatIntermediaryTrait], which knows to call the .copy method.

Upvotes: 5

nitishagar
nitishagar

Reputation: 9413

You can try using copy method:

scala> val b = B("B")
scala> val c = C("C")

scala> val newB = b.copy("B_new")
scala> newB.name // B_new

scala> val newC = c.copy("C_new")
scala> newC.name // C_new

Upvotes: 0

Related Questions