【问题标题】:Pattern matching on nested types in ScalaScala中嵌套类型的模式匹配
【发布时间】:2011-08-11 12:58:14
【问题描述】:

我正在尝试在 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)

}

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

【问题讨论】:

    标签: scala pattern-matching nested-class case-class


    【解决方案1】:

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

    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
      }
    }
    

    不出所料(好吧,如果你是我的话,不出所料)尝试在模式匹配中使用该类型会使 bejeezus 退出编译器。但原则上它是表达它的方式。

    编辑:人们似乎因为我指出模式匹配器崩溃而分心。让我以一种不那么突兀的方式来说明 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
    

    【讨论】:

    • 无效的答案会让 OP 和其他读者感到困惑。然而,使用存在类型是一个好主意。如果你得到它的工作,请发布更新。
    • 它工作正常,除了它使模式匹配器崩溃。如果您的意思是我应该在修复模式匹配器时发布更新,那么请不要屏住呼吸。如果我的正确答案比没有答案更令人困惑,我很抱歉,但这不是我评估事情的方式。
    • 该类型的成员不会被擦除(因此无论如何都会给出错误的结果?)
    • 我希望这能更好地工作(如果你真的能得到一个相关的 Horizo​​ntalAlignment 实例): object Test { def test(x: Horizo​​ntalAlignment) = x match { case x.Left => "got left" case x.Right => "got right" } } -- 这不会使模式匹配器崩溃。
    • 不。我将添加另一个答案,因为人们似乎被模式匹配器崩溃分心了。
    【解决方案2】:

    您不必使用包对象,它可能有一些额外的不受欢迎的语义:常规的旧伴侣对象也一样好:

    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 不同,没有与类关联的“静态命名空间”;要获得等价物,您必须使用伴随对象。

    【讨论】:

    • 谢谢,这正是我正在寻找的解决方案。
    猜你喜欢
    • 2020-03-18
    • 2013-09-13
    • 2017-12-16
    • 2021-04-30
    • 2016-10-21
    • 2020-08-09
    • 2019-06-15
    • 1970-01-01
    相关资源
    最近更新 更多