15

Supongamos que se quiere construir una nueva clase genérica, Novel[A]. Esta clase contendrá muchos métodos útiles, tal vez sea un tipo de colección, y por lo tanto desea subclasificarla. Pero desea que los métodos devuelvan el tipo de la subclase, no el tipo original. En Scala 2.8, ¿cuál es la cantidad mínima de trabajo que uno tiene que hacer para que los métodos de esa clase devuelvan la subclase relevante, no la original? Por ejemplo,Marco mínimo en Scala para colecciones con tipo de retorno heredado

class Novel[A] /* What goes here? */ { 
    /* Must you have stuff here? */ 
    def reverse/* What goes here instead of :Novel[A]? */ = //... 
    def revrev/*?*/ = reverse.reverse 
} 
class ShortStory[A] extends Novel[A] /* What goes here? */ { 
    override def reverse: /*?*/ = //... 
} 
val ss = new ShortStory[String] 
val ss2 = ss.revrev // Type had better be ShortStory[String], not Novel[String] 

cambia esto mínima cantidad si desea que se Novel covariante?

(Las colecciones 2.8 lo hacen entre otras cosas, pero también juegan con tipos de devolución de formas más sofisticadas (y útiles); la pregunta es qué tan poco marco se puede lograr si uno solo quiere estos subtipos-siempre -return-subtítulos función.)

Editar: Suponga en el código anterior que reverse hace una copia. Si se realiza una modificación in situ y luego se devuelve, se puede usar this.type, pero eso no funciona porque la copia no es this.

Arjan vinculada a otra pregunta que sugiere la siguiente solución:

def reverse: this.type = { 
    /*creation of new object*/.asInstanceOf[this.type] 
} 

que básicamente se encuentra al sistema de tipos con el fin de conseguir lo que queremos. Pero esta no es realmente una solución, porque ahora que hemos mentido al sistema de tipos, el compilador no puede ayudarnos a asegurarnos de que realmente haga obtener un ShortStory cuando creemos que lo hacemos. (Por ejemplo, no tendríamos que anular reverse en el ejemplo anterior para hacer que el compilador sea feliz, pero nuestros tipos no serían lo que queríamos)

+3

Probablemente no se refirió a los tipos Singleton de Scala como un ejemplo del marco mínimo que tenía en mente, supongo? En cualquier caso, ejemplos en línea para mostrar cómo se puede utilizar se pueden encontrar en http://scalada.blogspot.com/2008/02/thistype-for-chaining-method-calls.html. También hay un ejemplo en SO que muestra cómo se pueden producir clases inmutables con algunos hackers http://stackoverflow.com/questions/775312/why-cannot-this-type-be-used-for-new-instances –

+0

@Arjan - No quise decir eso, ya que eso es demasiado limitante. (Los métodos de copia están prohibidos, por ejemplo). –

Respuesta

3

no he pensado esto a través completamente, pero escriba cheques:

object invariant { 
    trait Novel[A] { 
    type Repr[X] <: Novel[X] 

    def reverse: Repr[A] 

    def revrev: Repr[A]#Repr[A] 
     = reverse.reverse 
    } 
    class ShortStory[A] extends Novel[A] { 
    type Repr[X] = ShortStory[X] 

    def reverse = this 
    } 

    val ss = new ShortStory[String] 
    val ss2: ShortStory[String] = ss.revrev 
} 

object covariant { 
    trait Novel[+A] { 
    type Repr[X] <: Novel[_ <: X] 

    def reverse: Repr[_ <: A] 

    def revrev: Repr[_ <: A]#Repr[_ <: A] = reverse.reverse 
    } 

    class ShortStory[+A] extends Novel[A] { 
    type Repr[X] = ShortStory[X] 

    def reverse = this 
    } 

    val ss = new ShortStory[String] 
    val ss2: ShortStory[String] = ss.revrev 
} 

EDITAR

La versión co-variante puede ser mucho más agradable:

object covariant2 { 
    trait Novel[+A] { 
    type Repr[+X] <: Novel[X] 

    def reverse: Repr[A] 

    def revrev: Repr[A]#Repr[A] = reverse.reverse 
    } 

    class ShortStory[+A] extends Novel[A] { 
    type Repr[+X] = ShortStory[X] 

    def reverse = this 
    } 

    val ss = new ShortStory[String] 
    val ss2: ShortStory[String] = ss.revrev 
} 
+0

Parece prometedor, pero ¿qué ocurre si quiero crear la subclase 'ShortStory'? El problema es que podría tener un código de inicializador útil en una clase y quiero subclasificarlo sin perder ese trabajo, por lo que preferiría no hacer que todo desciende de los rasgos. Sin embargo, si la respuesta es que uno no puede obtener el sistema de tipo para aceptar mis demandas, esto parece un premio de consolación razonable. –

5

Editar: Me di cuenta de que Rex tenía una clase concreta Novela en su ejemplo, no es un rasgo como he usado a continuación. La implementación del rasgo es un poco demasiado simple para ser una solución a la pregunta de Rex, por lo tanto. También se puede hacer usando una clase concreta (ver más abajo), pero la única forma en que podría hacer ese trabajo es mediante un casting, lo que hace que esto no sea realmente 'compilable time type safe'. Esto Entonces esto no califica como una solución.

Tal vez no

el más bonito, pero un simple ejemplo usando tipos de miembro abstractos podría implementarse de la siguiente manera:


