Home > Net >  Can Scala Companion Object Traits call a constructor of the class?
Can Scala Companion Object Traits call a constructor of the class?

Time:12-08

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