Reputation: 457
I'm working in Spark 3.1 with Scala 2.12.10.
I'd like to create a collection (Seq, whatever) of case classes that have implemented a common trait, so I can execute some generic dataset code on the collection members without having to type each incantation separately. I think I can get the erased type via TypeTag, but I'm unable to define the collection in the first place!
Given these types:
trait HasID { val id: String }
case class Customer(id: String, name: String) extends HasID
case class Product(id: String, desc: Int) extends HasID
case class Sale(id: String, value: Float) extends HasID
class Appender[T <: HasID : Encoder] { ... } // dataset methods
I can call the code:
new Appender[Customer]() ...
new Appender[Product]() ...
new Appender[Sale]() ...
But what I want is to put the classes in a collection and loop through it:
Seq[HasID](Customer, Product, Sale).foreach(c =>
// get type C of c somehow
new Appender[C]() ...
)
Upvotes: 1
Views: 683
Reputation: 1118
val appendCustomer = new Appender[Customer]()
val appendProduct = new Appender[Product]()
val appendSale = new Appender[Sale]()
//get your instances
val aCustomer: Custome = Customer(....)
val aProduct = Product(...)
val aSale = Sale()
//loop throw them and apply the correspondent appender
Seq[HasID](aCustomer, aProduct, aSale).foreach {
case x: Customer => appenderCustomer.method(x)
case x: Product => appenderProduct.method(x)
case x: Sale => appenderSale.method(x)
)
I've used that logic but apply whatever you need in the right side of the pattern maching
Upvotes: 0
Reputation: 13985
When you say that you are failing to create the collection itself, are you talking about the collection Seq[HasID](Customer, Product, Sale)
?
This part will cause an error because Customer
, Product
, Sale
in Seq[HasID](Customer, Product, Sale)
are actually compaion objects of those case classes. And these companion objects have no relation so HasId
.
If you want to create a collection ofhave instances of Customer
, Product
and Sale
then you will have to put instances of these classes inside the Seq/List.
val list = List[HasID](
Customer("c1", "c1"),
Product("p1", 1),
Sale("s1", 1.0F)
)
Now, we can focus on your actual objective.
You can use the canonical class names as the keys when you are creating your map for appenders.
trait HasID {
val id: String
}
case class Customer(id: String, name: String) extends HasID
case class Product(id: String, desc: Int) extends HasID
case class Sale(id: String, value: Float) extends HasID
import java.security.InvalidParameterException
abstract class Appender[T <: HasID] {
def append(item: T): Unit
def appendHasId(item: HasID): Unit = item match {
case t: T => append(t)
case _ => throw new InvalidParameterException()
}
}
val appenders = Map(
classOf[Customer].getCanonicalName -> new Appender[Customer] {
override def append(item: Customer): Unit = println("appended Customer")
},
classOf[Product].getCanonicalName -> new Appender[Product] {
override def append(item: Product): Unit = println("appended Product")
},
classOf[Sale].getCanonicalName -> new Appender[Sale] {
override def append(item: Sale): Unit = println("appended Sale")
}
)
val list = List[HasID](
Customer("c1", "c1"),
Product("p1", 1),
Sale("s1", 1.0F)
)
list.foreach { x => appenders(x.getClass.getCanonicalName).appendHasId(x) }
Output:
appended Customer
appended Product
appended Sale
Upvotes: 0
Reputation: 1551
Actually, it works if you change the type Seq[HasID]
to Seq[String => HasID]
. You don't need a ObjectCreaterWithId
or HasIDMaker
at all:
scala> val objs = Seq[String => HasID](Customer, Product, Sale)
val objs: Seq[String => HasID] = List(Customer, Product, Sale)
// And then you can create a list of instances by objs
scala> val ids = objs map (_ ("one"))
val ids: Seq[HasID] = List(Customer(one), Product(one), Sale(one))
EDIT: @aminmal, talk is cheep, check the code yourself.
Welcome to Scala 2.13.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_312).
Type in expressions for evaluation. Or try :help.
scala> case class A(i: Int)
class A
// Note: A.type is a subclass of Function1.
// SO, YES **object A is An instance of function Int => A**
scala> classOf[A.type].getSuperclass()
val res0: Class[_ >: A.type] = class scala.runtime.AbstractFunction1
By the way, there is a question Why do case class companion objects extend FunctionN? answered by the scala creator Martin Odersky.
Upvotes: 0
Reputation: 3173
This:
case class Customer(id: String) extends HasId {...}
Is compiled into something like this (not completely equal):
class Customer(val id: String) extends HasId {...}
object Customer { def apply(id: String): Customer = ??? }
So pay attention that the object is not extending HasId. Companion objects kind of hold the static members of a class, but the id here is instance-dependent. Now if you just need the companion objects, and you can use reflection or something to access the id of an actual object, I recommend you to define a class and a companion object instead of a case class:
trait HasId {
def id: String
}
trait ClassHasIdMarker
class Customer private(override val id: String) extends HasId
object Customer extends ClassHasIdMarker {
def apply(id: String): Customer = new Customer(id)
}
// These 2 are companions
// all the companion objects now extend a single trait (you can create a seq now)
// if you use reflection to access objects of the class, you can have the id
Please let me know if I understood your question correctly and if this helps!
Update
A type class would also be a good idea (as @Luis Miguel Suarez mentioned)
trait ObjectCreaterWithId[ActualType <: HasId] {
def apply(id: String): HasId
}
// class Customer ...
object Customer extends ObjectCreaterWithId[Customer] {
override def apply(id: String): HasId = new Customer(id)
}
Update #2 So if you need the runtime types, you should use reflection, it has easy APIs to use, you can take a look on the internet, here's a brief code about the same thing:
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(getClass.getClassLoader)
val customerClass = classOf[Customer]
def getConstructor[T: TypeTag] =
typeOf[T].decl(termNames.CONSTRUCTOR)
.asMethod // this is the primary constructor
val classMirror = mirror.reflectClass(mirror.classSymbol(customerClass))
val instance = classMirror.reflectConstructor(getConstructor[Customer]).apply("two").asInstanceOf[Customer]
Upvotes: 3