用提取器替换案例类继承,保留 Scala 中的详尽检查
我有一个简单的类层次结构,它表示一个类似图形的结构,其中具有使用案例类实现的几种不同类型的顶点:
sealed trait Node
sealed abstract case class Vertex extends Node
case class Arc extends Node
case class VertexType1 (val a:Int) extends Vertex
case class VertexType2 (val b:Int) extends Vertex
这允许我编写像这样的匹配块:
def test (x: Node) = x match {
case _ : Arc => "got arc"
case _ : Vertex => "got vertex"
}
或像这样:
def test (x: Node) = x match {
case _ : Arc => "got arc"
case c : Vertex => c match {
case _ : VertexType1(a) => "got type 1 vertex " + a
case _ : VertexType2(a) => "got type 2 vertex " + a
}
}
请注意,此实现具有以下属性:
1)它允许编写区分弧和顶点的匹配块,但不能区分特定顶点类型,但也允许编写区分顶点类型的匹配块。
2) 在特定于顶点类型和非特定于顶点类型的匹配块中,检查模式匹配的详尽性。
但是,不推荐从案例类继承,编译器建议使用提取器来支持非叶节点上的匹配(即,在上面的示例中,区分弧和顶点,但不区分顶点类型)。
问题:是否可以在不使用案例类继承的情况下实现类似的类层次结构,但仍然在上面显示的两个用例中由编译器执行模式详尽性检查?
编辑:我已向 VertexType 类添加了一个构造函数参数,以便不仅仅对类型执行匹配。
我当前没有案例类的实现如下:
sealed trait Node
sealed abstract class Vertex extends Node
class Arc extends Node
class VertexType1 (val a:Int) extends Vertex
class VertexType2 (val b:Int) extends Vertex
object VertexType1 {
def unapply (x : VertexType1) : Some[Int] = Some(x.a)
}
object VertexType2 {
def unapply (x : VertexType2) : Some[Int] = Some(x.b)
}
测试代码:
def test (x: Node) = x match {
case _ : Arc => "got arc"
case v : Vertex => v match {
case VertexType1(a) => "got vertex type 1 " + a
}
}
我期望在第二个块中出现关于非详尽匹配的警告(VertexType2 从未匹配),但没有。
实际上,2.9.0-RC3 之前的 Scala 编译器会产生我希望看到的警告,但以 RC3 开头的版本(包括 2.9.0 和 2.9.0-1)却不会,这相当令人困惑。
I have a simple class hierarchy that represents a graph-like structure with several distinct types of vertexes implemented using case classes:
sealed trait Node
sealed abstract case class Vertex extends Node
case class Arc extends Node
case class VertexType1 (val a:Int) extends Vertex
case class VertexType2 (val b:Int) extends Vertex
This allows me to write match blocks like this:
def test (x: Node) = x match {
case _ : Arc => "got arc"
case _ : Vertex => "got vertex"
}
or like this:
def test (x: Node) = x match {
case _ : Arc => "got arc"
case c : Vertex => c match {
case _ : VertexType1(a) => "got type 1 vertex " + a
case _ : VertexType2(a) => "got type 2 vertex " + a
}
}
Note that this implementation has the following properties:
1) It allows writing match blocks that differentiate between arcs and vertices, but not between specific vertex types, but also match blocks that do differentiate between vertex types.
2) In both vertex-type-specific and non-vertex-type-specific match blocks the exhaustiveness of pattern matching is checked.
However, inheritance from case classes is deprecated, and the compiler suggests to use extractors instead to support matching on non-leaf nodes (i.e., in the above example, to differentiate between arcs and vertices, but not between vertex types).
The question: is it possible to implement a similar class hierarchy without using case class inheritance, but still having pattern exhaustiveness checks performed by the compiler in both use cases shown above?
EDIT: I have added a constructor parameter to the VertexType classes so that the match is not performed only on types.
My current implementation without the case classes is as follows:
sealed trait Node
sealed abstract class Vertex extends Node
class Arc extends Node
class VertexType1 (val a:Int) extends Vertex
class VertexType2 (val b:Int) extends Vertex
object VertexType1 {
def unapply (x : VertexType1) : Some[Int] = Some(x.a)
}
object VertexType2 {
def unapply (x : VertexType2) : Some[Int] = Some(x.b)
}
And the test code:
def test (x: Node) = x match {
case _ : Arc => "got arc"
case v : Vertex => v match {
case VertexType1(a) => "got vertex type 1 " + a
}
}
I expect a warning about non-exhaustive match in the second block (VertexType2 is never matched), but there isn't one.
Actually, Scala compilers before 2.9.0-RC3 produce a warning that I expect to see, but versions starting with RC3 (including 2.9.0 and 2.9.0-1) do not, which is rather confusing.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
一般来说,这是无法做到的。
密封类是一种特殊情况(没有双关语),因为 scalac 在编译时知道可能有多少个匹配。
但是,由于提取器允许您运行任意代码,并且由于该死的停止问题,编译器无法保证在每种情况下您都会检查每种情况。考虑一下:
这并不详尽,因为它不处理不是 8 倍数的数字,但编译器无法推断出(不在所有
Int
上运行提取器)仅某些类型的值Int
是 8 的倍数。In general, this can't be done.
Sealed classes are a special case (no pun intended) because
scalac
knows at compile time how many matches are possible.But since extractors allow you to run arbitrary code and because of the damnable halting problem, there's no way for the compiler to guarantee in every case that you'll check every case. Consider:
This is not exhaustive because it doesn't handle numbers that aren't multiples of 8, but the compiler cannot deduce (without running your extractor on all
Int
's) that only some values of typeInt
are multiples of 8.提取器使您可以像 scala 中的 case 类一样在模式匹配中使用它,但它们没有使用 case 修饰符时获得的其他标准实现。
但是这些额外的实现(尤其是 equals 的实现)使得案例类继承变得危险,因此它被弃用了。
然而,密封类是一个正交功能,无论您是否有案例类或提取器,您都可以使用它们。通过使用提取器,您不会即时获得标准实现,但因此您可以继承提取器 - 它们只是带有 unapply 和/或 unapplySeq 方法的普通类。
您在示例中所做的称为类型的模式匹配。如果您只想这样做,则不需要案例类和提取器。您可以在您想要的每一堂课上进行。所以你可以简单地删除大小写修饰符。
因此得出结论:模式的详尽性是通过密封类 hiracies 实现的。模式匹配是通过提取器和案例类来实现的,后者只是一个提取器,具有常用功能的吸引人的标准实现。
我希望这有帮助。
extractors give you the possibility to use it in pattern matching like case classes in scala, but they don´t have other standard implementations you get when using the case modifier.
But hust these extra implementation(especially the implementation of equals) is what makes case class inheritance dangerous and so it got deprecated.
However sealed classes are an orthogonal feature and you can use them whether or not you have a case class or an extractor. By using extractors you don´t get standard implementations on the fly but thus you can have inheritance of extractors - they are simply normal classes with an unapply and/or unapplySeq method.
What You´ve done in your example is called pattern-matching on types. If you only want to do this, you don´t need case classes neither extractors. You can do it with every class you want. So you can simply remove the case modifier.
So to conclude: exhaustiveness of pattern is achieved by sealed class hirachies. Pattern matching is achieved by extractors and case classes of what the latter is just an extractor with appealing standard implementations of frequently used functions.
I hope this helped.
来自 scala-lang.org 的引用:
The citation from scala-lang.org: