Scala - 将传递的参数与递归函数中传递的类型参数进行匹配

时间:2012-09-28 09:20:42

标签: scala pattern-matching type-systems

我有一个节点类,它可以指向另一个节点(通过next)。该节点可以在不同的层次结构中进行子类化。然后,我可以在单链表的图像中拥有这些不同节点的链。然后,从该列表中的任意节点开始,我想搜索特定类型的第一个节点。因此,我为此创建了一个类型参数化函数check。但它无法找到正确的匹配。以下是完整的输出代码示例,取自Scala Worksheet:

class A (i: Int) {
    var next: Option[A] = None
    def + (a: A) = {
      next = Some(a)
      a
    }
    override def toString = super.toString + "[" + i + "]"
}
class B (i: Int) extends A (i)
class C (i: Int) extends B (i)
class D (i: Int) extends B (i)

object test {

    val start = (new A(0))                    //> start  : A = A@e80a59[0]
    val test = start + (new A(1)) + (new B(2)) + (new C(3)) + (new D(4)) + (new A(5)) + (new C(6))
                                                  //> test  : A = C@5d173[6]

    def check[T <: B](a: A): Option[B] = {
        println("starting search for " + a)
        a match {
          case t: T =>
            println("SUCCESS, found=" + t)
            Some(t)
          case wrong =>
            println("did not find! wrong=" + wrong)
            a.next match {
            case Some(nxt) =>
                println("checking next=" + nxt)
                check[T](nxt)
            case _ =>
                println("SEARCH FAILED")
                None
          }
        }
    }                                         //> check: [T <: B](a: A)Option[B]

    /* correct - i expect this */
    println(check[C](new A(1)))               //> starting search for A@1f9dc36[1]
                                                  //| did not find! wrong=A@1f9dc36[1]
                                                  //| SEARCH FAILED
                                                  //| None
    /* correct - i expect this */
    println(check[D](new A(2)))               //> starting search for A@e86da0[2]
                                                  //| did not find! wrong=A@e86da0[2]
                                                  //| SEARCH FAILED
                                                  //| None
    /* incorrect - it must fail looking for C */
    println(check[C](new B(4)))               //> starting search for B@1754ad2[4]
                                                  //| SUCCESS, found=B@1754ad2[4]
                                                  //| Some(B@1754ad2[4])
    println(check[B](start))                  //> starting search for A@e80a59[0]
                                                  //| did not find! wrong=A@e80a59[0]
                                                  //| checking next=A@fe64b9[1]
                                                  //| starting search for A@fe64b9[1]
                                                  //| did not find! wrong=A@fe64b9[1]
                                                  //| checking next=B@186db54[2]
                                                  //| starting search for B@186db54[2]
                                                  //| SUCCESS, found=B@186db54[2]
                                                  //| Some(B@186db54[2])
    /* incorrect - it must find C(3) instead */
    println(check[C](start))                  //> starting search for A@e80a59[0]
                                                  //| did not find! wrong=A@e80a59[0]
                                                  //| checking next=A@fe64b9[1]
                                                  //| starting search for A@fe64b9[1]
                                                  //| did not find! wrong=A@fe64b9[1]
                                                  //| checking next=B@186db54[2]
                                                  //| starting search for B@186db54[2]
                                                  //| SUCCESS, found=B@186db54[2]
                                                  //| Some(B@186db54[2])
    /* incorrect - it must find D(4) instead */
    println(check[D](start))                  //> starting search for A@e80a59[0]
                                                  //| did not find! wrong=A@e80a59[0]
                                                  //| checking next=A@fe64b9[1]
                                                  //| starting search for A@fe64b9[1]
                                                  //| did not find! wrong=A@fe64b9[1]
                                                  //| checking next=B@186db54[2]
                                                  //| starting search for B@186db54[2]
                                                  //| SUCCESS, found=B@186db54[2]
                                                  //| Some(B@186db54[2])

问题是:

  1. 为什么会这样失败?它忽略了传递的类型。

  2. 我怎样才能实现我的目标?

  3. 更新:正如我所说的那样,我尝试使用Scala“清单”来对抗所谓的“类型擦除”,通过执行此手动显式验证。但是,我无法在清单的帮助下进行模式匹配 - 它根本无法以我能想到的方式使用它,也无法在网上找到任何有用的示例。

2 个答案:

答案 0 :(得分:1)

以下似乎有效。不知道还有什么可说的,因为我不知道还有什么其他(可能更好)的方法来实现你想做的事情。我研究了Manifest的API一段时间才想出来。此外,我不知道这是否适用于'ClassManifest'。

并注意子类型,因为我认为这段代码会失败。

scala> trait A
defined trait A        
scala> case class A1() extends A
defined class A1    
scala> case class A2() extends A
defined class A2

scala> def check[T <: A](a: A)(implicit m: Manifest[T]) = a.getClass == m.erasure
check: [T <: A](a: A)(implicit m: Manifest[T])Boolean

scala> check[A1](A2())
res7: Boolean = false
scala> check[A2](A2())
res8: Boolean = true

答案 1 :(得分:1)

首先,我建议你将任务分成:

  1. 搜索具有特定属性的节点,
  2. 将属性指定为某个类的子类。
  3. 例如,Scala集合有方法

    def collectFirst[B](pf: PartialFunction[A, B]): Option[B]
    

    找到定义函数的第一个元素并将函数应用于它。在您的情况下,您将在节点上使用部分函数而不是元素。

    然后,您可以创建一个函数来创建给定(隐式)清单的部分函数:

    def subclass[A <: AnyRef](implicit m: Manifest[A]): PartialFunction[Any,A] = {
      // Works properly only for arguments that have non-polymorphic types!
      case x if m.erasure.isInstance(x)  => x.asInstanceOf[A]
    }
    

    (请注意,这不是防弹:只有在赋予部分函数的参数不是多态的情况下才能正常工作。原因是我们通常没有可用于参数的清单传递给它的只有它们的类型擦除类。但由于你的所有节点类都不是多态的,所以这不应该是一个问题。)

    通过组合这两个功能,您应该得到所需的结果。


    我将为您提供适合您案例的Scala集合的完整示例:

    import scala.reflect._
    
    object Test extends App {
      // Scala collections have this method:
      // def collectFirst[B](pf: PartialFunction[A, B]): Option[B]
    
      def subclass[A <: AnyRef](implicit m: Manifest[A]): PartialFunction[Any,A] = {
        // Works properly only for arguments that have non-polymorphic types!
        case x if m.erasure.isInstance(x)  => x.asInstanceOf[A]
      }
    
      def collectFirst[T <: AnyRef](xs: Seq[AnyRef])(implicit m: Manifest[T]) =
        xs.collectFirst(subclass[T]);
    
      val s: Seq[AnyRef] = Seq(
        new java.lang.Object(),
        "a",
        3 : java.lang.Integer,
        '@' : java.lang.Character
      );
    
      println(collectFirst[String](s));
      println(collectFirst[AnyRef](s));
      println(collectFirst[java.lang.Character](s));
      println(collectFirst[java.lang.Integer](s));
    }