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:
How does type inference work when extending a companion object? (aka: Why does this happen?)
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.