Scala中依赖类型参数的模式匹配的类型安全性

f87krz0w  于 2022-11-09  发布在  Scala
关注(0)|答案(1)|浏览(249)

我目前正在使用一个表示树的类型层次结构和一个附带的类型层次结构,该类型层次结构表示该树中的根到节点路径的步骤。有不同类型的节点,因此可以在每个节点上采取不同的步骤。因此,节点类型有一个类型成员,该成员被设置为包括所有有效步骤的特征。举个例子:

// Steps
sealed trait Step
sealed trait LeafStep extends Step
sealed trait HorizontalStep extends Step
sealed trait VerticalStep extends Step
object Left extends HorizontalStep
object Right extends HorizontalStep
object Up extends VerticalStep
object Down extends VerticalStep

// The Tree
sealed trait Tree {
  type PathType <: Step
}
case class Leaf() extends Tree {
  override type PathType = LeafStep
}
case class Horizontal(left: Tree, right: Tree) extends Tree {
  override type PathType = HorizontalStep
}
case class Vertical(up: Tree, down: Tree) extends Tree {
  override type PathType = VerticalStep
}

在本例中,给定一棵树,路径Seq(Up, Right)将告诉我们转到根的“up”子节点的“右”子节点(假设树的节点具有适合的类型)。当然,导航树涉及使用PartialFunction的一组代码,本例中没有显示这些代码。但是,在该过程中,我希望提供一个类型安全的回调,该回调在采取的每个步骤中都会得到通知,其参数包括该步骤和相应的树节点。
我目前的方法是函数def callback(from: Tree, to: Tree)(step: from.PathType)。这在调用者端没有问题,但在实际实现这样一个处理传递给它的数据的回调时遇到了一个问题。

def callback(from: Tree, to: Tree)(step: from.PathType) = {
  from match {
    case f: Horizontal => step match {
      case Left => ??? // do useful stuff here
      case Right => ??? // do other useful stuff here
    }
    case _ => ()
  }
}

在该函数中,编译器不确定LeftRight属于from.PathType类型。当然,我可以简单地添加.asInstanceOf[f.PathType],代码似乎也可以这样做。然而,外部匹配告诉我们fHorizontal类型。我们知道Horizontal对象中的PathTypeHorizontalStep,由于ffrom相同,我们还知道from.PathType属于同一类型。最后,我们可以检查LeftRight都扩展了HorizontalStep。因此,即使没有强制转换,上面的代码也应该始终是类型安全的。
这是不是Scala编译器不检查的推理,或者我错过了类型可能不同的情况?有没有更好的方法来实现我的类型安全回调目标?我使用的是Scala 2.12.15

zsohkypk

zsohkypk1#

恐怕我没有详细的解释为什么这不是打字检查。(对于这种依赖类型的参数,编译器似乎无法将从模式匹配中获得的知识应用到另一个参数上(该参数没有明确匹配))。
然而,这里有一些可行的方法:
首先,使用类型参数而不是类型成员:

// (Steps as above)
// The Tree
sealed trait Tree[PathType <: Step]

// type alias to keep parameter lists simpler
// where we do not care..
type AnyTree = Tree[_ <: Step]

case class Leaf() extends Tree[LeafStep]

case class Horizontal(left: AnyTree, right: AnyTree) extends Tree[HorizontalStep]

case class Vertical(up: AnyTree, down: AnyTree) extends Tree[VerticalStep]

然后,这将完全检查类型:

def callback[S <: Step, F <: Tree[S]](from: F, to: AnyTree)(step: S) = {
  def horizontalCallback(from: Horizontal)(step: HorizontalStep) = {
    step match {
      case Left => ??? // do useful stuff here
      case Right => ??? // do other useful stuff here
    }
  }
  from match {
    case f: Horizontal =>
      horizontalCallback(f)(step)
    case _ =>
      ()
  }
}

令人困惑的是,如果将模式匹配直接放在外部匹配中,编译器将无法正确检查步骤上的模式匹配,如下所示(编辑:这只适用于2.12,其中这不会给出“匹配可能不是穷举”的警告-对于2.13或3.2,这是可以检查的):

def callback[S <: Step, F <: Tree[S]](from: F, to: AnyTree)(step: S) = {
  from match {
    case f: Horizontal =>
      step match {
        case Left => ??? // do useful stuff here
        case Right => ??? // do other useful stuff here
      }
    case _ =>
      ()
  }
}

相关问题