6

Tengo un patrón de diseño aquí donde hay un generador de objetos (MorselGenerator y sus hijos), cada instancia siempre genera el mismo tipo exacto de objeto (Morsels y sus hijos), pero el verificador de tipos no me permitirá realizar ninguna operación en dos o más de estos objetos generados, creyendo que podrían ser diferentes.La inferencia del tipo de Scala falla al señalar que estos tipos son idénticos, cualquiera que sea

¿Cómo puedo pasar esto al verificador de tipos?

trait Morsel 
{ 
    type M <: Morsel 
    def calories : Float 
    def + (v : M) : M 
} 

trait MorselGenerator 
{ 
    type Mg <: Morsel 
    def generateMorsel : Mg 
} 

class HotDog(c : Float, l : Float, w : Float) extends Morsel 
{ 
    type M = HotDog 
    val calories : Float = c 
    val length : Float = l  
    val width : Float = w 
    def + (v : HotDog) : HotDog = new HotDog(v.calories + calories, v.length + length, v.width + width) 
} 

class HotDogGenerator extends MorselGenerator 
{ 
    type Mg = HotDog 
    def generateMorsel : HotDog = new HotDog(500.0f, 3.14159f, 445.1f) 
} 

object Factory 
{ 
    def main (args : Array[String]) 
    { 
     val hdGen = new HotDogGenerator() 
     println(eatTwo(hdGen)) 
    } 

    def eatTwo (mGen : MorselGenerator) 
    { 
     val v0 : mGen.Mg = mGen.generateMorsel 
     val v1 : mGen.Mg = mGen.generateMorsel 
     v0 + v1       /// ERROR HERE 
    } 
} 

compilador genera el siguiente error de compilación

Generator.scala:43: error: type mismatch; 
found : v1.type (with underlying type mGen.Mg) 
required: v0.M 
     v0 + v1       /// ERROR HERE 
     ^one error found 


actualización

Aquí está el código de C++ que es más o menos equivalente a lo que estoy tratando de hacer. Tenga en cuenta que la función eatTwo es completamente polimórfica y no hace referencia a tipos específicos derivados de Morsel o MorselGenerator.

#include <stdlib.h> 
#include <stdio.h> 

template <class M> class Morsel 
{ 
public: 
    Morsel(float c) : calories(c) {} 
    float calories; 
    virtual M operator + (const M& rhs) const = 0; 
}; 

template <class M> class MorselGenerator 
{ 
public: 
    virtual M * generateMorsel() const = 0; 
}; 

class HotDog : public Morsel<HotDog> 
{ 
public: 
    HotDog(float c, float l, float w) : Morsel<HotDog>(c), length(l), width(w) {} 
    float length, width; 

    HotDog operator + (const HotDog& rhs) const 
    { return HotDog(calories+rhs.calories, length+rhs.length, width+rhs.width); } 
}; 

class HotDogGenerator : public MorselGenerator<HotDog> 
{ 
    HotDog * generateMorsel() const { return new HotDog(500.0f, 3.14159f, 445.1f); } 
}; 

/////////////////////////////////////////////// 

template <class MorselType> float eatTwo (const MorselGenerator<MorselType>& mGen) 
{ 
    MorselType * m0 = mGen.generateMorsel(); 
    MorselType * m1 = mGen.generateMorsel(); 
    float sum = ((*m0) + (*m1)).calories; 
    delete m0; delete m1; 
    return sum; 
} 

int main() 
{ 
    MorselGenerator<HotDog> * morselStream = new HotDogGenerator(); 
    printf("Calories Ingested: %.2f\n", eatTwo(*morselStream)); 
    delete morselStream; 
} 
+0

quizás esto ayude: http://stackoverflow.com/questions/9198562/scala-self-type-and-this-type-in-collections-issue – tuxSlayer

Respuesta

2

Esto es sólo la forma en tipos miembros trabajan en Scala: sólo se consideran iguales cuando los objetos exteriores están (conocidos por el compilador para ser) la misma. Una opción es utilizar los parámetros de tipo en su lugar:

trait Morsel[M <: Morsel] 
{ 
    def calories : Float 
    def + (v : M) : M 
} 

