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
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;
}
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.
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.
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.
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
}
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With