Логический тип Scala не замечает, что эти типы идентичны, какими бы они ни были - PullRequest
6 голосов
/ 18 февраля 2012

У меня здесь есть шаблон проектирования, в котором есть генератор объектов (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 ]

4 голосов
/ 19 февраля 2012

Ошибка имеет смысл, потому что в методе, где компиляция заканчивается неудачей, компилятор не может гарантировать, что вы не добавляете мороженое в хот-дог.

Метод + в 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!")
}

Я не уверен, что вы можете заставить компилятор предотвратить добавление мороженого и хот-догов, используя код в предоставленной вами форме.

2 голосов
/ 18 февраля 2012

Именно так работают типы элементов в Scala: они считаются равными только тогда, когда внешние объекты (как известно компилятору) одинаковы.Вместо этого можно использовать параметры типа:

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

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

...
0 голосов
/ 23 февраля 2012

И небольшой другой вариант:

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.

0 голосов
/ 23 февраля 2012

Одно из возможных решений (я заменил + на 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))
}
...