我们可以优雅地匹配 Scala 中的擦除类型吗? [英] Can we elegantly match an erased type in scala?
问题描述
有没有什么优雅的方式,从:
Is there any elegant way, to arrive from:
def foo[T: TypeTag](a: A[T]) {
// can we match on the type of T here?
}
在 T 类型的匹配表达式中?
at a match expression on the type of T?
显然,下面的这并不能克服 T
的擦除问题,所以我们必须手动检查 TypeTag 吗?
Obviously this below does not overcome erasure for T
, so must we inspect the TypeTag by hand?
a match {
case _:A[SomeSpecificType] => ...
或者,Scala 是否为此提供了一些优雅?
Or does scala afford some elegance to that end?
推荐答案
遗憾的是没有,因为如果向模式添加类型检查,编译器不会考虑类型标记.我不确定为什么以及是否计划这样做.但是,您可以比较类型标签的相等性:
Sadly no, as the compiler does not take type tags into account if you add type checks to a pattern. I'm not sure why and whether this is planned. You can however compare type tags for equality:
typeOf[T] =:= typeOf[List[String]]
您可以在 if 或 match 条件中使用它,然后转换为目标类型.
You can use that in an if or match condition and then cast to the target type.
仔细考虑之后,我意识到编写自己的模式提取器会很容易,它隐藏了检查和转换:
After thinking a bit more about it, I recognized that it would be quite easy to write my own pattern extractor, that hides the check and cast:
import scala.reflect.runtime.universe._
class TypeTest[A: TypeTag]() {
def unapply[B: TypeTag](v: B): Option[A] =
if(typeOf[B] <:< typeOf[A])
Some(v.asInstanceOf[A])
else
None
}
object TypeTest {
def apply[A: TypeTag] = new TypeTest()
}
现在,我们可以做这样的事情:
Now, we can do stuff like this:
def printIfStrings[T: TypeTag](v: T) {
val string = TypeTest[List[String]]
v match {
case string(s) => printString(s)
case _ =>
}
}
def printString(s: List[String]) {
println(s)
}
printIfStrings(List(123))
printIfStrings(List("asd"))
这已经很简洁了,但是由于 Scala 不支持在模式中直接将参数传递给提取器,因此我们必须在匹配表达式之前将所有提取器定义为 val string
.
This is already quite neat, but as Scala does not support passing an argument directly to an extractor in a pattern, we have to define all extractors as val string
before the match expression.
宏可以转换代码,因此将匹配表达式中任何未检查的类型检查转换为适当的模式或直接使用类型标记添加显式检查应该很容易.
Macros can transform code, so it should be easy enough to transform any unchecked typechecks in a match expression into an appropriate pattern or add explicit checks using the type tags directly.
然而,这要求我们在每个关键匹配表达式周围都有一个宏调用,这将非常难看.另一种方法是用一些将部分函数作为参数的方法调用替换匹配表达式.可以使用隐式转换为任意类型提供此方法.
This however requires that we have a macro invocation wrapped around every critical match expression, which would be quite ugly. An alternative is to replace match expressions by some method call that takes a partial function as an argument. This method can be provide for an arbitrary type using an implicit conversion.
剩下的唯一问题是编译器在调用任何宏之前对代码进行类型检查,因此即使现在检查了未经检查的强制转换,它也会为未检查的强制转换生成警告.我们仍然可以使用 @unchecked
来抑制这些警告.
The only remaining problem then is that the compiler typechecks the code before any macros are invoked, so it will generate a warning for the unchecked cast even though it is now checked. We can still us @unchecked
to suppress these warnings.
我选择通过上述提取器替换模式中的类型检查,而不是向案例和显式类型转换添加条件.原因是这个转换是局部的(我只需要用另一个子表达式替换一个子表达式).
I chose to replace type checks in patterns by the extractor described above instead of adding a condition to the case and explicit type casts. The reason for that is that this transformation is local (I just have to replace a subexpression with another).
这里是宏:
import scala.language.experimental.macros
import scala.language.implicitConversions
import scala.reflect.macros.blackbox.Context
object Switch {
implicit class Conversion[A](val value: A) {
def switch[B](f: PartialFunction[A, B]): B = macro switchImpl
}
def switchImpl(c: Context)(f: c.Tree): c.Tree = {
import c.universe._
val types = collection.mutable.Map[Tree,String]()
val t1 = new Transformer {
override def transformCaseDefs(trees: List[CaseDef]) = {
val t2 = new Transformer {
override def transform(tree: Tree) = {
def pattern(v: String, t: Tree) = {
val check = types.getOrElseUpdate(t, c.freshName())
pq"${TermName(check)}(${TermName(v)})"
}
tree match {
case Bind(TermName(v),Typed(Ident(termNames.WILDCARD),
Annotated(Apply(
Select(New(Ident(TypeName("unchecked"))),
termNames.CONSTRUCTOR), List()
), t)))
=> pattern(v,t)
case Bind(TermName(v),Typed(Ident(termNames.WILDCARD),t))
=> pattern(v,t)
case _ => super.transform(tree)
}
}
}
t2.transformCaseDefs(trees)
}
}
val tree = t1.transform(c.untypecheck(f))
val checks =
for ((t,n) <- types.toList) yield
q"val ${TermName(n)} = Switch.TypeTest[$t]"
q"""
..$checks
$tree(${c.prefix}.value)
"""
}
import scala.reflect.runtime.universe._
class TypeTest[A: TypeTag]() {
def unapply[B: TypeTag](v: B): Option[A] =
if(typeOf[B] <:< typeOf[A]) Some(v.asInstanceOf[A])
else None
}
object TypeTest {
def apply[A: TypeTag] = new TypeTest()
}
}
现在神奇地键入检查模式工作:
And now magically type checks in patterns work:
import Switch.Conversion
val l = List("qwe")
def printIfStrings2[T: scala.reflect.runtime.universe.TypeTag](v: T) {
v switch {
case s: Int => println("int")
case s: List[String] @unchecked => printString(s)
case _ => println("none")
}
}
printIfStrings2(l)
printIfStrings2(List(1, 2, 3))
printIfStrings2(1)
我不确定我是否正确处理了所有可能的情况,但我尝试过的每件事都很好.带有多个注释的类型如果也被@unchecked 注释,则可能无法正确处理,但我在标准库中找不到用于测试的示例.
I'm not sure whether I handle all possible cases correctly, but every thing I tried worked fine. A type with multiple annotations is possibly not handled correctly if it is also annotated by @unchecked, but I couldn't find an example in the standard library to test this.
如果省略@unchecked,结果完全相同,但如上所述,您将收到编译器警告.我看不到使用普通宏消除该警告的方法.也许注解宏可以做到,但它们不在 Scala 的标准分支中.
If you leave out the @unchecked the result is exactly the same, but as mentioned above you will get a compiler warning. I don't see a way to get rid of that warning with normal macros. Maybe annotation macros can do it but they are not in the standard branch of Scala.
这篇关于我们可以优雅地匹配 Scala 中的擦除类型吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!