【问题标题】:Pattern matching structural types in ScalaScala 中的模式匹配结构类型
【发布时间】:2010-01-01 08:26:23
【问题描述】:

为什么会打印 wtf?模式匹配对结构类型不起作用吗?

  "hello" match {
    case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
    case _ => println("okie dokie")
  }

【问题讨论】:

    标签: scala pattern-matching structural-typing


    【解决方案1】:

    在 (scala -unchecked) 上使用未经检查的警告在 Scala 解释器中运行此示例会产生以下警告:warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure。不幸的是,这样的泛型类型无法在运行时检查,因为 JVM 没有具体化的泛型。

    JVM 在这个模式匹配中看到的只有:

    "hello" match {
      case s: Object => ... 
      case annon: Object => ...
    }
    

    编辑:针对您的 cmets,我一直在考虑解决方案,但昨天没有时间发布。不幸的是,即使它应该工作,编译器也无法注入正确的Manifest

    您要解决的问题是比较对象是否属于给定的结构类型。这是我一直在考虑的一些代码(Scala 2.8-r20019,因为 Scala 2.7.6.final 在我玩类似的想法时崩溃了几次)

    type Foo = AnyRef { def doesNotExist(i: Int, x: List[_]): Double }
    
    def getManifest[T](implicit m: Manifest[T]) = m
    
    def isFoo[T](x: T)(implicit mt: Manifest[T]) = 
      mt == getManifest[Foo]
    

    方法isFoo 基本上比较Foo 的类x 的清单。在理想世界中,结构类型的清单应该等于包含所需方法的任何类型的清单。至少这是我的思路。不幸的是,这无法编译,因为编译器在调用getManifest[Foo] 时注入了Manifest[AnyRef] 而不是Manifest[Foo]。有趣的是,如果您不使用结构类型(例如,type Foo = String),此代码将按预期编译和工作。我会在某个时候发布一个问题,看看为什么结构类型会失败——这是一个设计决定,还是只是实验性反射 API 的问题。

    如果做不到这一点,您始终可以使用 Java 反射来查看对象是否包含方法。

    def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = {
      try { 
        x.getClass.getMethod(name, params: _*)
        true
        }
      catch {
        case _ =>  false
      }
    }
    

    按预期工作:

    containsMethod("foo", "concat", classOf[String]) // true
    containsMethod("foo", "bar", classOf[List[Int]]) // false
    

    ...但不是很好。

    另外,请注意结构类型的结构在运行时不可用。如果你有一个方法def foo(x: {def foo: Int}) = x.foo,擦除后你会得到def foo(x: Object) = [some reflection invoking foo on x],类型信息会丢失。这就是为什么首先使用反射的原因,因为您必须在 Object 上调用一个方法,而 JVM 不知道 Object 是否具有该方法。

    【讨论】:

    • 谢谢,弗拉维。这回答了我的问题。但这仍然让我想知道实现这一目标的最佳方法是什么,因为该结构实际​​上是在运行时通过反射可用的东西。这样做很笨拙。
    【解决方案2】:

    如果你必须使用反射,你至少可以使用提取器让它看起来更好:

    object WithFoo {
        def foo(){
            println("foo was called")
        }
    }
    
    object HasFoo {
        def containsMethod(x: AnyRef, name: String, params: Array[java.lang.Class[_]]) : Boolean = {
            try { 
                x.getClass.getMethod(name, params: _*)
                true
            } catch {
                case _ => false
            }
        }
    
        def unapply(foo:AnyRef):Option[{def foo():Unit}] = {
            if (containsMethod(foo, "foo", new Array[Class[_]](0))) {
                Some(foo.asInstanceOf[{def foo():Unit}])
            } else None
        }
    }
    
    
    WithFoo.asInstanceOf[AnyRef] match {
        case HasFoo(foo) => foo.foo()
        case _ => println("no foo")
    }
    

    【讨论】:

    • 如果能更灵活地定义HasFoo就好了,比如val HasFoo = new Has[{def foo():Unit}]("foo")。我只是尝试这样做,但是对于更复杂的类型,例如{def foo(i:Int):Int},似乎仍然存在一些问题。
    • 为什么编译器不自动执行此操作?
    • containsMethod可以脱水成Try(x.getClass.getMethod(name, params: _*)).isSuccess
    猜你喜欢
    • 2020-03-18
    • 2020-08-09
    • 1970-01-01
    • 2018-01-03
    • 2013-03-17
    • 2014-03-05
    • 2017-08-18
    • 2011-08-11
    相关资源
    最近更新 更多