Consider this simple class:
class MyClass(p: (String, String) *) {
val params: Map[String, String] = p.toMap
}
And a class that extends it:
class SomeOtherClass(p: (String, String) *) extends MyClass(p: _*)
I would like to remove the constructor from MyClass
to avoid carrying around the initialization parameters in everything that extends it (potentially many types). Instead, I'd prefer the companion objects of the types that extend MyClass
to inherit some sort of builder method.
class MyClass {
val param = Map.empty[String, String] // some member with a default value
}
trait MyClassBuilder[A <: MyClass] {
def apply(p: (String, String) *): A = ???
}
Basically apply
should do something like this:
new A {
override val param = p.toMap
}
Obviously, the above code cannot work. The idea is that a sub-type would be used like this:
class SomeOtherClass extends MyClass
object SomeOtherClass extends MyClassBuilder[SomeOtherClass] {
// object specific things..
}
Then SomeOtherClass
would rely on the inherited apply
method for creating instances of itself. It seems something like this may be possible with reflection or macros, but I really have no idea. To be clear, I want client code like SomeOtherClass
to require no constructor at all, which is why I want to explore creating anonymous classes on a generic type. It might not be SomeOtherClass
that gets generated, it could be anything that extends MyClass
.
This may help you:
import scala.reflect._
abstract class MyClassBuilder[A <: MyClass: ClassTag] {
def apply() = classTag[A].runtimeClass.newInstance.asInstanceOf[A]
}
You may find that I didn't pass (String, String) *
here as they're redundant for instantiation. Anyway, you have an access to the whole A
's instance here, so at least you can change the param
after instantiation. It's not exactly what you want - as you seems to looking for a way to additionaly extend SomeOtherClass
in runtime. However, runtime creation of new type in scala is impossible, but you may think of it as a prototype - just take SomeOtherClass instance and mutate once inside apply()
:
import scala.reflect._
object Ctx {
class MyClass {
private[Ctx] var _param = Map.empty[String, String]
def param = _param
}
abstract class MyClassBuilder[A <: MyClass: ClassTag] {
def apply(p: (String, String) *) = {
val i = classTag[A].runtimeClass.newInstance.asInstanceOf[A]
i._param = Map(p: _*)
i
}
}
}
scala> class MySpecialClass extends Ctx.MyClass
defined class MySpecialClass
scala> object MySpecialBuilder extends Ctx.MyClassBuilder[MySpecialClass]
defined module MySpecialBuilder
scala> MySpecialBuilder("A" -> "b")
res12: MySpecialClass = MySpecialClass@2871ed4a
scala> res12.param
res13: scala.collection.immutable.Map[String,String] = Map(A -> b)
Otherwise you have to deal with compile-time reflection.
An interesting alternative is extensible records (Shapeless2) - they practically allow you to build new type piece by piece, but you will have to deal with HList
instead of class there:
import shapeless._ ; import syntax.singleton._ ; import record._
import shapeless.ops.record.Updater
import scala.reflect.runtime.universe._
val param = Witness("param")
val someFunW = Witness("someFun")
//defining classess (instead of "class MyClass")
type Param = Map[String, String] with KeyTag[param.T, Map[String, String]]
type SomeFun = (String => String) with KeyTag[someFunW.T, (String => String)]
type MyClass = Param :: HNil
type MySpecialClass = SomeFun :: MyClass
def someFun(s: String) = s + "A"
//defining default values (instead of "val param = ...")
def newMyClass[T <: HList : TypeTag]: T = (
if (typeTag[T] == typeTag[MyClass])
(param ->> Map.empty[String, String]) :: HNil
else if (typeTag[T] == typeTag[MySpecialClass])
(someFunW ->> someFun _) :: newMyClass[MyClass]
else HNil
).asInstanceOf[T]
//Defining builder
def buildWithParam[T <: HList: TypeTag](p: Map[String, String])(implicit ev: Updater[T, Param])
= newMyClass[T] + ("param" ->> p)
scala> buildWithParam[MySpecialClass](Map("a" -> "v"))
res6: ... = <function1> :: Map(a -> v) :: HNil
scala> res6("someFun").apply("a")
res7: String = aA
scala> buildWithParam[MyClass](Map("a" -> "v"))
res8: ... = Map(a -> v) :: HNil
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With