简体   繁体   中英

Object type inference in Scala while implementing traits

I'm working on a little generic tool, in which I need to have something like this:

  • An Operator trait, which will provide tools for operating elements

  • A Publisher trait, responsible for publishing a result represented by a Set() in the following example

  • A class which will implement operation traits

  • A companion object for this class, who will implement the publisher operations. It is mandatory for my design to keep the published results in the same trait as the publish operations

In a nutshell, I've got the following structure:

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

}

But, as you can imagine, I get the following error:

publishOper(res): Type mismatch, expected: A, actual: B

This raises several questions for me:

  1. How does type inference work when extending a companion object? (aka: Why does this happen?)

  2. How can I solve this, while trying to keep the same structure?

Use a shared publisher object, perhaps via an implicit. This is actually a bit more versatile as you can control scope now. Also this is testable, using the companion object as a singleton is likely not.

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

Global publisher is a bad idea as it will be non-typed then. But if you really need it - just don't bind your publisher to the concrete type:

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
  }
}

But this is a bad design. I'd recommend to define Oper as trait with external dependency:

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
    }
 }

Example:

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)

So now, depending on your context - you can choose the biggest type, that your publisher may work with (in my example it was Any ). The compiler will automatically check if it's fine with your Oper - that's why we need contravariant Publisher here.

PS Interesting note: 3.0 was casted to 3 automatically as expected existential type of collection was Int, so for my example there is no duplication Set(3,4, 2.0, 3.0) but strings of course will be different: 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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM