提问人:Da Mike 提问时间:6/27/2023 最后编辑:Da Mike 更新时间:7/1/2023 访问量:67
Scala:将 Iterable[Any] 转换为其内容的实际类型
Scala: Convert Iterable[Any] to the actual type of its content
问:
我希望能够将泛型可迭代对象转换为其内容的实际类型,但在编译时我不知道类型。有没有办法拥有这样的功能?
def castIterable[IN, OUT](iterable: Iterable[IN]): Iterable[OUT] = {
iterable.asInstanceOf[Iterable[ figureOutContentType(...) ]]
}
答:
不能在类型系统中表示未知的类型,因为它在运行时会有所不同。充其量,你可以表达你知道它存在并且某些东西将与它属于同一类型的知识。
这就是泛型和存在类型开始发挥作用。
泛型是表达通用量化的类型系统方式:对于每个类型 X,都遵循以下内容。 是构造的证明,在没有任何假设的情况下 - 除了上限和下限以及一个值 - 可以在不知道的情况下构造主体。在里面,你只知道它的存在,而不知道其他的!在外部,您可以将其指定为所需的任何内容,并获得具体的实现。def method[A >: L <: U](a: A) = ...
A
A
method
A
存在类型是一种类型,你知道该类型存在,正是因为你收到了它的实例。例如。它是存在量化的类型系统编码。它与泛型的不同之处在于,你没有此未知类型的句柄。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)的领域。在这种情况下,我建议做一些关于类型论、编译器、运行时和编译时反射的课程——只是为了对你试图做什么有一个充分的理解。这并不简单,它不可能在一个晚上被黑客攻击(除非你真的知道你在做什么),如果它不是出于教育目的,我建议尝试使用一些现有的工作,因为从现在开始只会更难。
评论
asInstanceOf
map
Int
String
Any