Scala:将Iterable[Any]转换为其内容的实际类型

kgsdhlau  于 2023-08-05  发布在  Scala
关注(0)|答案(1)|浏览(91)

我希望能够将泛型可迭代对象转换为其内容的实际类型,但在编译时我不知道该类型。有没有一种方法可以实现这样的功能?

def castIterable[IN, OUT](iterable: Iterable[IN]): Iterable[OUT] = {
  iterable.asInstanceOf[Iterable[ figureOutContentType(...) ]]
}

字符串

cngwdvgl

cngwdvgl1#

您不能在类型系统中表示您不知道的类型,因为它在运行时会变化。你最多只能表达你知道它存在的知识,以及某样东西将与它属于同一类型。
这就是泛型和存在类型的作用。
泛型是表示 * 全称量化 * 的类型系统方式:对于每一个类型X,都有一些东西跟随。def method[A >: L <: U](a: A) = ...是构造的一个证明,即在没有任何假设的情况下,除了上下限和一个值之外,物体可以构造为A而不知道A。在method里面,你只知道A的存在,而不知道别的!在外部,您可以指定它为您想要的任何内容,并获得具体的实现。
存在类型是这样一种类型,您知道该类型存在,正是因为您收到了它的示例。例如def method(opt: Option[?]) = ...。它是 * 存在量化 * 的类型系统编码。它与泛型的不同之处在于您没有此未知类型的句柄。
在实践中,如果你在name后面看到[A, B, ...],它将是泛型的,如果有?(在Scala 3中,在Scala 2中没有-Xsource:3和其他标志,存在类型有_,很容易与类型构造函数混淆)。但你常常可以把一个翻译成另一个:

// The content of Option is some unknown type, an existential type
val option: Option[?] = ...

// This method is defined with generic parameter A
def asString[A]: A => String = _.toString

// Here, compiler applies unknown type to asString
// - we don't know it but since asString can be applied
// FOR EVERY TYPE it must be applicable to our unknown type as well.
option.map(asString)

个字符
在Scala 3中,存在类型的使用是有限的,但你可以使用路径依赖类型来表达这个想法:

trait MyType {
  type MyAbstractType

  val value: MyAbstractType

  val function: (MyAbstractType, MyAbstractType) => MyAbstractType
}

// We have no idea what myType.MyAbstractType is...
val myType: MyType = ...

// ...but we can pass values around when we know what this unknown type
// match the type of method arguments etc.
myType.function(myType.value, myType.value)


这让我们回到你的问题,电流和the previous one
你想创建类似于:

val values: List[SomeType] = ...

values
  .map(parsedLambda(string1))
  .map(parsedLambda(string2))
  .map(parsedLambda(string3))


这里的问题是你不能编译没有输入类型(a => something)的lambda表达式。你最多可以对初始输入类型(你知道的)建模,但其余的类型是在运行时创建的,所以你不能静态地表达它们。
然而,你可以用存在类型/路径依赖类型来建模你的代码。一个草案,应该提出的一般想法(但不一定工作)可能看起来像:

object Utility {
  class Helper[Input] {
    def apply[Output](f: Input => Output): Input => Output = f
  }
  def fixInputInferOutput[Input] = new Helper[Input]
}

import scala.reflect.runtime.universe.*
import scala.tools.reflect.*

trait SomeSequence { self =>
  type ElementType
  val ElementType: String

  val sequence: List[ElementType]

  def parseLambdaAndMap(lambda: String): SomeSequence = {
    val code = s""" Utility.fixInputInferOutput[$ElementType]($lambda) """
    val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
    val tree = toolbox.parse(code)
    new SomeSequence {
      // The type isn't necessarily Any, but it's easier to implement it that
      // way - the important part is that outside world would have to rely
      // on the ElementType string when chaining the results
      type ElementType = Any
      val ElementType = tree.tpe.finalResultType.typeSymbol.fullName

      val sequence = self.sequence.map(
        toolbox.compile(tree)().asInstanceOf[ElementType => Any]
      )
    }
  }
}
object SomeSequence {
  def apply[A: WeakTypeTag](list: List[A]): SomeSequence = new SomeSequence {
    type ElementType = A
    val ElementType = weakTypeTag[A].toString()

    val sequence = list
  }
}


用作

SomeSequence(values)
  .parseLambdaAndMap(string1)
  .parseLambdaAndMap(string2)
  .parseLambdaAndMap(string3)
  ...


当编写这样的东西时,你就进入了REPL、类似Scastie的ID、编译器的领域,可能还有Apache Spark的lambda序列化。在这种情况下,我建议你学习一些关于类型理论、编译器、运行时和编译时反射的课程--以便对你正在尝试做的事情有一个很好的理解。这并不简单,它不可能在一个晚上被破解(除非你真的知道你在做什么),如果不是出于教育目的,我建议尝试使用一些现有的工作,因为从现在开始它只会更难。

相关问题