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(version = 0, name = "McDonnald")
и
customer.incrementVersion
должен также возвращать экземпляр объекта Customer(version = 1, name = "Scot")
Насколько я знаю, нынешняя нехватка таких функциональных возможностей в Scala не позволяет использовать неизменяемые классы и черты без использования класс-конструктора классов с полями признаков. В моем примере я не хочу вводить параметр с именем version в класс Customer, потому что функциональность обработки версий, которую я хочу инкапсулировать в черту Versionable.
Я знаю функциональность метода копирования в классах классов и возможность писать собственный метод копирования в классе с использованием параметров по умолчанию - но я думаю, что эта функциональность не решает мою проблему, потому что невозможно использовать такой метод копирования в чертах. Другим недостатком существующей функциональности является то, что родительский класс с использованием метода copy возвращает родительский класс, а не класс объекта, который фактически скопирован.
Мои вопросы:
1) у вас есть идея, как правильно выработать пример выше. Я новичок в Scala, поэтому, возможно, уже есть хорошее решение. На мой взгляд, элегантные решения должны иметь следующие функции:
-
не следует использовать отражение
-
не следует использовать сериализацию
-
должен быть быстрым
-
следует проверять во время компиляции
2) что вы думаете о написании плагина компилятора для генерации кода для метода копирования для моего примера выше? Можно ли это сделать с помощью плагина компилятора? У вас есть примеры или советы, как это сделать?
Ответы
Ответ 1
Чистое решение, вероятно, должно отбросить некоторую логику реализации из Versionable
и направить его вниз на стек типа в класс case (где метод copy
будет доступен вам). Дайте свойству версии значение по умолчанию, чтобы завершить проект.
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)
}
Ответ 2
Здесь другое решение, которое, как и код 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)
}
Код будет работать, если компилятор распознает метод Customer class copy
как соответствующий методу, определенному в аннотации самостоятельного типа Versionable, что кажется естественным способом использования именованных и стандартных параметров.
Ответ 3
Хотя вы сказали, что вы не хотите использовать классы 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)
Ответ 4
Это должно делать то, что вы ищете:
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
Ответ 5
Трудно понять, как это будет работать и соответствовать семантике Scala - в частности, семантике неизменяемого поля, определенного в признаке. Рассмотрим свойство Versionable:
trait Versionable {
val version = 0
}
В этом объявлении указано, что, если это не переопределено, поле версии всегда будет иметь значение 0. Чтобы изменить значение version
"без загрязнения класса конструктора с полями свойств" (т.е. без явного переопределения поля версии), будет нарушено эти семантики.