trait MorselGenerator[Mg <: Morsel] 
{ 
    def generateMorsel : Mg 
} 

... 
+0

Bueno, esto no parece funcionar, en realidad.En primer lugar, debemos definir los rasgos así: 'rasgo Morsel [Mg <: Morsel [Mg]] etc. que parece extrañamente circular. Siguiendo con este diseño, el compilador está más confundido que nunca cuando trato de agregar los dos Morsels (HotDogs). – Fooberman

+1

Esto no es más circular que 'clase Hotdog ... {type M = HotDog ...}'. Ver http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern –

4

El error tiene sentido, ya que en el método en el que falla la compilación, el compilador no puede garantizar que no va a agregar helado a un perrito caliente.

método

El + en el hot dog ayuda a resaltar el problema, y ​​de hecho no se han anular el método, en vez que ha añadido una nueva: se añaden

def + (v : HotDog) : HotDog = new HotDog(v.calories + calories, v.length + length, v.width + width) 

Es necesario explícitamente el tipo de tener la el mismo tipo que "esto".

Definir Morsel como tal, y el problema está casi resuelto:

trait Morsel { 
    def calories : Float 
    def + (v : Morsel) : Morsel 
} 

La parte final es reemplazar el método + correctamente:

override def + (v : Morsel): Morsel = v match { 
    case hd: HotDog => new HotDog(hd.calories + calories, hd.length + length, hd.width + width) 
    case x => throw new IllegalArgumentException("eurgh!") 
} 

No estoy seguro de si se puede conseguir el compilador para evitar agregar helados y salchichas, usando el código en la forma que ha proporcionado.

0

Una de las posibles soluciones (he reemplazado con +add aquí para permanecer lejos de +(String, String), al final, + está bien):

trait Morsel[M <: Morsel[M]] { /// this 
    this: M =>      /// and this make the trick 
    def calories : Float 
    def add(v : M) : M 
} 

trait MorselGenerator[Mg <: Morsel[Mg]] 
{ 
    def generateMorsel : Mg 
} 

class HotDog(c : Float, l : Float, w : Float) extends Morsel[HotDog] 
{ 
    val calories : Float = c 
    val length : Float = l  
    val width : Float = w 
    override def add (v : HotDog) : HotDog = new HotDog(v.calories + calories, v.length + length, v.width + width) 
} 

class HotDogGenerator extends MorselGenerator[HotDog] 
{ 
    def generateMorsel : HotDog = new HotDog(500.0f, 3.14159f, 445.1f) 
} 

object Factory extends App 
{ 
    def eatTwo[M <: Morsel[M]](mGen : MorselGenerator[M]) = { 
    val v0 = mGen.generateMorsel 
    val v1 = mGen.generateMorsel 
    v0 add v1  
    } 

    val hdGen = new HotDogGenerator() 
    println(eatTwo(hdGen)) 
} 
0

Y ligera otra variante:

trait MorselGenerator { 
    type M <: Morsel 

    trait Morsel { this: M => 
    def calories : Float 
    def add (v : M) : M 
    }  

    def generateMorsel : M 
} 

class HotDogGenerator extends MorselGenerator 
{ 
    type M = HotDog 

    class HotDog(c : Float, l : Float, w : Float) extends Morsel { 
    val calories : Float = c 
    val length : Float = l  
    val width : Float = w 
    def add (v : HotDog) : HotDog = new HotDog(v.calories + calories, v.length + length, v.width + width) 
    } 

    def generateMorsel: HotDog = new HotDog(500.0f, 3.14159f, 445.1f) 
} 

object Factory extends App 
{ 
    val hdGen = new HotDogGenerator() 

    hdGen.generateMorsel add hdGen.generateMorsel add hdGen.generateMorsel 

    produceDouble(hdGen) 

    def produceDouble(gen: MorselGenerator): MorselGenerator#Morsel = { 
    gen.generateMorsel add gen.generateMorsel 
    } 
} 

probablemente sea menos útil, pero puede mostrar dónde está el problema. Scala tiene tipos "dependientes de la ruta", por lo tanto, obj1.Type y obj2.Type son tipos diferentes incluso si obj1.type == obj2.type.

Cuestiones relacionadas