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

Function scala模式匹配函数-如何绕过类型擦除,function,scala,pattern-matching,scala-2.11,Function,Scala,Pattern Matching,Scala 2.11,我想模式匹配一个函数,问题是类型擦除。请注意,在下面的代码片段中,尽管发出了警告,但仍会出现匹配,并且出现“错误”匹配 scala> def f1 = ()=>true f1: () => Boolean scala> val fl = f1 fl: () => Boolean = <function0> scala> scala> fl match { | case fp :Function0[Boolean] =>

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

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>
scala>def f1=()=>true
f1:()=>布尔值
scala>val fl=f1
fl:()=>布尔=
斯卡拉>
scala>fl匹配{
|案例fp:Function0[布尔]=>1
|案例=2
| }
res8:Int=1
斯卡拉>
scala>fl匹配{
|案例fp:Function0[字符串]=>1
|案例=2
| }
:11:警告:无结果类型测试:类型为()=>Boolean的值也不能是()=>String(但仍可能与其擦除匹配)
案例fp:Function0[字符串]=>1
^
res9:Int=1
斯卡拉>
我能想到的是一个包装函数的case类。我得到了类型安全,请注意下面的错误但是,首先,这是不雅观的,其次,我不理解case类如何强制类型,而模式匹配不能。我唯一的猜测是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
scala>case类FunctionWrapper(fn:()=>Boolean)
定义的类函数包装器
scala>val fw=FunctionWrapper(fl)
fw:FunctionWrapper=FunctionWrapper()
scala>def fs=()=>“随便什么”
fs:()=>字符串
scala>val fws=FunctionWrapper(fs)
:10:错误:类型不匹配;
找到:()=>字符串
必需:()=>布尔值
val fws=FunctionWrapper(fs)
^
scala>fw匹配{
|大小写函数包装器(f)=>f()
|大小写=>false
| }
res10:Boolean=true

总而言之,我想知道是否有一种优雅的方式来匹配函数的模式,也许可以理解为什么上面的示例会像它们那样运行

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

1) 首先,“类型为()=>布尔的值也不能是()=>字符串”:事实上,您正在与
()=>布尔值进行匹配,并且它不能同时是
()=>字符串,因此这种情况没有意义,在理想世界中永远不应该匹配。然而,擦除开始发挥作用,因为第二部分暗示

2) “(但仍可能匹配其擦除)”:此处的擦除意味着
()=>Boolean
(又名
Function0[Boolean]
)的实例和
()=>String
(又名
Function0[String]
)的实例在运行时表示完全相同。因此,无法区分它们,当您与
Function0[String]
进行模式匹配时,编译器实际上只能分辨出它是某些
Function0
,但无法知道它是
Function0[Boolean]
还是
Function0[String]

令人钦佩的是,警告的第二部分很容易在这里漏掉。 如果键入
fl
任何
,警告的第一部分将不适用,您将得到一条更有用的消息:

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>(fl:Any)匹配{
|案例fp:Function0[布尔]=>1
|案例=2
| }
:11:警告:类型模式()中的非变量类型参数Boolean=>Boolean未选中,因为它已通过擦除消除
案例fp:Function0[布尔]=>1

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

简短的回答是:您必须撤消擦除操作,并使用
TypeTag
对类型进行具体化

我不明白case类如何强制类型,而模式匹配却不能

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

相关问题:。以下代码与其中的一个答案基本相同,但使用函数而不是列表:

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
我不确定是否有办法编写一个提取器,使匹配块更好

如果您需要以丢失静态类型信息的方式传递这些函数(将它们推入
函数[\uu,\u]
的集合中,然后将其用作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
与函数本身打包(在包装器中),则可以区分模式中的实际类型。更新以演示如何使用包装器。无形状会有帮助吗?()