不可变类的多态更新

时间:2014-02-10 21:36:14

标签: scala inheritance polymorphism abstract-type

我一直试图解决这个问题,但我似乎找不到解决这个问题的办法。我似乎无法在Scala中正确建模。

假设我有一个特性MyTrait,其中包含一些实现它的不可变类。

它看起来像这样:

trait MyTrait {
  type Repr <: MyTrait

  def substitute(original: Item, replacement: Item) : Repr

  def substituteAll(
    originals: List[Item],
    replacement: Item
  ) : Repr = {
    originals match {
      case head :: tail => substitute(head).substituteAll(tail, replacement)
      case Nil => this //this complains that this is not of type Repr
    }     
  }
}

trait MyTrait2 { ... }

case class MyClassA(originals: List[Item])
extends MyTrait with MyTrait2 {
  type Repr = MyClassA

  def substitute(original: Item, replacement: Item) : MyClassA = {
    //whatever code that updates the list etc. 

    MyClassA(newOriginals)
  }    
}

case class MyClassB(originals: List[Item])
extends MyTrait with MyTrait2 {
  type Repr = MyClassB

  def substitute(original: Item, replacement: Item) : MyClassB = {
    //whatever code that updates the list etc. 

    MyClassB(newOriginals)
  }    
}

case class CompoundClass(list : List[MyTrait with MyTrait2])
extends MyTrait {
  type Repr = CompoundClass

  def substitute(
    original: Item,
    replacement: Item
  ) : CompoundClass = 
    CompoundClass(list.map(
      myClass => myClass.substitute(original, replacement)
    ))
  )
  //the above complains that it is expecting
  // List[MyTrait with MyTrait2]
  //while in fact it is getting MyTrait2#Repr
}

如果我总结一下我的问题,他们将如下:

  • 通过super-trait方法更新不可变类应该返回相同类型的实现类。

  • 超级特质需要能够在有意义的时候返回this,而不是返回另一个对象。我似乎对此有疑问。

  • 我需要能够在不知道实际类型的情况下将超级特征传递给函数。标准多态性。那么函数就可以在没有的情况下调用trait的方法 知道实际的具体类型。

  • 我需要能够使用合成将类组合到其他组中。 (想象一下由子表达式组成的表达式)。它似乎是标准的组合,但是上面我用#Repr

  • 得到了这些错误

我最初尝试在MyTrait[T]中使用泛型类型,但这使得无法传递我想要的任何具体类。我现在正在尝试使用抽象类型,我似乎在编译时遇到了同样的问题。基本上我认为现在没有任何区别,我再次陷入同样的​​陷阱。

我做错了什么?我是以错误的方式看待这个吗?

1 个答案:

答案 0 :(得分:1)

这里有一些问题,其中一些与多态完全无关!

substituteAll方法开始:

trait MyTrait {
  type Repr <: MyTrait

  def substitute(original: Item, replacement: Item) : Repr

  def substituteAll(
    originals: List[Item],
    replacements: List[Item]
  ) : Repr = {
    originals match {
      case head :: tail => substitute(head).substituteAll(tail)
      case Nil => this
    }     
  }
}

substitutesubstituteAll都有两个参数,但你试图用一个参数调用它们。这永远不会奏效!

您还有一个问题,即编译器没有证据thisRepr。第一个问题可以很容易地解决,将两个输入压缩成单个元组列表然后使用内部函数,第二个问题可以通过手动提供证据来修复:

trait MyTrait {
  type Repr <: MyTrait

  def substitute(original: Item, replacement: Item) : Repr

  def substituteAll(
    originals: List[Item],
    replacements: List[Item]
  )(implicit typed: this.type => Repr) : Repr = {
    def loop(pairs: List[(Item, Item)]): Repr = pairs match {
      case (orig, rep) :: tail =>
        substitute(orig, rep)
        loop(tail)
      case Nil => typed(this) //this complains that this is not of type Repr
    }
    loop(originals zip replacements)
  }
}

您的下一个问题是,Repr类型参数不能保存您希望它为复合类型MyTrait with MyTrait2保留的内容。

这不是一个完整的类型,因为Repr param仍然是抽象的。您真正想要的是完全指定的类型MyTrait with MyTrait2 { type Repr = MyTrait with MyTrait2 }

鉴于这有点麻烦,引入另一个特征来表示它更容易:

trait CompoundElem extends MyTrait with MyTrait2 {
  type Repr <: CompoundElem
}

然后您可以在其余代码中使用它:

case class MyClassA(originals: List[Item]) extends CompoundElem {
  type Repr = MyClassA

  def substitute(original: Item, replacement: Item) : MyClassA = {
    val newOriginals = originals
    MyClassA(newOriginals)
  }
}

case class MyClassB(originals: List[Item]) extends CompoundElem {
  type Repr = MyClassB

  def substitute(original: Item, replacement: Item) : MyClassB = {
    val newOriginals = originals
    MyClassB(newOriginals)
  }
}

case class CompoundClass(list : List[CompoundElem]) extends MyTrait {
  type Repr = CompoundClass

  def substitute(
    original: Item,
    replacement: Item
  ) = CompoundClass(
   list.map( _.substitute(original, replacement) )
  )  
}

如果你想让元素类型更长一些,那么最后一个类也可以写成:

object MyTrait {
  //type alias helper to view the type member as though it were a param
  //A neat trick, shamelessly borrowed from the shapeless library
  type Aux[R] = MyTrait { type Repr = R }
}

case class CompoundClass[E <: MyTrait.Aux[E]](list : List[E]) extends MyTrait {
  type Repr = CompoundClass[E]

  def substitute(
    original: Item,
    replacement: Item
  ) = CompoundClass(
    list.map( _.substitute(original, replacement) )
  )  
}

以这种方式编写,对于大多数情况,您也不需要中间特征。例如,包含CompoundClass列表的ClassA将正常工作。

如果你想要ListClassAClassB,那么你仍然需要中间体来帮助编译器准确解释Repr应该是什么是的,它无法计算Repr的{​​{1}}也应该是ClassA with ClassB

如果你不介意完全删除元素的类型,你可以这样做:

ClassA with ClassB

它只是case class CompoundClass(list : List[MyTrait]) extends MyTrait { type Repr = CompoundClass def substitute( original: Item, replacement: Item ) = CompoundClass( list.map( _.substitute(original, replacement) ) ) } 的{​​{1}},当您拉动元素以将其视为CompoundClassMyTraits时,您需要使用模式匹配或ClassA,但您不需要任何中间特征。


最后......作为参考,这里使用F-Bounds重新实现了相同的想法。请注意类型参数如何允许ClassB,因此您无需在所有子类中明确定义MyTrait2

self type