如何在为变量分配值的同时引用变量,同时保留不变性?

时间:2012-01-22 15:09:16

标签: scala immutability

我正在周日下午摆弄,并试图创造一种“房间”结构。基本上,Room对象有多个出口,每个出口都引用其他Room个。现在,我要创建的第一件事是两个Room彼此连接,最好是在一个赋值语句中。像这样:

case class Room(title: String, exits: Map[Direction.Direction, Room])

val firstRoom = Room("A room", Map(North -> Room("Another room", Map(South -> firstRoom))))

Ergo:第一房间有一个出口North到第二个房间,第二个房间有一个出口South回到第一个房间。

但是,正如您可以想象的那样,这是错误的:firstRoom val在创建时未定义,因此在分配期间尝试引用它将无效。

我很确定大多数(如果不是全部)编程语言都是如此。我的问题:如何在不使使Room对象变为可变的情况下解决此?我可以简单地创建一些Room对象并在之后添加退出,但这会使Room变为可变,并且作为个人练习我会尽量避免这种情况。

3 个答案:

答案 0 :(得分:17)

<强>非递归

我认为你最好的选择是做这样的事情

object Rooms {
  case class Room(title: String) {
    def exits = exitMap(this)
  }
  val first:Room = Room("first")
  val second:Room = Room("second")

  private val exitMap = Map(first -> Map("S" -> second), second -> Map("N" -> first))
}

scala> Rooms.first.title
res: String = first

scala> Rooms.first.exits
res: scala.collection.immutable.Map[java.lang.String,Rooms.Room] = Map(S -> Room(second))

完全不可变,你可以避免令人讨厌的递归。

<强>递归

构造递归结构需要更加小心,因为默认情况下Scala不是懒惰的。具体而言,无法创建惰性或按名称调用case class参数。因此,我们必须为此采用专门的数据结构。

一种选择可能是使用Stream s:

case class LazyRoom(title: String, exits: Stream[LazyRoom])

object LazyRooms {
  lazy val nullRoom: LazyRoom = LazyRoom("nullRoom", Stream.empty)
  lazy val first: LazyRoom = LazyRoom("first", nullRoom #:: second #:: Stream.empty)
  lazy val second: LazyRoom = LazyRoom("second", nullRoom #:: first #:: Stream.empty)
}

scala> LazyRooms.first.exits(1).title
res> String: second

为了保存,我在每个Stream之前为假房间加前缀,以避免过早访问。 (Stream的尾部只是懒惰而不是它的头部。)专用数据结构可能能够避免这种情况。

清理版

我们可以通过逐个调用辅助函数来做更好的工作:

case class LazyRoom(title: String, exitMap: Stream[Map[String, LazyRoom]]) {
  def exits = exitMap(1) // skip the Streams empty head
}

def _exitMap(mappedItems: => Map[String, LazyRoom]) = {
  Map[String, LazyRoom]() #::
  mappedItems #::
  Stream.empty
}

object LazyRooms {
  lazy val first: LazyRoom = LazyRoom("first", _exitMap(Map("South" -> second)))
  lazy val second: LazyRoom = LazyRoom("second", _exitMap(Map("North" -> first)))
}

scala> LazyRooms.first.exits
res: Map[String,LazyRoom] = Map(South -> LazyRoom(second,Stream(Map(), ?)))

答案 1 :(得分:2)

我完全同意Debilski的回答,但我无法拒绝用惰性val exists替换方法exists,阻止查找一次又一次地发生。

object Rooms {
  case class Room(title: String) {
    lazy val exits = exitMap(this)
  }
  val first:Room = Room("first")
  val second:Room = Room("second")

  private val exitMap = Map(first -> Map("S" -> second), second -> Map("N" -> first))
}

答案 2 :(得分:0)

另一种解决方案,不幸的是(我认为)不适用于案例类:

class Room(val title: String, _exits : => Map[String, Room]) { lazy val exits = _exits }
val room1 : Room = new Room("A room", Map("N" -> room2))
val room2 : Room = new Room("Another room", Map("S" -> room1))

另一种选择是使用“懒惰”地图:

case class Room(val title : String, exits : Map[String, () => Room])
val room1 : Room = Room("A room", Map("N" -> (() => room2)))
val room2 : Room = Room("Another room", Map("S" -> (() => room1)))

如果您使用扩展scala.collection.immutable.Map特征的自己的延迟地图实现,语法会更好。