Reputation: 5882
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
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
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
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
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