Scala混入具有相同参数化类型的特质

4
我很新于Scala,正在努力适应Scala特定的模式。
目标是分离消息处理和消息生成。有一种基本的协变参数化类型表示消息处理。具体实现可以通过常规mixin或混合底层协议来组合。
要求如下:
1.尽可能简单地扩展 2.为了防止愚蠢的错误而安全
我提供了干净的样例代码(包含定义和使用):
trait Protocol

trait Handler [+proto <: Protocol] {
  def handle : PartialFunction[Protocol,Unit] 
  /* can not name the actual protocol type since handler for a subtype also fully supports handling supertype
besides any message extends other subtype ot the supertype since the scala use matching with case classes
and these algebraic type realization is seemed excluded from strait scala type system
  */
}

/*
==============
using scenario
==============
*/

trait SumMsg extends Protocol
case class Sum(op : Int) extends SumMsg
case class Sub(op : Int) extends SumMsg

trait ProdMsg extends Protocol
case class Mul(op : Int) extends ProdMsg
case class Diff(op : Int) extends ProdMsg {
  require (0 != op, "Division by zero is not permited")
}

/* stackable traites */
trait NullHandler {
  def handle : PartialFunction[Protocol,Unit] = { case _ => {} }
}

trait SumHandler extends Handler [SumMsg] with NullHandler{
  var store : Int
  abstract override def handle : PartialFunction[Protocol,Unit] = ({
case Sum(op) => { this.store += op}
case Sub(op) => { this.store -= op}
  }: PartialFunction[Protocol,Unit]) orElse super.handle
}

trait MulHandler extends Handler [ProdMsg] with NullHandler{
  var store : Int
  abstract override def handle : PartialFunction[Protocol,Unit] = ({
case Mul(op) => {this.store *= op}
case Diff(op) => {this.store /= op}
  }: PartialFunction[Protocol,Unit]) orElse super.handle
}

/* concrete classes */
class SumAccum (var store: Int) extends SumHandler

class MulAccum (var store: Int) extends MulHandler

class ArithmAccum (var store: Int) extends SumHandler with MulHandler

/* producers */
class ProduceSums (val accum : Handler [SumMsg]) {
  var state : Boolean = true
  def touch() = if (this.state)
{
  this.state = false
  this.accum.handle(Sum(2))
} else {
  this.state = true
  this.accum.handle(Sub(1))
}
}

class ProduceProds (val accum : Handler [ProdMsg]) {
  var state : Boolean = true
  def touch() = if (this.state)
{
  this.state = false
  this.accum.handle(Mul(2))
} else {
  this.state = true
  this.accum.handle(Diff(2))
}
}

/* tying together via cake pattern */
trait ProtocolComp {
  type Proto <: Protocol
}

trait ProducerComp { this: ProtocolComp =>
  type ProducerT <: {def touch()}
  def getProducer(accum : Handler[Proto]) : ProducerT
}

trait HandlerComp { this: ProtocolComp =>
  type HandlerT <: Handler[Proto]
  def getHandler(store:Int) : HandlerT
}

trait AppComp extends ProtocolComp with ProducerComp with HandlerComp {
  val initStore = 1
  def test() {
val handler = getHandler(initStore)
val producer = getProducer(handler)
producer.touch()
  }
}

/* different examples of compositions */

/* correct usage */

object One extends AppComp{
  override type Proto = SumMsg
  override type ProducerT = ProduceSums
  override type HandlerT = SumAccum
  override def getProducer(accum : Handler[Proto]) = new ProduceSums(accum)
  override def getHandler(store : Int) = new SumAccum(store)
}

object Two extends AppComp{
  override type Proto = SumMsg
  override type ProducerT = ProduceSums
  override type HandlerT = ArithmAccum
  override def getProducer(accum : Handler[Proto]) = new ProduceSums(accum)
  override def getHandler(store : Int) = new ArithmAccum(store)
}

object Three extends AppComp{
  override type Proto = SumMsg with ProdMsg
  override type ProducerT = ProduceSums
  override type HandlerT = ArithmAccum
  override def getProducer(accum : Handler[Proto]) = new ProduceSums(accum)
  override def getHandler(store : Int) = new ArithmAccum(store)
}

/* incorrect usage
static type checking protects from some kind of logic errors
*/

object Four extends AppComp{
  override type Proto = SumMsg
  override type ProducerT = ProduceProds
  override type HandlerT = SumAccum
  override def getProducer(accum : Handler[Proto]) = new ProduceProds(accum)
  override def getHandler(store : Int) = new SumAccum(store)
}

上一个例子没有很好地输入,如预期所示产生了错误:
mixed.scala:140: error: type mismatch;
found   : Handler[Four.Proto]
required: Handler[ProdMsg]
  override def getProducer(accum : Handler[Proto]) = new ProduceProds(accum)

我已经构建了一个灵活的系统,可以简单地组合和扩展,但尽可能地保持类型安全,使用Scala的case类而不是代数类型。

我几乎实现了我的目标,但遇到了一个大问题:底层JVM的类型擦除。我使用的结构对于Scala来说是非法的,因为我希望参数化的trait可以通过“with”子句进行扩展。

编译器报错:

mixed.scala:53: error: illegal inheritance;
class ArithmAccum inherits different type instances of trait Handler:
Handler[ProdMsg] and Handler[SumMsg]
class ArithmAccum (var store: Int) extends SumHandler with MulHandler

我有什么选择?我不能使用我设计的模式,需要通过可用性找到一个相等的替代品。是否有人可以提供另一种源代码解决方案?是否有scala插件(它们似乎存在于编译器中)或其他方法来将scala参数化类型的后端从Java通用代码更改为类似C ++的代码生成?


我有同样的问题。该死的JVM。 - iron9light
1个回答

2

你的问题不是JVM的类型擦除,而是Scala使用线性化来解决继承的问题。

从Handler中删除类型参数[+proto <: Protocol]。反正它在handle方法中没有被使用。然后你的非法继承错误就会消失。


但这样做会失去类型安全性。第四个示例将编译,但是实际上是非法的。 - ayvango
但是你的类型安全性实际上只是想象的。handle方法没有使用类型参数proto,因此你无法确保Handler[A]能够真正处理类型为A的协议。 - Ruediger Keller
另外,我认为你目前的解决方案并不像你想象的那样有效。在对象Three中,你定义了类型Proto = SumMsg with ProdMsg。这意味着它只接受既是Sums又是Prods的协议。但你可能想要的是Sums或Prods。如果我没记错的话,在Scala中你不能轻松地编码这样的类型。 - Ruediger Keller
处理协变情况,使Handler [SumMsg with ProdMsg]成为Handler [SumMsg]的子类型,并使用它是正确的。如果我在方法处理中暗示具体类型,则在这种情况下它不会是协变的。因此,我必须使用未经分类的 PartialFunction [Protocol,Unit] 而不是 PartialFunction [proto,Unit]。这个例子相当简洁,在真实代码中,我使用Akka的actors进行消息分发和Akka的接收类型签名(PartialFunction [Any,Unit])也是另一种限制。 - ayvango
抱歉,我没有考虑到Handler是协变的。但是你有没有考虑过这意味着你可以提供一个Handler [Sum],而你需要一个Handler [SumMsg]的情况?我无法想象这真的是你想要的。 - Ruediger Keller
是的,我已经考虑过了。这是一个很大的缺点,我不知道如何用更合适的东西替换 case classes。不允许使用 case class 而不是 protocol trait 进行参数化是通过协议强制执行的。我找不到适合的代数类型表示在 Scala 中。 - ayvango

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接