trait Novel[A] { 
    type T <: Novel[A] 
    def reverse : T 
    def revrev : T#T = reverse.reverse 
} 

class ShortStory[A](var story: String) extends Novel[A] { 
type T = ShortStory[A] 
def reverse : T = new ShortStory[A](story reverse) 
def myMethod: Unit = println("a short story method") 
} 

scala> val ss1 = new ShortStory[String]("the story so far") 
ss1: ShortStory[String] = [email protected] 

scala> val ssRev = ss1 reverse 
ssRev: ss1.T = [email protected] 

scala> ssRev story 
res0: String = raf os yrots eht 

scala> val ssRevRev = ss1 revrev 
ssRevRev: ss1.T#T = [email protected] 

scala> ssRevRev story 
res1: String = the story so far 

scala> ssRevRev myMethod 
a short story method 

Ciertamente es mínima, pero dudo que esto lo suficiente haría a ser utilizado como una especie de marco. Y, por supuesto, los tipos devueltos no son tan claros como en el marco de las colecciones de Scala, así que quizás esto sea demasiado simple. Para el caso dado, parece hacer el trabajo, sin embargo. Como se comentó anteriormente, esto no hace el trabajo para el caso dado, por lo que se requiere alguna otra solución aquí.

Sin embargo, otro de edición: Algo similar se puede hacer uso de una clase concreta, así, a pesar de que tampoco basta para ser un tipo seguro:


class Novel[A](var story: String) { 
    type T <: Novel[A] 
    def reverse: T = new Novel[A](story reverse).asInstanceOf[T] 
    def revrev : T#T = reverse.reverse 
} 
class ShortStory[A](var s: String) extends Novel[A](s) { 
type T = ShortStory[A] 
override def reverse : T = new ShortStory(story reverse) 
def myMethod: Unit = println("a short story method") 
} 

Y el código funcionará como en el ejemplo rasgo. Pero sufre el mismo problema que Rex mencionado en su edición también. La anulación en ShortStory no es necesaria para hacer esta compilación. Sin embargo, fallará en tiempo de ejecución si no lo hace y llama al método inverso en una instancia de ShortStory.

+0

¡Buen intento, incluso si esto no lo hizo! No estoy seguro de lo que he pedido es posible con el sistema de tipo actual. (Las colecciones de Scala usan una gran cantidad de rasgos y muy pocas clases creadas con una instancia). –

2

Después discusiones en la lista de correo de Scala - ¡muchas gracias a la gente de allí por ponerme en el camino correcto! - Creo que esto es lo más cercano que uno puede llegar a un mínimo al marco. Lo dejo aquí por referencia, y estoy usando un ejemplo diferente porque pone de relieve lo que está pasando mejor:

abstract class Peano[A,MyType <: Peano[A,MyType]](a: A, f: A=>A) { 
    self: MyType => 
    def newPeano(a: A, f: A=>A): MyType 
    def succ: MyType = newPeano(f(a),f) 
    def count(n: Int): MyType = { 
    if (n<1) this 
    else if (n==1) succ 
    else count(n-1).succ 
    } 
    def value = a 
} 

abstract class Peano2[A,MyType <: Peano2[A,MyType]](a: A, f: A=>A, g: A=>A) extends Peano[A,MyType](a,f) { 
    self: MyType => 
    def newPeano2(a: A, f: A=>A, g: A=>A): MyType 
    def newPeano(a: A, f: A=>A): MyType = newPeano2(a,f,g) 
    def pred: MyType = newPeano2(g(a),f,g) 
    def uncount(n: Int): MyType = { 
    if (n < 1) this 
    else if (n==1) pred 
    else uncount(n-1).pred 
    } 
} 

La clave aquí es la adición del parámetro MyType tipo que es un marcador de posición para el tipo de la clase con la que realmente terminaremos Cada vez que heredamos, tenemos que redefinirlo como un parámetro de tipo, y hemos agregado un método constructor que creará un nuevo objeto de este tipo. Si el constructor cambia, tenemos que crear un nuevo método constructor.

Ahora, cuando se desea crear una clase que se utiliza en realidad, sólo tiene que rellenar el método constructor con una llamada a la nueva (y decirle a la clase que es de su propio tipo):

class Peano2Impl[A](a: A, f: A=>A, g: A=>A) extends Peano2[A,Peano2Impl[A]](a,f,g) { 
    def newPeano2(a: A, f: A=>A, g: A=>A) = new Peano2Impl[A](a,f,g) 
} 

y ya está en marcha:

val p = new Peano2Impl(0L , (x:Long)=>x+1 , (y:Long)=>x-1) 

scala> p.succ.value 
res0: Long = 1 

scala> p.pred.value 
res1: Long = -1 

scala> p.count(15).uncount(7).value 
res2: Long = 8 

lo tanto, para revisión, el texto modelo mínimo - si desea incluir métodos recursivos, que rompe el otro estilo de respuesta - es para cualquiera de los métodos que devuelven un nuevo copiar desde fuera de la clase (usando nuevo o una fábrica o lo que sea) para dejarlo abstracto (re, he reducido todo a un método que duplica el constructor), y tiene que agregar la anotación de tipo MyType como se muestra. Luego, en el último paso, estos métodos de nueva copia deben ser instanciados.

Esta estrategia funciona bien para la covarianza en A también, excepto que este ejemplo particular no funciona ya que f y g no son covariantes.

Cuestiones relacionadas