Неизменяемые объекты Scala и признаки с полями val

Я хотел бы построить свою модель предметной области, используя только неизменяемые объекты. Но я также хочу использовать черты с полями val и перенести некоторые функциональные возможности в черты. Пожалуйста, посмотрите на следующий пример:

trait Versionable {
 val version = 0
 def incrementVersion = copy(version=version+1)
}

К сожалению, такой код не работает - метод копирования неизвестен для черты Versionable.

Я думаю, что было бы неплохо создать метод копирования для каждой черты и класса. Такой метод должен создать поверхностную копию объекта и вернуть его, используя тот же тип, что и для оригинального объекта с заданным полем, измененным в соответствии с аргументами, переданными методу.

Так в следующем примере:

class Customer(val name: String) extends Versionable {
 def changeName(newName: String) = copy(name = newName)
}

val customer = new Customer("Scot")

customer.changeName ("McDonnald") должен возвращать экземпляр объекта Customer (версия = 0, name = "McDonnald")

и

customer.incrementVersion также должен возвращать экземпляр объекта Customer (версия = 1 , name = "Scot")

Насколько я знаю, в настоящее время отсутствие такой функциональности в Scala не позволяет использовать неизменяемые классы и признаки, не загрязняя конструктор класса полями признаков. В моем примере я не хочу вводить параметр с именем version в класс Customer, поскольку функциональность обработки версий я хочу включить в свойство Versionable.

Я знаю функциональность метода копирования в классах дел и возможность писать собственный метод копирования в классе с использованием параметров по умолчанию - но я думаю, что эта функциональность не решает мою проблему, потому что невозможно использовать такой метод копирования в чертах. Другим недостатком существующей функциональности является то, что родительский класс, использующий метод copy, возвращает родительский класс, а не класс объекта, который фактически копируется.

Мои вопросы:

1) У вас есть идея, как элегантно обработать приведенный выше пример. Я довольно новичок в Scala, так что, возможно, уже есть хорошее решение. На мой взгляд, элегантные решения должны иметь следующие особенности:

  • не следует использовать отражение

  • не следует использовать сериализацию

  • должно быть быстрым

  • должно проверяться во время компиляции

2) что вы думаете о написании плагина компилятора для генерации кода для метода копирования для моего приведенный выше пример? Возможно ли это сделать с помощью компилятора? У вас есть примеры или советы, как это сделать?

11
задан resilva87 11 November 2012 в 23:40
поделиться

3 ответа

Вот еще одно решение, которое, как и код OP, не работает. Однако он может предоставить более простую (и более полезную) отправную точку для расширения языка.

trait Versionable[T] {
   self: { def copy(version: Int): T } =>
   val version = 0
   def incrementVersion = copy(version = version + 1)
}

case class Customer(name: String, override val version: Int) 
      extends Versionable[Customer] {
   def changeName(newName: String) = copy(name = newName)
}

Код будет работать, если компилятор распознает метод copy класса Customer как соответствующий методу, определенному в аннотации собственного типа Versionable, что кажется естественным способом использования именованных параметров и параметров по умолчанию.

4
ответ дан 3 December 2019 в 06:44
поделиться

Хотя вы сказали, что не хотите использовать классы вариантов. Вот решение, использующее их:

case class Version(number: Int) {
  override def toString = "v" + number
  def next = copy(number+1)
}

case class Customer(name: String, version: Version = Version(0)) {
  def changeName(newName: String) = copy(newName)
  def incrementVersion = copy(version = version.next)
}

Теперь вы можете сделать это:

scala> val customer = new Customer("Scot")
customer: Customer = Customer(Scot,v0)

scala> customer.changeName("McDonnald")
res0: Customer = Customer(McDonnald,v0)

scala> customer.incrementVersion
res1: Customer = Customer(Scot,v1)

scala> customer // not changed (immutable)
res2: Customer = Customer(Scot,v0)
3
ответ дан 3 December 2019 в 06:44
поделиться

Трудно понять, как это будет работать и соответствовать семантике Scala - в частности, семантике неизменяемого поля, определенного в трейте. Рассмотрим трейты Versionable:

trait Versionable {
   val version = 0
}

Это объявление говорит, что, если оно не переопределено, поле version всегда будет иметь значение 0. Изменение значения version "без загрязнения конструктора класса полями трейта" (т.е. без явного переопределения поля version) нарушит эту семантику.

1
ответ дан 3 December 2019 в 06:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: