0

I'm trying to solve a problem that 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 abstract classes, but hadn't had any success

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

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
  • The answer might depend on whether you're on 2.14 or 3 or something else. Do you have any constraints regarding the version on which it has to work? – Andrey Tyukin Dec 07 '22 at 17:41
  • Currently working on Scala 2.12 – Hektor J.G. Dec 07 '22 at 17:45
  • 1
    There is no way to abstract over constructors, you would need to duplicate that bit or generate using macros / reflection / implicits. - However, one approach that sometimes works for me is `trait FooCompanion[F <: Foo] extends (Args => F)` where `Args` are the common arguments of all `Foo` subtypes and then if a `Foo` subtype is a `case class` its companion object will implement the logic for me so I have to do is `object FooSubType extends FooCompanion[FooSubtype]` – Luis Miguel Mejía Suárez Dec 07 '22 at 18:22

3 Answers3

1

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 macros.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
1

Because of the type erasure, in ordinary code new T is allowed only for class type T, not an abstract type/type parameter.

In Scala, is it possible to instantiate an object of generic type T?

How to create an instance of type T at runtime with TypeTags

Class type required but T found

An alternative to runtime reflection (see @StanislavKovalenko's answer) is macros. new T is possible there because during macro expansion T is not erased yet.

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

Alternative implementations:

trait Builder[T <: BuildableClass] {
  def build(code: String): T = macro BuilderMacros.buildImpl
  def build: T = macro BuilderMacros.buildDefaultImpl
}

class BuilderMacros(val c: blackbox.Context) {
  import c.universe._
  val tpe = c.prefix.tree.tpe.baseType(symbolOf[Builder[_]]).typeArgs.head
  def buildImpl(code: Tree): Tree = q"new $tpe($code)"
  def buildDefaultImpl: Tree = q"""new $tpe("bar")"""
}
class BuilderMacros(val c: blackbox.Context) {
  import c.universe._
  val symb = c.prefix.tree.symbol.companion
  def buildImpl(code: Tree): Tree = q"new $symb($code)"
  def buildDefaultImpl: Tree = q"""new $symb("bar")"""
}
class BuilderMacros(val c: blackbox.Context) {
  import c.universe._
  val tpe = c.prefix.tree.tpe.companion
  def buildImpl(code: Tree): Tree = q"new $tpe($code)"
  def buildDefaultImpl: Tree = q"""new $tpe("bar")"""
}
class BuilderMacros(val c: blackbox.Context) {
  import c.universe._
  val tpe = symbolOf[Builder[_]].typeParams.head.asType.toType
    .asSeenFrom(c.prefix.tree.tpe, symbolOf[Builder[_]])
  def buildImpl(code: Tree): Tree = q"new $tpe($code)"
  def buildDefaultImpl: Tree = q"""new $tpe("bar")"""
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
0

Found a much cleaner solution to this problem without using macros / reflection / implicits

trait Buildable

trait Builder[T <: Buildable] {
  self =>
  def apply(code: String): T

  def build: T = self.apply("foo")

  def build(code: String): T = self.apply(code)
}


case class A(code: String) extends Buildable {
  def getCode: String = code
}

object A extends Builder[A]

A.build("bar").getCode
A.build.getCode
  • 1
    Well, actually, it's not clear why you needed `Builder`/`Buildable` at all (especially if you make the class a case class): `case class A(code: String = "foo")` `A("bar").code` `A().code` – Dmytro Mitin Dec 13 '22 at 12:04