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

Scala: Convert Iterable[Any] to the actual type of its content

提问人:Da Mike 提问时间:6/27/2023 最后编辑:Da Mike 更新时间:7/1/2023 访问量:67

问:

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

def castIterable[IN, OUT](iterable: Iterable[IN]): Iterable[OUT] = {
  iterable.asInstanceOf[Iterable[ figureOutContentType(...) ]]
}
Scala 泛型 反射 转换

评论

0赞 stefanobaghino 6/27/2023
您可以使用运行时反射。此处记录了 Scala 2:docs.scala-lang.org/overviews/reflection/overview.html#
0赞 Da Mike 6/27/2023
你能举个例子吗?因为我仍然不清楚我应该怎么做。 谢谢!
3赞 t9dupuy 6/27/2023
问:你希望你的程序如何知道返回类型?是通过某种继承吗?或者您只是在寻找泛型类型转换?无论哪种方式,在99%的情况下,使用错误定义的问题都是糟糕的解决方案。顺便说一句,在编译时不知道类型实际上是不合理的:编译器肯定知道类型(即使你不知道)。您可以查看匹配类型。请为您的问题添加更多信息。asInstanceOf
0赞 Da Mike 6/27/2023
它涉及这种情况,即我从用户给出的字符串创建 lambda 函数。例如,这些 lambda 可以用于运算符。因此,可以将列表映射到例如.但目前,每个用户定义的 lambda 的输出类型都是,我想改变它。mapIntStringAny

答:

0赞 Mateusz Kubuszok 7/1/2023 #1

不能在类型系统中表示未知的类型,因为它在运行时会有所不同。充其量,你可以表达你知道它存在并且某些东西将与它属于同一类型的知识。

这就是泛型和存在类型开始发挥作用。

泛型是表达通用量化的类型系统方式:对于每个类型 X,都遵循以下内容。 是构造的证明,在没有任何假设的情况下 - 除了上限和下限以及一个值 - 可以在不知道的情况下构造主体。在里面,你只知道它的存在,而不知道其他的!在外部,您可以将其指定为所需的任何内容,并获得具体的实现。def method[A >: L <: U](a: A) = ...AAmethodA

存在类型是一种类型,你知道该类型存在,正是因为你收到了它的实例。例如。它是存在量化的类型系统编码。它与泛型的不同之处在于,你没有此未知类型的句柄。def method(opt: Option[?]) = ...

在实践中,如果你看到 name 之后,它将是泛型的,如果有(在 Scala 3 中,在 Scala 2 中没有和其他标志,则存在类型,这很容易与类型构造函数混淆)。但是您通常可以将一个翻译成另一个:[A, B, ...]?-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)
// This class has a generic parameter A 
class Foo[A](a: A)

// Here, we made compiler "forget" the exact type applied to A
// turning it into an existential type.
val foo: Foo[?] = new Foo("test")

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

这让我们回到你的问题,当前和上一个问题

你想创建类似的东西:

val values: List[SomeType] = ...

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

其中问题是您无法在没有输入类型 () 的情况下编译 lambda。充其量,您可以对初始输入类型(您知道的)进行建模,但其余的都是在运行时创建的类型,因此您不会静态地表示它们。a => something

但是,您可以使用存在类型/路径相关类型对代码进行建模。应该提出总体思路(但不一定有效)的草案可能如下所示:

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、编译器,可能还有 lambda 序列化(如 Apache Spark)的领域。在这种情况下,我建议做一些关于类型论、编译器、运行时和编译时反射的课程——只是为了对你试图做什么有一个充分的理解。这并不简单,它不可能在一个晚上被黑客攻击(除非你真的知道你在做什么),如果它不是出于教育目的,我建议尝试使用一些现有的工作,因为从现在开始只会更难。