Scala 中嵌套类型的模式匹配

发布于 2024-11-02 21:28:36 字数 3782 浏览 1 评论 0原文

我正在尝试在 Scala 中实现一个有效的枚举。我想使用案例类来做到这一点,以便编译器能够检测到任何非详尽的模式匹配。

这在非常基本的形式中工作得很好,例如:

sealed abstract class HorizontalAlignment
case object Left extends HorizontalAlignment
case object Right extends HorizontalAlignment
case object Center extends HorizontalAlignment
case object AsIs extends HorizontalAlignment
...
def test (x : HorizontalAlignment) = 
  x match {
    case Left => ...
    ...  
  } 

但是这并不理想,因为案例对象的名称很容易发生冲突:

sealed abstract class HorizontalAlignment
case object Left extends HorizontalAlignment
case object Right extends HorizontalAlignment
case object Center extends HorizontalAlignment
case object AsIs extends HorizontalAlignment

sealed abstract class VerticalAlignment
case object Top extends VerticalAlignment
case object Bottom extends VerticalAlignment
case object Center extends VerticalAlignment
case object AsIs extends VerticalAlignment

// "Center" and "AsIs" clash

明显的解决方案是将案例对象放入单独的命名空间中:

sealed abstract class HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

sealed abstract class VerticalAlignment {
  case object Top extends VerticalAlignment
  case object Bottom extends VerticalAlignment
  case object Center extends VerticalAlignment
  case object AsIs extends VerticalAlignment
}

但是如何在匹配块中引用这些类?

它们不能用 Java 风格的点引用:

def test (x : HorizontalAlignment) = 
x match {
  case HorizontalAlignment.Left => 0  //  error: not found: value HorizontalAlignment
}

“#”符号似乎也不起作用:

def test (x : HorizontalAlignment) = 
x match {
  case HorizontalAlignment#Left => 0 // error: '=>' expected but '#' found 
}

而且这种形式也不起作用:

def test (x : HorizontalAlignment) = 
x match {
  case _ : HorizontalAlignment#Left => 0  // error: type Left is not a member of Test.HorizontalAlignment
}

这是有道理的,因为“Left”在这种情况下是一个实例而不是类型,并且我怀疑有一种简单的方法来引用该类型。我能达到的最接近的目标是:

sealed abstract class HorizontalAlignment {
  case class Left extends HorizontalAlignment
  case class Right extends HorizontalAlignment
  case class Center extends HorizontalAlignment
  case class AsIs extends HorizontalAlignment

  object Left
  object Right
  object Center
  object AsIs

}

但是,尽管这使得匹配块编译得很好,但我找不到任何方法来实际引用这些对象,例如将此“枚举”的成员传递给函数。这是因为 Horizo​​ntalAlignment 是一种类型而不是对象,因此不可能使用字段访问引用其中一个嵌套对象,另一方面,这些对象不是类型,因此不可能使用“#“ 象征。

有没有办法从类外部引用嵌套在该类中的对象?

编辑

到目前为止,我发现包对象是解决这个问题的最好方法。

