6

I have a design pattern here where there is an object generator (MorselGenerator and its children), any instance of which always generates the same exact type of object (Morsels and its children), but the type checker will not let me perform any operations on two or more of these generated objects, believing they might be different.

How do I get this past the type checker?

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

Compiler generates the following compile error

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


Update

Here is C++ code that is more or less equivalent to what I'm trying to do. Note that the eatTwo function is fully polymorphic and makes no reference to specific derived types of Morsel or 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;
}
Fooberman
  • 626
  • 5
  • 14
  • maybe this will help: http://stackoverflow.com/questions/9198562/scala-self-type-and-this-type-in-collections-issue – tuxSlayer Feb 22 '12 at 20:01

4 Answers4

4

The error makes sense, because in the method where compilation fails, the compiler cannot guarantee that you are not adding icecream to a hotdog.

The + method in HotDog helps highlight the problem, and in fact you have not override the method, rather you've added a new one:

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

You explicitly need the type being added to have the same type as "this".

Define Morsel as such, and the problem is almost solved:

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

The final part is to override the + method properly:

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

I'm not sure if you can get the compiler to prevent adding icecream and hotdogs, using the code in the form you have supplied.

Ant Kutschera
  • 6,257
  • 4
  • 29
  • 40
2

This is just how member types work in Scala: they are only considered equal when the outer objects are (known to the compiler to be) the same. One option is to use type parameters instead:

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

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

...
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Well, this does not seem to work, actually. Firstly, we must define the traits thus: `trait Morsel[Mg <: Morsel[Mg]]` etc. which seems bizarrely circular. Following through with this design, the compiler is more confused than ever when I try to add the two Morsels (HotDogs). – Fooberman Feb 18 '12 at 21:42
  • 1
    This isn't any more circular than `class Hotdog ... { type M = HotDog ... }`. See http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern – Alexey Romanov Feb 19 '12 at 15:57
0

One of the possible solutions (i've replaced + with add here to stay away from +(String, String), in the end, + is ok):

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))
}
tuxSlayer
  • 2,804
  • 2
  • 20
  • 24
0

And slight another variant:

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

probably less useful, but it may show where is the problem. Scala have "path dependent" types so, obj1.Type and obj2.Type are different types even if obj1.type == obj2.type.

tuxSlayer
  • 2,804
  • 2
  • 20
  • 24