在Scala中创建和访问抽象类字段

时间:2016-09-24 19:53:35

标签: scala class abstract-class

我是Scala的新手,对于一些相当简单的问题我真的遇到了一些困难。我实现了一个简单的二叉树来存储整数,如下所示:

abstract class IntSet {
  def incl(x: Int): IntSet
  def contains(x: Int): Boolean
}

class Empty extends IntSet {
  def contains(x: Int): Boolean = false
  def incl(x: Int): IntSet = new NonEmpty(x, new Empty, new Empty)
}

class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {
  val head = elem
  val leftChild = left
  val rightChild = right

  def contains(x: Int): Boolean =
    if (x < elem) left contains x
    else if (x > elem) right contains x
    else true

  def incl(x: Int): IntSet =
    if (x < elem) new NonEmpty(elem, left incl x, right)
    else if (x > elem) new NonEmpty(elem, left, right incl x)
    else this
}

val my_intset = new NonEmpty(5, new Empty, new Empty)
my_intset.head
val new_intset = my_intset.incl(10)
new_intset.head

遇到两个问题:首先,我无法访问这些类的任何元素,因此实现了字段headleftchildrightchild。这使my_intset可以访问它们。但是,调用incl后,对象的类型会发生变化,而new_intset现在变为IntSet,这使我无法再次访问其中的元素。我如何确保始终可以访问这些字段?

其次,对于incl的调用,对象的类型会发生变化,我感到很不舒服。从用户的角度来看,我觉得这两个对象都是IntSet的实例,无论如何这都是我的意图。我有理由担心吗?有没有办法控制这种行为?

1 个答案:

答案 0 :(得分:2)

在Scala中,可以利用Abstract Data TypesPattern Matching来操作此类型。

我们可以稍微修改你的例子。我们将创建一个sealed trait来定义类型的基本结构:

sealed trait IntSet {
  val head: Option[Int]
  val left: IntSet
  val right: IntSet

  def incl(x: Int): IntSet
  def contains(x: Int): Boolean
}

现在我们将添加继承的具体案例对象/类:

case class NonEmpty(head: Option[Int], left: IntSet, right: IntSet) extends IntSet {
  def contains(x: Int): Boolean = {
    if (x < head.getOrElse(0)) left contains x
    else if (x > head.getOrElse(0)) right contains x
    else true
  }

  def incl(x: Int): IntSet = {
    if (x < head.getOrElse(0)) NonEmpty(head, left incl x, right)
    else if (x > head.getOrElse(0)) NonEmpty(head, left, right incl x)
    else this
  }
}

case object Empty extends IntSet {
  override val head: Option[Int] = None
  override val left: IntSet = Empty
  override val right: IntSet = Empty

  def contains(x: Int): Boolean = false
  def incl(x: Int): IntSet = NonEmpty(Some(x), Empty, Empty)
}

请注意Emptycase object的方式。由于所有空IntSet都是相同的,我们只需要一个表示一个,我们不需要分配它的实例。另请注意我们如何使用它来表示leftright值。

现在,回到原来的例子,这应该可以正常工作:

def main(args: Array[String]): Unit = {
  val myIntSet = new NonEmpty(Some(5), Empty, Empty)
  println(myIntSet.head)
  val newIntSet = myIntSet.incl(10)
  println(newIntSet.right.head)
}

此外,当我们想要对IntSet进行操作时,我们可以对其进行模式匹配以使用底层具体类型:

def isEmpty(intSet: IntSet): Boolean = intSet match {
  case Empty => true
  case NonEmpty(_, _, _) => false
}

作为旁注,我使用Option[Int]来代表head。如果您不介意head Empty代替值0,则可以使用Int

如果你想要一个类似的例子,我建议looking at the implementation for List[+A]

代数仅定义您操作的类型的另一个示例可能如下所示:

sealed trait BinaryTree
case class Node(value: Int, left: BinaryTree, right: BinaryTree) extends BinaryTree
case object Empty extends BinaryTree

def incl(tree: BinaryTree, x: Int): BinaryTree = {
  tree match {
    case Node(value, left, right) =>
      if (x < value) Node(value, incl(left, x), right)
      else if (x > value) Node(value, left, incl(right, x))
      else tree
    case Empty => Node(x, Empty, Empty)
  }
}

现在:

def main (args: Array[String] ): Unit = {
  val n = Node(0, Node(1, Empty, Empty), Node(2, Empty, Empty))
  val x = incl(n, 3)
  println(x)
}

收率:

Node(0,Node(1,Empty,Empty),Node(2,Empty,Node(3,Empty,Empty)))