Построение объекта на основе переданного параметра. Но параметр не String. Я нашел решение, как это сделать с помощью String Scala создает экземпляры объектов из имени класса String но я считаю, что это можно сделать красивее. Скажем, следующие классы:
sealed trait Figure
object Figure {
final case class Circle(radius: Double) extends Figure
final case class Square(a: Double) extends Figure
}
И давайте определим функцию (что не имеет смысла), которая принимает параметр, основанный на том, что я могу вызвать правильный конструктор:
val construct123: Figure => Either[String, Figure] = (figure: Figure) => Right(figure.apply(1.23))
я хочу вызвать
construct123(Circle)
//or
construct123(Square)
Это вообще возможно?
Ваш параметр figure
— это не Figure
, а скорее Function1[Int, Figure]
Проще всего было бы немного изменить подпись construct123
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))
Или construct123
можно записать как функцию высшего порядка
val construct123: (Double => Figure) => Either[String, Figure] =
figure => Right(figure(1.23))
Разница между методом и функцией в Scala
Circle
и Square
в construct123(Circle)
и construct123(Square)
не классы case Circle
и Square
, а их сопутствующие объекты
Объект-компаньон класса против самого класса case
Итак, на самом деле вы хотите преобразовать объект в экземпляр класса-компаньона. Вы можете сделать это, например, с помощью макроса
// 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
}
или
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
}
Тестирование (в другом подпроекте):
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))
Вы можете скрыть работу с макросами в классах шрифтов (определяемых с помощью вайтбоксов неявных макросов ). Класс типа ToCompanion
ниже аналогичен HasCompanion
в Получить сопутствующий объект класса по заданному универсальному типу Scala ( ответ ). Класс типа Generic
из Shapeless (используемый ниже для определения construct123
) также генерируется макросом. Некоторые вступления к классам шрифтов (обычные, не сгенерированные макросом):
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))
Поскольку все классы теперь известны во время компиляции, лучше использовать отражение во время компиляции (приведенные выше макросы). Но в принципе отражение во время выполнения тоже можно использовать
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)
}
или
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)
}
Или вы можете использовать структурные типы, также известные как утиная типизация (то есть также отражение во время выполнения под капотом).
import scala.language.reflectiveCalls
def construct123(figure: { def apply(x: Double): Figure }): Either[String, Figure] =
Right(figure(1.23))
Вам нужен класс типов или отражение, ни один из которых я бы не рекомендовал тем, кто изучает язык.