【问题标题】:Scala - looping over case class names to use as type parametersScala - 循环案例类名称以用作类型参数
【发布时间】:2021-10-22 10:40:08
【问题描述】:

我在 Scala 代码 (Scala 2.13) 中有一个这样的函数,可与 Spark 一起使用

def getDataset[T <: Product: TypeTag](name:String): Dataset[T] = {
    import spark.implicits._

    val ds = spark.read.parquet(BASE_PATH + "/" + name).as[T]
    ds.createOrReplaceTempView(name)
    ds
}

现在我想转一个Seq的案例类,并为每个类调用这个函数:

case class CLASS1(...)
case class CLASS2(...)
case class CLASS3(...)

Seq(CLASS1, CLASS2, CLASS3, ....).foreach {
  c => getDataset[c??](name=c???)
}

我很难弄清楚确切的语法;案例类名称的符号,由foreach 中的变量c 表示,似乎表示apply 方法的类型(() =&gt; Product)。我真正想要的是用作类型参数的案例类的type,以及案例类的name

感觉我应该能够做到这一点 - 我在这里错过了什么?

更新看起来可以通过TypeTag在运行时获取类型参数中使用的类型的名称。

我正在融合的解决方案是这样的:

def getDataset[T <: Product: TypeTag]: Dataset[T] = {
    import spark.implicits._

    val name = typeTag[T].tpe.typeSymbol.name.toString
    val ds = spark.read.parquet(BASE_PATH + "/" + name).as[T]
    ds.createOrReplaceTempView(name)
    ds
}

然后像Seq(getDataset[CLASS1], getDataset[CLASS2], ...)

不是我所希望的,但至少我可以剪掉类名和字符串的复制粘贴。

【问题讨论】:

  • 你是如何在 Scala 2 中构建这个 Seq 的? Scala 2 中的所有集合都是同质的。你不能有一个元素序列,其中每个元素都是不同类的实例
  • 这可能是Seq[Class[_]] 吗?
  • 啊,好吧,所以它们都是同一类型,只是对于任何A,seq 可能是Seq[A]。写class1, class2, class3 有点混乱。 c.getClass 出于某种原因在这里不够用吗?
  • 不,这是案例类名,但显然案例类名的符号被解释为应用函数而不是类,所以它是同质的Seq[() =&gt; Product]
  • @wrschneider In Seq(Class1, Class2, Class3) Class1, Class2, Class3 是案例类的伴随对象。

标签: scala apache-spark


【解决方案1】:

您可以为案例类定义自己的伴随对象,并在每个对象中包含一个调用getDataset 的方法。例如,这应该可以工作(由我的心理编译器传递):

abstract class DatasetProvider[T <: Product : TypeTag] {
  val name: String
  def dataset: Dataset[T] =
    getDataset[T](name)
}

case class Class1(...)

object Class1 extends DatasetProvider[Class1] {
  override val name: String = "class1"
}

// and so forth for Class2, Class3

Seq(Class1, Class2, Class3).foreach { c =>
  val ds = c.dataset
  ???
}

请注意,如果定义您自己的伴生对象,如果您想将其用作一个函数,则必须将其显式标记为函数:这可能是可取的,也可能不是可取的。

【讨论】:

  • 为了满足getDataset 的界限,我将trait DatasetProvider[T] 替换为abstract class DatasetProvider[T &lt;: Product : TypeTag]
  • 我的心理编译器和实际编译器之间的任何相似之处都是巧合:)
【解决方案2】:

问题是您想在类型级别替换T(在编译时已知)和在值级别替换name(在运行时已知)。

通常Tname 不会同时存在。

一种选择是将值级别的Seq(Class1, Class2, Class3) 替换为类型级别的Class1 :: Class2 :: Class3 :: HNil 并使用Shapeless

import shapeless.{::, HNil, Poly0, Poly1, Typeable}
import shapeless.ops.hlist.FillWith
import scala.reflect.runtime.universe.{TypeTag, typeOf}

object datasetPoly extends Poly1 {
  implicit def cse[T <: Product : TypeTag /*: Typeable*/]: Case.Aux[T, Dataset[T]] = 
    at(_ => getDataset[T](/*Typeable[T].describe*/typeOf[T].toString))
}

object nullPoly extends Poly0 {
  implicit def cse[T >: Null]: Case0[T] = at(null)
}

FillWith[nullPoly.type, Class1 :: Class2 :: Class3 :: HNil].apply().map(datasetPoly)

您也可以使用macrosruntime reflection。 在Seq(Class1, Class2, Class3)Class1Class2Class3 中是案例类的伴随对象。 例如reflective toolbox

import scala.reflect.runtime.universe.Quasiquote
import scala.reflect.runtime.{currentMirror => cm}
import scala.tools.reflect.ToolBox

val tb = cm.mkToolBox()

Seq(Class1, Class2, Class3).foreach(c => {
  val classSymbol = cm.reflect(c).symbol.companion
  tb.eval(q"App.getDataset[$classSymbol](${classSymbol.name.toString})")
})

你应该添加到build.sbt

libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value

【讨论】:

  • 我会接受这个答案,因为我认为这里的难题很简单,你不能在没有反射的情况下在运行时指定类型参数,我需要找到另一个选项。
猜你喜欢
  • 1970-01-01
  • 2015-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多