Scala: сбор обновлений / изменений неизменяемого состояния

В настоящее время я пытаюсь применить более функциональный стиль программирования к проекту, включающему низкоуровневую (на основе LWJGL) разработку GUI. Очевидно, что в таком случае необходимо иметь при себе много состояния, которое изменчиво в текущей версии. Моя цель - в конечном итоге получить полностью неизменное состояние, чтобы избежать изменений состояния в качестве побочного эффекта. Я некоторое время изучал линзы и государственные монады скалаза, но моя главная проблема остается: все эти методы основаны на копировании при записи. Так как в моем штате есть как много полей, так и полей большого размера, я беспокоюсь о производительности.

Насколько мне известно, наиболее распространенным подходом для изменения неизменяемых объектов является использование сгенерированныхcopy методcase class (это также то, что линзы делают под капотом). Мой первый вопрос, как этоcopy метод на самом деле реализован? Я провел несколько экспериментов с таким классом, как:

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

Путем сравнительного анализа, а также с учетом результатов-Xprof это выглядит как обновлениеsomeState.copy(innocentField = 42) на самом деле выполняет глубокое копирование, и я наблюдаю значительное падение производительности при увеличении размераlargeMap а такжеlargeArray, Я как-то ожидал, что вновь созданный экземпляр разделяет ссылки на объекты исходного состояния, поскольку внутренне ссылка должна просто быть передана конструктору. Могу ли я каким-то образом заставить или отключить это глубокое копирование поведения по умолчаниюcopy?

Размышляя над вопросом копирования при записи, мне было интересно, есть ли более общие решения этой проблемы в FP, которые хранят изменения неизменяемых данных своего рода инкрементным способом (в смысле «сбора обновлений» или «сбора»). изменения "). К моему удивлению я ничего не смог найти, поэтому попробовал следующее:

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

Теперь это позволяет сделать что-то вроде этого:

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

Мой вопрос сейчас: что вы думаете об этом дизайне? Это какой-то шаблон проектирования FP, о котором я не знаю (кроме сходства с шаблоном Builder)? Поскольку я не нашел ничего подобного, мне интересно, есть ли какая-то серьезная проблема с этим подходом? Или есть какие-то более стандартные способы устранения узкого места при копировании при записи, не отказываясь от неизменности?

Есть ли возможность каким-либо образом объединить функции get / set / mod?

Редактировать:

Мое предположение, чтоcopy выполняет глубокое копирование было действительно неправильно.

Ответы на вопрос(2)

Ваш ответ на вопрос