CruncherBigData
CruncherBigData

Reputation: 1122

scala accepting generic classes

I have two case classes

abstract  class MainClass
case class Acc(x:String, y:String)  extends MainClass
case class Bcc(x:String, y:String)  extends MainClass

I need to write a generic fucntion that will take either one of them and return a list of the same case class, something similar to :

def f1[T <: MainClass ]():List[T]={
val o1 = new Acc("sss","ddd").asInstanceOf[T]
List(o1)
}

the only problem is that I am hard-coding the type class "Acc" . My question is How do I make the f1 method instantiate the generic class T instead of Acc or Bcc

thanks in advance.

Upvotes: 2

Views: 382

Answers (2)

Travis Brown
Travis Brown

Reputation: 139038

While the ClassTag solution may work for you, relying on runtime reflection is a code smell, and it can actually get you into real trouble. Suppose you end up with a new subclass like this (defined either by you or by someone else using your code):

case class Ccc(x: String, y: String, i: Int)  extends MainClass

Now when someone writes f1[Ccc]—and there's no indication in the method signature that they shouldn't—their program will crash at runtime with a NoSuchMethodException.

There's a much safer way to do this kind of thing, and while it requires a bit of boilerplate-writing for you, it's transparent to people using your code down the line (including yourself). First you define a type class that describes how to create an instance of some specific subclass of MainClass:

trait MainClassFromStrings[T <: MainClass] {
  def apply(x: String, y: String): T
}

Then you write some type class instances:

implicit def AccFromStrings: MainClassFromStrings[Acc] =
  new MainClassFromStrings[Acc] {
    def apply(x: String, y: String) = Acc(x, y)
  }

implicit def BccFromStrings: MainClassFromStrings[Bcc] =
  new MainClassFromStrings[Bcc] {
    def apply(x: String, y: String) = Bcc(x, y)
  }

And now you can write your f1 pretty cleanly:

def f1[T <: MainClass](implicit fs: MainClassFromStrings[T]) =
  List(fs("sss", "ddd"))

And then:

scala> f1[Acc]
res0: List[Acc] = List(Acc(sss,ddd))

scala> f1[Bcc]
res1: List[Bcc] = List(Bcc(sss,ddd))

But you get a nice compile-time error if you try f1[Ccc].

Type classes are pretty widely used in Scala (including in the standard library)—searching Stack Overflow for "type classes" in Scala will turn up lots of examples and discussion.

Upvotes: 11

grotrianster
grotrianster

Reputation: 2468

You can use ClassTag to keep meta information about Class during runtime.

import scala.reflect._


abstract  class MainClass
case class Acc(x:String, y:String)  extends MainClass
case class Bcc(x:String, y:String)  extends MainClass

object  Boot extends App {



  def f1[T <: MainClass : ClassTag ]():List[T]={
    val o1 = classTag[T].runtimeClass.getConstructor(classOf[String], classOf[String]).newInstance("sss","ddd").asInstanceOf[T]
    List(o1)
  }

 val list =  f1[Acc]

}

Upvotes: 1

Related Questions