Логический тип Scala не замечает, что эти типы идентичны, какими бы они ни были
У меня здесь есть шаблон проектирования, где есть генератор объектов (MorselGenerator и его дочерние элементы), любой экземпляр которого всегда генерирует один и тот же точный тип объекта (Morsels и его дочерние элементы), но средство проверки типов не позволяет мне выполнять какие-либо операции над два или более из этих созданных объектов, полагая, что они могут быть разными.
Как мне пройти эту проверку типов?
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
}
}
Компилятор генерирует следующую ошибку компиляции
Generator.scala:43: error: type mismatch;
found : v1.type (with underlying type mGen.Mg)
required: v0.M
v0 + v1 /// ERROR HERE
^ one error found
Обновить
Вот код C++, который более или менее эквивалентен тому, что я пытаюсь сделать. Обратите внимание, что функция eatTwo является полностью полиморфной и не ссылается на определенные производные типы Morsel или 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;
}
4 ответа
Ошибка имеет смысл, потому что в методе, где компиляция заканчивается неудачей, компилятор не может гарантировать, что вы не добавляете мороженое в хот-дог.
Метод + в HotDog помогает выделить проблему, и на самом деле вы не переопределили метод, а добавили новый:
def + (v : HotDog) : HotDog = new HotDog(v.calories + calories, v.length + length, v.width + width)
Вам явно нужно, чтобы добавляемый тип имел тот же тип, что и "this".
Определите Морселя как такового, и проблема почти решена:
trait Morsel {
def calories : Float
def + (v : Morsel) : Morsel
}
Последняя часть - правильно переопределить метод +:
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!")
}
Я не уверен, сможете ли вы заставить компилятор предотвратить добавление icecream и hotdogs, используя код в форме, которую вы предоставили.
Именно так работают типы элементов в Scala: они считаются равными только тогда, когда внешние объекты (известные компилятору) одинаковы. Один из вариантов - использовать параметры типа:
trait Morsel[M <: Morsel]
{
def calories : Float
def + (v : M) : M
}
trait MorselGenerator[Mg <: Morsel]
{
def generateMorsel : Mg
}
...
Одно из возможных решений (я заменил +
с add
здесь, чтобы держаться подальше от +(String, String)
, в конце, +
все в порядке):
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))
}
И небольшой вариант:
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
}
}
вероятно, менее полезно, но это может показать, где проблема. В Scala есть "зависимые от пути" типы, поэтому obj1.Type и obj2.Type - это разные типы, даже если obj1.type == obj2.type.