Scala неизменяемые объекты и черты с полями val - PullRequest
11 голосов
/ 11 августа 2010

Я хотел бы построить свою модель предметной области, используя только неизменяемые объекты. Но я также хочу использовать черты с полями 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(version = 0, name = "McDonnald")

и

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

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

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

Мои вопросы:

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

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

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

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

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

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

Ответы [ 5 ]

8 голосов
/ 14 сентября 2010

Самое чистое решение - это, вероятно, удалить некоторую логику реализации из Versionable и поместить ее вниз в стек типов к классу дел (где вам будет доступен метод copy). Присвойте свойству version значение по умолчанию для завершения проектирования.

trait Versioned {
  def version : Int
  def nextVersion = version + 1 
}

case class Customer(name: String, version : Int = 0) extends Versioned {
  def withName(newName: String) = copy(name = newName, version = nextVersion)
}

Если вы хотите, вы также можете определить псевдоним типа для нумерации версий:

type Version = Int
val initialVersion = 0

trait Versioned {
  def version : Version
  def nextVersion = version + 1 
}

case class Customer(name: String, version : Version = initialVersion)
extends Versioned {
  def withName(newName: String) = copy(name = newName, version = nextVersion)
}
4 голосов
/ 12 августа 2010

Вот еще одно решение, которое, как и код 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, которая кажется естественным способом использования именованных и стандартных параметров.

3 голосов
/ 12 августа 2010

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

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)
2 голосов
/ 02 мая 2013

Это должно сделать то, что вы ищете:

trait Request[T <: Request[T]] extends Cloneable {
  this: T =>
  private var rets = 0
  def retries = rets
  def incRetries:T = {
    val x = super.clone().asInstanceOf[T]
    x.rets = rets + 1
    x
  }
}

Тогда вы можете использовать его как

case class Download(packageName:String) extends Request[Download]
val d = Download("Test")
println(d.retries) //Prints 0
val d2 = d.incRetries
println(d2.retries) //Prints 1
println(d.retries) //Still prints 0   
1 голос
/ 11 августа 2010

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

trait Versionable {
   val version = 0
}

В этом объявлении говорится, что, если оно не переопределено, поле версии всегда будет иметь значение 0. Чтобы изменить значение version «без загрязнения конструктора класса полями свойств»(т.е. без явного переопределения поля версии) нарушит эту семантику.

...