Scala: zbieranie aktualizacji / zmian niezmiennego stanu

Obecnie próbuję zastosować bardziej funkcjonalny styl programowania do projektu obejmującego niskopoziomowy (oparty na LWJGL) rozwój GUI. Oczywiście w takim przypadku konieczne jest przeniesienie wielu stanów, które można zmienić w obecnej wersji. Moim celem jest ostatecznie uzyskanie całkowicie niezmiennego stanu, aby uniknąć zmian stanu jako skutków ubocznych. Przez chwilę studiowałem obiektywy skalaz i monady stanów, ale moim głównym zmartwieniem pozostaje: wszystkie te techniki polegają na kopiowaniu na zapisie. Ponieważ mój stan ma zarówno dużą liczbę pól, jak i niektóre pola o znacznych rozmiarach, obawiam się wydajności.

Według mojej wiedzy najczęstszym sposobem modyfikowania obiektów niezmiennych jest użycie wygenerowanegocopy metoda acase class (jest to również to, co robią soczewki pod maską). Moje pierwsze pytanie brzmi: jak tocopy metoda została faktycznie wdrożona? Przeprowadziłem kilka eksperymentów z takimi klasami jak:

case class State(
  innocentField: Int, 
  largeMap: Map[Int, Int], 
  largeArray: Array[Int]
)

Poprzez benchmarking, a także patrząc na wynik-Xprof wygląda na aktualizacjęsomeState.copy(innocentField = 42) faktycznie wykonuje głęboką kopię i obserwuję znaczny spadek wydajności, gdy zwiększam rozmiarlargeMap ilargeArray. Spodziewałem się w jakiś sposób, że nowo skonstruowana instancja będzie współużytkowała odniesienia do stanu pierwotnego, ponieważ wewnętrznie referencja powinna zostać przekazana do konstruktora. Czy mogę w jakiś sposób wymusić lub wyłączyć to zachowanie głębokiego kopiowania domyślnecopy?

Zastanawiając się nad kwestią kopiowania przy pisaniu, zastanawiałem się, czy w FP istnieją bardziej ogólne rozwiązania tego problemu, które przechowują zmiany niezmiennych danych w pewien sposób przyrostowy (w sensie „zbierania aktualizacji” lub „gromadzenia zmiany"). Ku mojemu zdziwieniu nic nie znalazłem, więc spróbowałem:

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

Teraz pozwala to zrobić coś takiego:

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

Moje pytanie brzmi: co sądzisz o tym projekcie? Czy jest to jakiś wzorzec projektowania FP, którego nie znam (poza podobieństwem do wzorca Buildera)? Ponieważ nie znalazłem niczego podobnego, zastanawiam się, czy istnieje jakiś poważny problem z tym podejściem? A może istnieją bardziej standardowe sposoby rozwiązania problemu wąskiego gardła przy kopiowaniu bez zapisywania niezmienności?

Czy istnieje możliwość ujednolicenia funkcji get / set / mod w jakiś sposób?

Edytować:

Moje założenie, żecopy wykonuje głęboką kopię rzeczywiście była zła.

questionAnswers(2)

yourAnswerToTheQuestion