I'm trying to solve a problem which may not be possible in Scala.
I want to have a Trait to solve default constructors
trait Builder[T <: Buildable] {
def build(code:String): T = new T(code)
def build: T = new T("bar")
}
So extending the Trait on the companion object, automatically has access to functions that creates the class with specific constructors and parameters
class A(code:String) extends Buildable
object A extends Builder[A]
Extending the Trait, the companion object has the constructors
A.build("foo")
A.build
Is this possible in Scala?
Also tried with abstract classes, but hadn't had any chance
trait Builder[T <: BuildableClass] {
def build(code:String): T = new T(code)
def build: T = new T("bar")
}
abstract class BuildableClass(code:String)
class A(code:String) extends BuildableClass(code)
object A extends Builder[A]
Thanks in advance
Edit: currently locked on Scala 2.12
CodePudding user response:
One of the potential solution that uses a reflection looks a bit ugly, but it works.
import scala.reflect._
trait Builder[T <: Buildable] {
def build(code: String)(implicit ct: ClassTag[T]): T =
ct.runtimeClass.getConstructors()(0).newInstance(code).asInstanceOf[T]
def build(implicit ct: ClassTag[T]): T =
ct.runtimeClass.getConstructors()(0).newInstance("bar").asInstanceOf[T]
}
trait Buildable
class A(code: String) extends Buildable {
def getCode = code
}
object A extends Builder[A]
val a: A = A.build
println(a.getCode)
The problem is that your Builder
trait doesn't know anything about how to construct your instances. You can get this info from runtime with reflection or from compile time with macroses.
CodePudding user response:
Because of the type erasure, in ordinary code new T
is allowed only for class type T
, not an abstract type/type parameter.
An alternative to runtime reflection (see @StanislavKovalenko's answer) is macros. new T
is possible there.
import scala.language.experimental.macros
import scala.reflect.macros.blackbox // libraryDependencies = scalaOrganization.value % "scala-reflect" % scalaVersion.value
abstract class BuildableClass(code: String)
trait Builder[T <: BuildableClass] {
def build(code: String): T = macro BuilderMacros.buildImpl[T]
def build: T = macro BuilderMacros.buildDefaultImpl[T]
}
class BuilderMacros(val c: blackbox.Context) {
import c.universe._
def buildImpl[T: WeakTypeTag](code: Tree): Tree = q"new ${weakTypeOf[T]}($code)"
def buildDefaultImpl[T: WeakTypeTag]: Tree = q"""new ${weakTypeOf[T]}("bar")"""
}
// in a different subproject
class A(code:String) extends BuildableClass(code)
object A extends Builder[A]
A.build("foo") // scalac: new A("foo")
A.build // scalac: new A("bar")