2012-01-22 19 views
21

Estoy jugando mi domingo por la tarde y estoy tratando de crear una estructura tipo 'habitación'. Básicamente, un objeto Room tiene un número de salidas, cada una de las cuales se refiere a otras Room s. Ahora, lo primero que intento crear son dos Room s conectados entre sí, preferiblemente en una sola declaración de asignación. De esta manera:¿Cómo me refiero a una variable mientras le asigno un valor, mientras que retiene la inmutabilidad?

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

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

Ergo: habitación uno tiene una salida North a la habitación dos, sala de dos tiene una salida South volver a la habitación uno.

Sin embargo, como se puede imaginar, esto va mal: el firstRoom val no está definido al crearlo, por lo que intentar consultarlo durante su asignación no funcionará.

Estoy bastante seguro de que esto es cierto para la mayoría de los lenguajes de programación, si no para todos. Mi pregunta: ¿Cómo puedo resolver esto sin haciendo mutable mi objeto Room? Simplemente puedo crear algunos objetos Room y agregarles las salidas, pero eso hace que el Room sea mutable, y como ejercicio personal trato de evitarlo.

Respuesta

17

no recursivo

Creo que su mejor opción es hacer algo como esto

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

Es perfectamente inmutable y evitar recurrencias desagradables.

recursiva

La construcción de una estructura recursiva necesita mucho más cuidado que Scala no es perezoso por defecto. Específicamente, no es posible crear un parámetro case class perezoso o call-by-name. Entonces, tendremos que recurrir a una estructura de datos especializada para esto.

Una opción podría ser el uso de 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 

Para estar en el lado salvar, yo prefijado un ambiente simulado antes de cada Stream para evitar el acceso prematuro. (A Stream es solo flojo en su cola, pero no en su cabeza). Una estructura de datos dedicada podría evitar esto.

limpiado versión

Podemos hacer mejor con una función auxiliar llamada por el nombre de hacer el trabajo sucio:

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(), ?))) 
+0

Respuesta épica, mereces todos los votos a favor que obtienes. No solo proporciona una respuesta simple, sino también una más profunda acerca de una función de idioma que no había tenido la oportunidad de usar todavía o que tenía un propósito. Encontré las transmisiones una o dos veces al buscar esta respuesta, pero no tenía ni idea de cómo iban a resolver esto. Muy agradecido. – fwielstra

0

Una solución alternativa, que por desgracia (creo) doesn 't trabajo con clases de casos:

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

Otra opción sería utilizar un 'perezoso' mapa:

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

La sintaxis sería mejor si realizara su propia implementación de mapa diferido extendiendo el rasgo scala.collection.immutable.Map.

2

Estoy totalmente de acuerdo con la respuesta de Debilski, pero no pude resistirme a reemplazar el método exists por un valor exists, evitando que la búsqueda ocurriera una y otra vez.

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)) 
} 
Cuestiones relacionadas