首页 文章

scala模式匹配函数 - 如何绕过类型擦除

提问于
浏览
4

我想模式匹配一个函数,问题是类型擦除 . 请注意,在下面的代码段中,尽管发出了警告,但发生了匹配,并且在此处发生了一个匹配 .

scala> def f1 = ()=>true
f1: () => Boolean

scala> val fl = f1
fl: () => Boolean = <function0>

scala>

scala> fl match {
     | case fp :Function0[Boolean] => 1
     | case _ => 2
     | }
res8: Int = 1

scala>

scala> fl match {
     | case fp :Function0[String] => 1
     | case _ => 2
     | }
<console>:11: warning: fruitless type test: a value of type () => Boolean cannot also be a () => String (but still might match its erasure)
              case fp :Function0[String] => 1
                       ^
res9: Int = 1

scala>

我能想出的是一个包含该功能的案例类 . 我得到了类型安全,请注意下面的错误 . But ,这首先是不优雅的,第二,我不是 . 我唯一的猜测是case类受编译器保护,并且只在运行时解析匹配

scala> case class FunctionWrapper(fn: ()=>Boolean)
defined class FunctionWrapper

scala> val fw = FunctionWrapper(fl)
fw: FunctionWrapper = FunctionWrapper(<function0>)

scala> def fs = ()=>"whatever"
fs: () => String

scala> val fws = FunctionWrapper(fs)
<console>:10: error: type mismatch;
 found   : () => String
 required: () => Boolean
       val fws = FunctionWrapper(fs)
                                 ^

scala> fw match {
     | case FunctionWrapper(f) => f()
     | case _ => false
     | }
res10: Boolean = true

总而言之,我想知道是否有一种优雅的模式匹配函数的方法,也许可以理解为什么上面的例子像他们那样做了

2 回答

  • 3

    简短的回答:你必须撤消擦除,用 TypeTag 来表示类型 .

    我不明白案例类如何强制执行类型,而模式匹配则不能 .

    因为您的case类没有类型参数 . 只删除泛型类型,这就是它被称为“部分擦除”的原因 .

    相关问题:Generic unapply method for different types of List . 以下代码与其中一个答案基本相同,但使用函数而不是列表:

    import scala.reflect.runtime.universe._
    
    def foo[A : TypeTag](a: A): Int = typeOf[A] match {
      case t if t =:= typeOf[Int => Int] => a.asInstanceOf[Int => Int](0)
      case t if t =:= typeOf[Boolean => Int] => a.asInstanceOf[Boolean => Int](true)
      case _ => 3
    }
    
    foo((i: Int) => i + 1)
    // res0: Int = 1
    
    foo((b: Boolean) => if (b) 2 else 0)
    // res1: Int = 2
    
    foo((b: Boolean) => !b)
    // res2: Int = 3
    

    我不确定是否有办法编写一个提取器来使匹配块更好 .

    如果您需要以丢失静态类型信息的方式传递这些函数(将它们推送到 Function[_, _] 的集合中,然后将其用作Akka消息等),那么您还需要传递标记:

    import scala.reflect.runtime.universe._
    
    case class Tagged[A](a: A)(implicit val tag: TypeTag[A])
    
    def foo[A, B](tagged: Tagged[A => B]): Int = tagged.tag.tpe match {
      case t if t =:= typeOf[Int => Int] => tagged.a.asInstanceOf[Int => Int](0)
      case t if t =:= typeOf[Boolean => Int] => tagged.a.asInstanceOf[Boolean => Int](true)
      case _ => 3
    }
    foo(Tagged((i: Int) => i + 1))
    // res0: Int = 1
    
    foo(Tagged((b: Boolean) => if (b) 2 else 0))
    // res1: Int = 2
    
    foo(Tagged((b: Boolean) => !b))
    // res2: Int = 3
    
  • 4

    这里的警告实际上是双重的:

    1)首先,"a value of type () => Boolean cannot also be a () => String":事实上你正在匹配 () => Boolean 并且它永远不会同时 () => String 所以情况没有意义,并且理想世界永远不应该匹配 . 然而,第二部分提示,擦除开始发挥作用

    2)"(but still might match its erasure)":这里的擦除意味着 () => Boolean (又名 Function0[Boolean] )的实例和 () => String (又名 Function0[String] )的实例在运行时表示完全相同 . 因此,没有办法区分它们,当你模式匹配 Function0[String] 实际上编译器只能告诉它是 some Function0 但不知道它是 Function0[Boolean] 还是 Function0[String] .

    在这里很容易错过警告的第二部分 . 如果 fl 被输入 Any ,警告的第一部分将不适用,您将获得更有用的消息:

    scala> (fl:Any) match {
         |   case fp :Function0[Boolean] => 1
         |   case _ => 2
         | }
    <console>:11: warning: non-variable type argument Boolean in type pattern () => Boolean is unchecked since it is eliminated by erasure
                case fp :Function0[Boolean] => 1
    

    至于解决方案,除了确实包装函数实例之外,你几乎无能为力 . 幸运的是,您不需要为每种可能的返回类型编写一个特定的包装器 . Scala提供 ClassTagTypeTag 来解决擦除问题,我们可以通过将其存储在(通用)函数包装器中来利用它 . 然而,它仍然是相当麻烦的使用,并且在不安全方面是错误的,因为你必须匹配存储在包装器内的 ClassTag / TypeTag ,并且(直接通过 asInstanceOf 或通过相同的模式匹配间接)功能到相应的功能类型 .

相关问题