Scala: recogiendo actualizaciones / cambios de estado inmutable.
Actualmente estoy tratando de aplicar un estilo de programación más funcional a un proyecto que involucra el desarrollo de GUI de bajo nivel (basado en LWJGL). Obviamente, en tal caso es necesario llevar mucho estado, que es mutable en la versión actual. Mi objetivo es eventualmente tener un estado completamente inmutable, para evitar cambios de estado como efecto secundario. Estudié los lentes de Scalaz y las mónadas estatales durante un tiempo, pero mi principal preocupación sigue siendo: Todas estas técnicas se basan en la copia en escritura. Como mi estado tiene un gran número de campos y también algunos campos de tamaño considerable, me preocupa el rendimiento.
Que yo sepa, el enfoque más común para modificar objetos inmutables es utilizar los generadoscopy
método de uncase class
(Esto es también lo que hacen las lentes debajo de la capucha). Mi primera pregunta es, ¿cómo estacopy
¿Se implementa realmente el método? Realicé algunos experimentos con una clase como:
case class State(
innocentField: Int,
largeMap: Map[Int, Int],
largeArray: Array[Int]
)
Haciendo un benchmarking y también mirando la salida de-Xprof
parece que la actualizaciónsomeState.copy(innocentField = 42)
en realidad realiza una copia en profundidad y observo una caída significativa en el rendimiento cuando aumento el tamaño delargeMap
ylargeArray
. Esperaba de alguna manera que la instancia recién construida comparta las referencias de objeto del estado original, ya que internamente la referencia debería pasar al constructor. ¿Puedo forzar o deshabilitar de alguna manera este comportamiento de copia profunda del predeterminado?copy
?
Mientras reflexionaba sobre el tema de la copia en escritura, me preguntaba si existen soluciones más generales para este problema en FP, que almacenan los cambios de datos inmutables de una manera incremental (en el sentido de "recopilar actualizaciones" o "recopilar cambios "). Para mi sorpresa no pude encontrar nada, así que intenté lo siguiente:
// example state with just two fields
trait State {
def getName: String
def getX: Int
def setName(updated: String): State = new CachedState(this) {
override def getName: String = updated
}
def setX(updated: Int): State = new CachedState(this) {
override def getX: Int = updated
}
// convenient modifiers
def modName(f: String => String) = setName(f(getName))
def modX(f: Int => Int) = setX(f(getX))
def build(): State = new BasicState(getName, getX)
}
// actual (full) implementation of State
class BasicState(
val getName: String,
val getX: Int
) extends State
// CachedState delegates all getters to another state
class CachedState(oldState: State) extends State {
def getName = oldState.getName
def getX = oldState.getX
}
Ahora esto permite hacer algo como esto:
var s: State = new BasicState("hello", 42)
// updating single fields does not copy
s = s.setName("world")
s = s.setX(0)
// after a certain number of "wrappings"
// we can extract (i.e. copy) a normal instance
val ns = s.setName("ok").setX(40).modX(_ + 2).build()
Mi pregunta ahora es: ¿Qué piensas de este diseño? ¿Es este un tipo de patrón de diseño de FP que no conozco (aparte de la similitud con el patrón de Builder)? Dado que no he encontrado nada similar, me pregunto si hay algún problema importante con este enfoque. ¿O hay alguna otra forma estándar de resolver el cuello de botella de copia en escritura sin renunciar a la inmutabilidad?
¿Existe incluso la posibilidad de unificar las funciones get / set / mod de alguna manera?
Editar:
Mi suposición de quecopy
Realiza una copia profunda de hecho estaba mal.