scala 根据传递的参数调用构造函数

fhity93d  于 2023-02-08  发布在  Scala
关注(0)|答案(1)|浏览(170)

构造一个基于传递参数的对象。但参数不是字符串。我找到了如何用字符串Scala instantiate objects from String classname来做的解决方案,但我相信它可以做得更好。让我们假设下面的类:

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)

这可能吗?

li9yvcax

li9yvcax1#

最简单的方法是稍微修改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))

Difference between method and function in Scala
construct123(Circle)construct123(Square)中的CircleSquare不是事例类CircleSquare,而是它们的伴随对象
Class companion object vs. case class itself
所以实际上你想把一个对象转换成它的同伴类的示例,你可以用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
}

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

您可以隐藏类型类中的宏(使用白盒implicit macros定义)。下面的类型类ToCompanion类似于Get companion object of class by given generic type Scalaanswer)中的HasCompanionShapeless中的类型类Generic(下面用于定义construct123)也是宏生成的。一些类型类(普通,非宏生成)的介绍:1234567个89个

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

由于所有的类现在在编译时都是已知的,所以最好使用编译时反射(上面的宏),但原则上也可以使用runtime reflection

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

或者你可以使用结构类型aka duck typing(也就是运行时反射)

import scala.language.reflectiveCalls

def construct123(figure: { def apply(x: Double): Figure }): Either[String, Figure] =
  Right(figure(1.23))

相关问题