Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala type inference fails to note that these types are identical, whatever they are

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;
}
like image 895
Fooberman Avatar asked Feb 18 '12 18:02

Fooberman


People also ask

What does type inference mean in Scala?

The Scala compiler can infer the types of expressions automatically from contextual information. Therefore, we need not declare the types explicitly. This feature is commonly referred to as type inference. It helps reduce the verbosity of our code, making it more concise and readable.

What would be the type inferred by Scala compiler for variable?

Scala compiler can automatically infer types of each variable declared. If the value of a variable is declared in double-quotes it will automatically be inferred as String. Also, the compiler can infer any value in a single quote is inferred as Char.


2 Answers

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.

like image 159
Ant Kutschera Avatar answered Oct 04 '22 14:10

Ant Kutschera


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
}

...
like image 35
Alexey Romanov Avatar answered Oct 04 '22 13:10

Alexey Romanov