Constructing an object based on passed parameter. But the parameter is not String. I found the solution how to do it with String Scala instantiate objects from String classname but I believe that it can be done nicer. Let's say the following classes:
sealed trait Figure
object Figure {
final case class Circle(radius: Double) extends Figure
final case class Square(a: Double) extends Figure
}
And let's define a function (which doesn't make sense) which take a parameter based on I can invoke the proper constructor:
val construct123: Figure => Either[String, Figure] = (figure: Figure) => Right(figure.apply(1.23))
I want to invoke
construct123(Circle)
//or
construct123(Square)
Is it even possible?
CodePudding user response:
The easiest would be to modify the signature of construct123
slightly
def construct123(figure: Double => Figure): Either[String, Figure] =
Right(figure(1.23))
construct123(Circle.apply) // Right(Circle(1.23))
construct123(Square.apply) // Right(Square(1.23))
construct123(Circle(_)) // Right(Circle(1.23))
construct123(Square(_)) // Right(Square(1.23))
construct123(Circle) // Right(Circle(1.23))
construct123(Square) // Right(Square(1.23))
Or construct123
can be written as a higher-order function
val construct123: (Double => Figure) => Either[String, Figure] =
figure => Right(figure(1.23))
Difference between method and function in Scala
Circle
and Square
in construct123(Circle)
and construct123(Square)
are not the case classes Circle
and Square
but their companion objects
Class companion object vs. case class itself
So actually you want to transform an object into an instance of its companion class. You can do this for example with a macro
// libraryDependencies = scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def construct123[A](figure: A): Either[String, Figure] = macro construct123Impl[A]
def construct123Impl[A: c.WeakTypeTag](c: blackbox.Context)(figure: c.Tree): c.Tree = {
import c.universe._
val companionClass = weakTypeOf[A].companion
q"_root_.scala.Right.apply(new $companionClass(1.23))" // using constructor of the case class
}
or
def construct123Impl[A: c.WeakTypeTag](c: blackbox.Context)(figure: c.Tree): c.Tree = {
import c.universe._
val A = symbolOf[A].asClass.module
q"_root_.scala.Right.apply($A.apply(1.23))" // using apply method of the companion object
}
Testing (in a different subproject):
construct123(Circle) // Right(Circle(1.23))
construct123(Square) // Right(Square(1.23))
// scalacOptions = "-Ymacro-debug-lite"
//scalac: scala.Right.apply(new Macros.Figure.Circle(1.23))
//scalac: scala.Right.apply(new Macros.Figure.Square(1.23))
//scalac: scala.Right.apply(Circle.apply(1.23))
//scalac: scala.Right.apply(Square.apply(1.23))
You can hide the work with macros in type classes (defined with whitebox implicit macros). The type class ToCompanion
below is similar to HasCompanion
in Get companion object of class by given generic type Scala (answer). The type class Generic
from Shapeless (used below to define construct123
) is also macro-generated. Some intros to type classes (ordinary, not macro-generated): 1 2 3 4 5 6 7 8 9
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
// type class
trait ToCompanion[A] {
type Out
}
object ToCompanion {
type Aux[A, Out0] = ToCompanion[A] {type Out = Out0}
// materializer
def apply[A](implicit tcc: ToCompanion[A]): ToCompanion.Aux[A, tcc.Out] = tcc
// def apply[A](implicit tcc: ToCompanion[A]): tcc.type = tcc
// instance of the type class
implicit def mkToCompanion[A, B]: ToCompanion.Aux[A, B] = macro mkToCompanionImpl[A]
// implicit def mkToCompanion[A]: ToCompanion[A] = macro mkToCompanionImpl[A] // then implicitly[ToCompanion.Aux[Circle, Circle.type]] doesn't compile in spite of white-boxity
def mkToCompanionImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val companion = A.companion
val ToCompanion = weakTypeOf[ToCompanion[A]]
q"new $ToCompanion { type Out = $companion }"
}
}
implicitly[ToCompanion.Aux[Circle, Circle.type]] // compiles
implicitly[ToCompanion.Aux[Circle.type, Circle]] // compiles
val tc = ToCompanion[Circle.type]
implicitly[tc.Out =:= Circle] // compiles
val tc1 = ToCompanion[Circle]
implicitly[tc1.Out =:= Circle.type] // compiles
// libraryDependencies = "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.{::, Generic, HNil}
def construct123[A, B <: Figure](figure: A)(implicit
toCompanion: ToCompanion.Aux[A, B],
generic: Generic.Aux[B, Double :: HNil]
): Either[String, Figure] = Right(generic.from(1.23 :: HNil))
construct123(Circle) // Right(Circle(1.23))
construct123(Square) // Right(Square(1.23))
Since all the classes are now known at compile time it's better to use compile-time reflection (the above macros). But in principle runtime reflection can be used too
import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.universe._
def construct123[A: TypeTag](figure: A): Either[String, Figure] = {
val classSymbol = symbolOf[A].companion.asClass
//val classSymbol = typeOf[A].companion.typeSymbol.asClass
val constructorSymbol = typeOf[A].companion.decl(termNames.CONSTRUCTOR).asMethod
val res = rm.reflectClass(classSymbol).reflectConstructor(constructorSymbol)(1.23).asInstanceOf[Figure]
Right(res)
}
or
import scala.reflect.ClassTag
import scala.reflect.runtime.{currentMirror => rm}
import scala.reflect.runtime.universe._
def construct123[A: TypeTag : ClassTag](figure: A): Either[String, Figure] = {
val methodSymbol = typeOf[A].decl(TermName("apply")).asMethod
val res = rm.reflect(figure).reflectMethod(methodSymbol).apply(1.23).asInstanceOf[Figure]
Right(res)
}
Or you can use structural types aka duck typing (i.e. also runtime reflection under the hood)
import scala.language.reflectiveCalls
def construct123(figure: { def apply(x: Double): Figure }): Either[String, Figure] =
Right(figure(1.23))