Scala类型推断未能注意到这些类型是相同的,无论它们是什么

时间:2012-02-18 18:15:54

标签: scala type-inference abstract-type static-polymorphism dependent-type

我在这里有一个设计模式,其中有一个对象生成器(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 个答案:

答案 0 :(得分:4)

错误是有道理的,因为在编译失败的方法中,编译器无法保证您不会将冰淇淋添加到热狗中。

HotDog中的+方法有助于突出显示问题,实际上您没有覆盖该方法,而是添加了一个新方法:

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

您明确需要添加的类型与“this”具有相同的类型。

定义Morsel,问题几乎解决了:

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

我不确定你是否可以使用你提供的表格中的代码来防止添加冰淇淋和热狗。

答案 1 :(得分:2)

这就是成员类型在Scala中的工作方式:当外部对象(编译器已知)相同时,它们才被认为是相同的。一种选择是使用类型参数:

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

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

...

答案 2 :(得分:0)

其中一个可能的解决方案(我已将+替换为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))
}

答案 3 :(得分:0)

还有另一种变体:

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也是不同的类型。