实现特征时Scala中的对象类型推断

时间:2015-02-07 23:04:58

标签: scala object types traits inference

我正在研究一个小型通用工具,我需要这样的东西:

  • 运营商特征,它将提供操作元素的工具

  • 发布商特征,负责发布以下示例中由Set()表示的结果

  • 将实施操作特征的类

  • 此类的伴随对象,将实现发布者操作。我的设计必须使发布的结果与发布操作保持相同的特性

简而言之,我有以下结构:

trait Publisher[A]{
  var storage: Set[A] = Set[A]()
  def publishOper(elem: A) = storage += elem
}

trait Operator[A, B]{
  def operate(elem: A): B = ???
}


object Oper extends Publisher {

}

class Oper[A, B] extends Operator[A, B]{

  def publishOper(elem: A): B = {
    val res = operate(elem)
    publishOper(res)
  }

}

但是,正如您可以想象的那样,我收到以下错误:

  

publishOper(res):类型不匹配,预期:A,实际:B

这给我提出了几个问题:

  1. 扩展伴随对象时,类型推断如何工作? (又名:为什么会这样?)

  2. 如何在尝试保持相同结构的同时解决这个问题?

2 个答案:

答案 0 :(得分:0)

使用共享发布者对象,可能通过隐式。这实际上更加通用,因为您现在可以控制范围。这也是可测试的,使用伴侣对象作为单身可能不是。

implicit operPub = new Publisher[B] {
   // implementation...
}

class Oper[A, B](implicit publisher: Publisher[B]) extends Operator[A, B]{
  def publishOper(elem: A): B = {
    val res = operate(elem)
    publishOper(res)
  }
}

val a = new Oper[Int, String]
val b = new Oper[Int, String] // they both should get operPub

答案 1 :(得分:0)

全球发布商是一个坏主意,因为它将是非类型的。但如果你真的需要它 - 只是不要将你的出版商绑定到具体类型:

object Oper extends Publisher[Any]

class Oper[A, B] extends Operator[A, B]{

  def publishOper(elem: A): B = {
    val res = operate(elem)
    Oper.publishOper(res)
    res
  }
}

但这是一个糟糕的设计。我建议将Oper定义为具有外部依赖性的特征:

trait Publisher[-A]{ //"-" - if can store Any then can store Int
   type T >: A //to compensate covariant position for set or any other your internal providers; implementations without explicit T will produce existential type `_ >: A` for T to guarantee that storage will have a biggest A type
   var storage: Set[T] = Set[T]()
   def publishOper(elem: A) = storage += elem
}

trait Operator[A, B]{
    def operate(elem: A): B = elem.asInstanceOf[B] //just mock
}

trait Oper[A, B] extends Operator[A, B]{
    def publisher: Publisher[B]

    def publishOper(elem: A): B = {
       val res = operate(elem)
       publisher.publishOper(res)
       res
    }
 }

示例:

scala> val pbl = new Publisher[Any]{}
pbl: Publisher[Any] = $anon$1@49dbe5f0

scala> class Oper1 extends Oper[Int, Int] { val publisher: Publisher[Int] = pbl }
defined class Oper1

scala> new Oper1
res10: Oper1 = Oper1@4bd282fd

scala> res10.publishOper(3)
res11: Int = 3

scala> res10.publishOper(4)
res12: Int = 4

scala> res10.publisher.storage
res13: Set[res10.publisher.T] = Set(3, 4)

scala> class Oper2 extends Oper[Double, Double] { val publisher: Publisher[Double] = pbl }
defined class Oper2

scala> new Oper2
res14: Oper2 = Oper2@271f68d2

scala> res14.publishOper(2.0)
res15: Double = 2.0

scala> res14.publishOper(3.0)
res16: Double = 3.0

scala> res14.publisher.storage
res17: Set[res14.publisher.T] = Set(3, 4, 2.0)

现在,根据您的具体情况,您可以选择您的发布商可以使用的最大类型(在我的示例中为Any)。编译器会自动检查你的Oper是否正常 - 这就是我们在这里需要逆变Publisher的原因。

P.S。有趣的说明:3.0被自动转换为3,因为预期的存在类型的集合是Int,所以对于我的例子,没有重复Set(3,4, 2.0, 3.0)但是字符串当然会有所不同:Set(3, 4, 2.0, "3")

scala> class Oper3 extends Oper[String, String] { val publisher = pbl }
defined class Oper3

scala> new Oper3
res23: Oper3 = Oper3@f93893c

scala> res23.publishOper("3")
res38: String = 3

scala> res23.publisher.storage
res39: Set[res23.publisher.T] = Set(3.0, 4.0, 2.0, 3)