【问题标题】:scala pattern match a function - how to get around type erasurescala模式匹配一​​个函数 - 如何绕过类型擦除
【发布时间】:2015-11-21 16:19:11
【问题描述】:

我想对一个函数进行模式匹配,问题是类型擦除。请注意在下面的 sn-p 中,尽管发出了 警告,但仍会发生匹配并且出现“错误”匹配。

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>

我能想出的是一个封装函数的案例类。我得到类型安全,请注意下面的错误。 但是,首先,这是不优雅的,其次,我不明白 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

总结一下,我想知道是否有一种优雅的方式来模式匹配一​​个函数,也许能理解上面的例子为什么会这样

【问题讨论】:

标签: function scala pattern-matching scala-2.11


【解决方案1】:

简短的回答:您必须撤消擦除,并使用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

【讨论】:

  • 这种使用TypeTag 的方式的问题是您必须始终保留确切的类型。假设您有一个存储在列表中的函数列表,并且所有编译器都不知道函数的确切类型是什么,并且您对它们调用 foo 不会达到您的预期。因此,与没有擦除的情况相比,您在模式匹配方面仍然受到严重限制。如果您将 TypeTag 与函数本身(在包装器中)打包在一起,那么您可以区分模式中的实际类型。
  • 更新以演示使用包装器。
【解决方案2】:

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

1) 首先,“() => Boolean 类型的值也不能是 () => String”:确实,您匹配的是 () =&gt; Boolean,它永远不能同时是 () =&gt; String所以这种情况没有意义,在理想世界中永远不应该匹配。然而,擦除开始发挥作用,因为第二部分暗示了

2) “(但仍可能匹配其擦除)”:此处的擦除意味着() =&gt; Boolean(又名Function0[Boolean])的实例和() =&gt; String(又名Function0[String])的实例在运行时表示完全相同.因此,没有办法区分它们,当您对Function0[String] 进行模式匹配时,编译器实际上只能判断它是some Function0,但无法知道它是Function0[Boolean] 还是@987654330 @。

诚然,警告的第二部分在这里很容易被忽略。 如果将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或间接通过相同的模式匹配)函数到相应的函数类型。

【讨论】:

  • 谢谢,这有助于澄清一点,但你仍然没有提出解决方案,或者,反过来说没有一个......
  • 我扩展了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 2018-04-12
  • 2015-10-06
  • 2016-01-24
  • 2017-11-23
  • 2013-09-07
  • 1970-01-01
相关资源
最近更新 更多