3

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
0

4 Answers 4

5

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.

Sign up to request clarification or add additional context in comments.

1 Comment

You don't need override val in case class
2

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")

Comments

1

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)

Comments

0

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

2 Comments

Thanks... I'm already using copy in my question in def newALike. The point is to have a function that accepts any child object and creates new instance of it.
@marcin_koss Ahh. I see - the above way of implicit will help - EOD you have to use copy :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.