package object HorizontalAlignment  {
  sealed abstract class HorizontalAlignment
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

package object VerticalAlignment {
  sealed abstract class VerticalAlignment 
  case object Top extends VerticalAlignment 
  case object Bottom extends VerticalAlignment 
  case object Center extends VerticalAlignment 
  case object AsIs extends VerticalAlignment 
}


object Test {
  import HorizontalAlignment.HorizontalAlignment
  import VerticalAlignment.VerticalAlignment 

  def test (x : HorizontalAlignment, y : VerticalAlignment) =  {
    x match {
      case HorizontalAlignment.Left => ...
      ...
    }

    y match {
      case VerticalAlignment.Top => ...
      ...
    }
  }

  def testTest = test (HorizongalAlignment.Left, VerticalAlignment.Top)

然而

,上述问题(访问类中的嵌套对象)仍然存在。

I am trying to implement something that is effectively an enumeration in Scala. I would like to do that using case classes so that the compiler is able to detect any non-exhaustive pattern matches.

This works fine in the very basic form, e.g.:

sealed abstract class HorizontalAlignment
case object Left extends HorizontalAlignment
case object Right extends HorizontalAlignment
case object Center extends HorizontalAlignment
case object AsIs extends HorizontalAlignment
...
def test (x : HorizontalAlignment) = 
  x match {
    case Left => ...
    ...  
  } 

However this is not ideal as the names of the case objects can easily clash:

sealed abstract class HorizontalAlignment
case object Left extends HorizontalAlignment
case object Right extends HorizontalAlignment
case object Center extends HorizontalAlignment
case object AsIs extends HorizontalAlignment

sealed abstract class VerticalAlignment
case object Top extends VerticalAlignment
case object Bottom extends VerticalAlignment
case object Center extends VerticalAlignment
case object AsIs extends VerticalAlignment

// "Center" and "AsIs" clash

The obvious solution is to put the case objects into separate namespaces:

sealed abstract class HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

sealed abstract class VerticalAlignment {
  case object Top extends VerticalAlignment
  case object Bottom extends VerticalAlignment
  case object Center extends VerticalAlignment
  case object AsIs extends VerticalAlignment
}

But how to reference those classes in a match block?

They cannot be referenced with a Java-style dot:

def test (x : HorizontalAlignment) = 
x match {
  case HorizontalAlignment.Left => 0  //  error: not found: value HorizontalAlignment
}

The "#" symbol also does not seem to work:

def test (x : HorizontalAlignment) = 
x match {
  case HorizontalAlignment#Left => 0 // error: '=>' expected but '#' found 
}

And this form does not work either:

def test (x : HorizontalAlignment) = 
x match {
  case _ : HorizontalAlignment#Left => 0  // error: type Left is not a member of Test.HorizontalAlignment
}

This makes sense as "Left" is in this case an instance and not a type, and I suspect there is an easy way to refer to the type. The closest I could get to achieving that is this:

sealed abstract class HorizontalAlignment {
  case class Left extends HorizontalAlignment
  case class Right extends HorizontalAlignment
  case class Center extends HorizontalAlignment
  case class AsIs extends HorizontalAlignment

  object Left
  object Right
  object Center
  object AsIs

}

But although this makes the match block compile fine I could not find any way to actually refer to those objects, e.g. to pass a member of this "enumeration" to a function. This is because HorizontalAlignment is a type and not an object and therefore it is impossible to refer to one of the nested objects using field access, and, on the other hand, those objects are not types so it is impossible to refer to them using the "#" symbol.

Is there any way to refer to objects nested in a class from outside that class?

EDIT

So far I have found that the package objects are the best way to solve this problem.

package object HorizontalAlignment  {
  sealed abstract class HorizontalAlignment
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

package object VerticalAlignment {
  sealed abstract class VerticalAlignment 
  case object Top extends VerticalAlignment 
  case object Bottom extends VerticalAlignment 
  case object Center extends VerticalAlignment 
  case object AsIs extends VerticalAlignment 
}


object Test {
  import HorizontalAlignment.HorizontalAlignment
  import VerticalAlignment.VerticalAlignment 

  def test (x : HorizontalAlignment, y : VerticalAlignment) =  {
    x match {
      case HorizontalAlignment.Left => ...
      ...
    }

    y match {
      case VerticalAlignment.Top => ...
      ...
    }
  }

  def testTest = test (HorizongalAlignment.Left, VerticalAlignment.Top)

}

However, the above question (access to nested objects in classes) still stands.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

红ご颜醉 2024-11-09 21:28:36

您不必使用包对象,它可能具有一些额外的不需要的语义:常规的旧伴生对象同样好:

sealed trait HorizontalAlignment
object HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

scala> def test (x : HorizontalAlignment) = x match {
     |   case HorizontalAlignment.Left => "got left"
     | }

scala> test(HorizontalAlignment.Left)
res0: java.lang.String = got left

您遇到的问题是,由于 Horizo​​ntalAlignment 是一个抽象类,因此没有实例Horizo​​ntalAlignment 的取消引用。使用原始的命名空间公式,您需要实例化一个 Horizo​​ntalAlignment 实例,并且内部对象将特定于该实例。但是,由于 Horizo​​ntalAlignment 是密封的,因此您无法在定义它的编译单元之外的任何其他编译单元中创建这样的实例,因此您的枚举值实际上永远无法通过任何方式获得。

与 Java 不同,没有与类关联的“静态命名空间”;要获得等效的效果,您必须使用伴生对象。

You don't have to use package objects, which may have some additional undesirable semantics: regular old companion objects are just as good:

sealed trait HorizontalAlignment
object HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

scala> def test (x : HorizontalAlignment) = x match {
     |   case HorizontalAlignment.Left => "got left"
     | }

scala> test(HorizontalAlignment.Left)
res0: java.lang.String = got left

The problem you encountered was that since HorizontalAlignment was an abstract class there was no instance of HorizontalAlignment to dereference. With your original namespaced formulation you'd need to instantiate a HorizontalAlignment instance, and the inner objects would be specific to that instance. However, since HorizontalAlignment is sealed, you could not create such an instance in any other compilation unit than the one in which it was defined, so your enumerated values could actually never be obtained by any means.

Unlike Java, there is no "static namespace" associated with classes; to get the equivalent, you have to use a companion object.

断舍离 2024-11-09 21:28:36

您已经明智地避开了这种结构,但要回答剩下的问题:要引用您没有实例的类的 a 值成员,您将不得不诉诸存在主义。

sealed abstract class HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

object Test {
  type LeftOb = x.Left.type forSome { val x: HorizontalAlignment }

  def test(x: HorizontalAlignment): Int = x match {
    case _: LeftOb => 0
  }
}

毫不奇怪(好吧,如果你是我的话,也就不足为奇了)尝试在模式匹配中使用该类型会使编译器崩溃。但原则上这是表达它的方式。

编辑:人们似乎因我指出模式匹配器崩溃而分心。让我以一种不那么仓促的方式说明:a)这是表达相关概念的唯一方法,b)它有效。

sealed abstract class HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

object Test {
  type LeftOb = x.Left.type forSome { val x: HorizontalAlignment }

  def f(x: Any) = x.isInstanceOf[LeftOb]

  def main(args: Array[String]): Unit = {
    val ha = new HorizontalAlignment { }
    println(f(ha.Left))
    println(f(ha.Right)) 
  }
}

输出:

true
false

You have already been wisely steered away from this structure, but to answer the remaining question: to refer to an a value member of a class for which you hold no instance, you will have to resort to existentials.

sealed abstract class HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

object Test {
  type LeftOb = x.Left.type forSome { val x: HorizontalAlignment }

  def test(x: HorizontalAlignment): Int = x match {
    case _: LeftOb => 0
  }
}

Unsurprisingly (well, unsurprisingly if you're me) trying to use that type in a pattern match crashes the bejeezus out of the compiler. But in principle it is the way to express it.

Edit: people seem distracted by my pointing out the pattern matcher crash. Let me illustrate in a less crashy manner that a) this is the only way to express the concept in question and b) it works.

sealed abstract class HorizontalAlignment {
  case object Left extends HorizontalAlignment
  case object Right extends HorizontalAlignment
  case object Center extends HorizontalAlignment
  case object AsIs extends HorizontalAlignment
}

object Test {
  type LeftOb = x.Left.type forSome { val x: HorizontalAlignment }

  def f(x: Any) = x.isInstanceOf[LeftOb]

  def main(args: Array[String]): Unit = {
    val ha = new HorizontalAlignment { }
    println(f(ha.Left))
    println(f(ha.Right)) 
  }
}

Output:

true
false
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文