2010-08-11 24 views
11

Me gustaría construir mi modelo de dominio utilizando solo objetos inmutables. Pero también quiero usar rasgos con campos val y mover algunas funcionalidades a rasgos. Por favor, mira el siguiente ejemplo:objetos inmutables Scala y rasgos con val campos

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

Desafortunadamente dicho código no funciona - método de copia es desconocida para el rasgo versionable.

Creo que sería bueno tener el método de copia generado para cada rasgo y clase. Tal método debe crear una copia superficial del objeto y devolverlo usando el mismo tipo que para el objeto original con el campo dado modificado de acuerdo con los argumentos pasados ​​al método.

Así en el ejemplo siguiente:

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

val customer = new Customer("Scot") 

customer.changeName("McDonnald") debe devolver una instancia de objeto Customer(version = 0, name = "McDonnald")

y

customer.incrementVersion debe también devolver una instancia de objeto Customer(version = 1, name = "Scot")

Por lo que yo sé la falta actual de tal funcionalidad en Scala no te permite Se inmutable clases y rasgos sin contaminante constructor de clase con campos de rasgo. En mi ejemplo, no deseo introducir el parámetro denominado versión en la clase Cliente porque la funcionalidad del manejo de la versión que quiero tener encapsulado en el rasgo de Versionable.

Conozco la funcionalidad del método de copia en las clases de casos y la capacidad de escribir el propio método de copia en clase usando parámetros predeterminados, pero creo que esta funcionalidad no resuelve mi problema porque no es posible usar dicho método rasgos. Otro inconveniente de la funcionalidad existente es que la clase primaria que utiliza el método de copia devuelve la clase principal y no la clase de objeto que realmente se copia.

Mis preguntas:

1) ¿Tiene usted idea de cómo manejar el ejemplo anterior en forma elegante. Soy bastante nuevo en Scala, así que quizás ya haya una buena solución. En mi opinión soluciones elegantes deben tener las características siguientes:

  • no debe utilizar la reflexión

  • no debe utilizar la serialización

  • debe ser rápido

  • debe ser verificable en tiempo de compilación

2) ¿Qué piensas acerca de escribir el complemento del compilador para generar código para el método de copia para el ejemplo anterior? ¿Es posible hacer eso usando el plugin de compilador? ¿Tienes algún ejemplo o consejos sobre cómo hacer eso?

+1

¿Sabe usted que el caso cl Culos son dotados por el compilador con un método de 'copia' como usted describe? Las clases de casos no pueden (técnicamente, no deberían) derivar de otras clases de casos. En particular, la clase de caso derivada heredará el método 'copy' de la clase super (caso). –

+0

Sí, lo sé y no voy a utilizar clases de casos para resolver el problema descrito. Estoy tratando de encontrar una solución para escribir clases inmutables con rasgos que no solo sean interfaces como en Java, sino que también agreguen implementación. Los métodos "Mutator" en tales rasgos deben devolver la copia del objeto con campos modificados "mutados" pero ¿cómo hacer esto de manera elegante? – Mariusz

+0

Tal vez esta pregunta puede proporcionarle algunas formas útiles de lidiar con esto: http://stackoverflow.com/questions/3007464/minimal-framework-in-scala-for-collections-with-initing-return-type –

Respuesta

1

Es difícil ver cómo funcionaría esto y ser consecuente con la semántica de Scala, en particular, la semántica de un campo inmutable definido en un rasgo.Considere el rasgo versionable:

trait Versionable { 
    val version = 0 
} 

Esta declaración dice que, a no ser anulado, el campo de versión tendrá siempre el valor 0. Para cambiar el valor de version "sin contaminar el constructor de la clase con los campos de rasgos" (es decir, sin explícitamente anulando el campo de versión) violaría esta semántica.

3

Aunque haya dicho que no desea utilizar clases de casos. Aquí es una solución de usarlos:

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

Ahora usted puede hacer esto:

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

Aquí hay otra solución que, al igual que el código de la OP, que no funciona. Sin embargo, puede proporcionar un punto de partida más simple (y más útil en general) para extender el idioma.

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

El código funcionaría si el compilador reconocido método copy de la clase de cliente como en conformidad con el procedimiento definido en el tipo de auto anotación de versionable, lo que parece ser una manera natural de usar el nombre y los parámetros por defecto.

8

La solución más limpia es probablemente soltar alguna lógica de implementación de Versionable, y empujarla hacia abajo en la pila de tipos a una clase de caso (donde el método copy estará disponible para usted). Proporcione a la propiedad de la versión un valor predeterminado para completar el diseño.

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

Si lo desea, también puede definir un alias de tipo en algún lugar de la numeración de la versión:

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

Esto debería hacer lo que busca:

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

entonces se puede úselo como

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 
+0

Guau, eso es una locura. Por lo que puedo ver, esto tiene un comportamiento inmutable. Pero solo quiero volver a verificar: ¿podría esta solución tener algún problema con la seguridad de las hebras? – Magnus

+1

Debería estar bien con la seguridad de los hilos. – fuzion24

Cuestiones relacionadas