Reputation: 1
I'm sorry for the vague or possibly incorrect question, but I'm not even sure how to state my problem in one sentence.
I have 2 traits:
trait Property[T] {
val name: String
def conformsTo(rule: Rule[T]): Boolean
}
and
trait Rule[T] {
def evaluate(valueToCheck: T): Boolean
}
I have a list of concrete classes implementing Property
and a map of Rule
implementations, where both parameterized types can either be a Double
or a String
.
When traversing this list, for each Property
instance I pass in a concrete Rule
implementation to the conformsTo
method, the compiler however errors with a type mismatch.
So in a broader sense, what I'm trying to achieve is to have a list of attributes that can be evaluated based on a given set of rules of different types, without splitting these attributes into separate lists by type.
I tried tinkering with upper/lower bounds and implicit types but ended up with nothing.
The complete code:
trait Rule[T] {
def evaluate(valueToCheck: T): Boolean
}
case class GreaterThan(value: Double) extends Rule[Double] {
override def evaluate(valueToCheck: Double): Boolean = {
valueToCheck > this.value
}
}
case class LessThan(value: Double) extends Rule[Double] {
override def evaluate(valueToCheck: Double): Boolean = {
valueToCheck < this.value
}
}
case class Is(value: String) extends Rule[String] {
override def evaluate(valueToCheck: String): Boolean = {
valueToCheck == this.value
}
}
case class isNot(value: String) extends Rule[String] {
override def evaluate(valueToCheck: String): Boolean = {
valueToCheck != this.value
}
}
trait Property[T] {
val name: String
def conformsTo(rule: Rule[T]): Boolean
}
case class DoubleProperty(name: String, value: Double) extends Property[Double] {
override def conformsTo(rule: Rule[Double]): Boolean = rule.evaluate(value)
}
case class StringProperty(name: String, value: String) extends Property[String] {
override def conformsTo(rule: Rule[String]): Boolean = rule.evaluate(value)
}
object Evaluator extends App {
val ruleMap = Map(
"name1" -> GreaterThan(123),
"name1" -> LessThan(500),
"name2" -> GreaterThan(1000),
"name3" -> Is("something"))
val numericProperty = DoubleProperty("name1", 600)
val numericProperty2 = DoubleProperty("name2", 1000)
val stringProperty = StringProperty("name3", "something")
val stringProperty2 = StringProperty("name4", "something")
val attributes = List(
numericProperty,
numericProperty2,
stringProperty,
stringProperty2)
val nonConforming = attributes
.filter(x => ruleMap.contains(x.name))
.filter(x => !x.conformsTo(ruleMap(x.name))).toList
}
the error:
type mismatch;
found : Product with Rule[_ >: String with Double] with java.io.Serializable
required: Rule[(some other)<root>._1]
Note: Any >: _1 (and Product with Rule[_ >: String with Double] with java.io.Serializable <: Rule[_ >: String with Double]), but trait Rule is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
.filter(x => !x.conformsTo(ruleMap(x.name))).toList
Thank you for any help.
Upvotes: 0
Views: 92
Reputation: 51658
Firstly, based on error message it can make sense to extend Rule
from Product with Serializable
https://typelevel.org/blog/2018/05/09/product-with-serializable.html
Then error message changes to:
type mismatch;
found : App.Rule[_1(in value $anonfun)] where type _1(in value $anonfun) >: String with Double
required: App.Rule[<root>._1]
Secondly, you can make Rule
contravariant
trait Rule[-T] extends Product with Serializable {
def evaluate(valueToCheck: T): Boolean
}
type mismatch;
found : App.Rule[String with Double]
required: App.Rule[_1]
and Property
covariant
trait Property[+T] {
val name: String
def conformsTo(rule: Rule[T]): Boolean
}
type mismatch;
found : App.Rule[String with Double]
required: App.Rule[Any]
The thing is that this not gonna work with List
. Elements of attributes
have types Property[Double]
and Property[String]
. But when you add them to a list they lost their individual types and become just Property[Any]
(Property[Double | String]
if there were union types in Scala 2).
Similarly, values of ruleMap
have types Rule[Double]
and Rule[String]
. But when you put them with some keys into a map they lost their individual types and become just Rule[Double with String]
(or even Rule[Nothing]
).
If you didn't modify variances of Property
and Rule
then the types would be Property[_]
and Rule[_]
(actually, Property[_1]
and Rule[_2]
) where type parameters don't correspond to each other.
But the signature of conformsTo
says that property.conformsTo(rule)
compiles only when arguments have types Property[T]
and Rule[T]
for the same T
, which you can't guarantee.
You could try to use HList
instead of List
and HMap
instead of Map
in order to keep individual types of elements. But then in order to make filter
work you should know whether ruleMap.contains(x.name)
at compile time. So you would have to make keys have singleton types and trait Property
to depend on this one more type parameter trait Property[+T, S <: String with Singleton] { val name: S ...
Why Does This Type Constraint Fail for List[Seq[AnyVal or String]]
Scala: verify class parameter is not instanceOf a trait at compile time
How to make a typeclass works with an heterogenous List in scala
Use the lowest subtype in a typeclass?
flatMap with Shapeless yield FlatMapper not found
The easiest way to make your code compile is
val nonConforming = attributes
.filter(x => ruleMap.contains(x.name))
.filter{
case x: Property[t] => !x.conformsTo(ruleMap(x.name).asInstanceOf[Rule[t]])
}
Upvotes: 1