【问题标题】:Problem determining parameter type when calling a method调用方法时确定参数类型的问题
【发布时间】:2020-09-26 12:25:18
【问题描述】:

对于这个含糊不清或可能不正确的问题,我很抱歉,但我什至不知道如何用一句话来表达我的问题。

我有两个特点:

trait Property[T] {
  val name: String

  def conformsTo(rule: Rule[T]): Boolean
}

trait Rule[T] {
  def evaluate(valueToCheck: T): Boolean
}

我有一个实现Property 的具体类的列表和一个Rule 实现的映射,其中两个参数化类型都可以是DoubleString。 遍历此列表时,对于每个 Property 实例,我将具体的 Rule 实现传递给 conformsTo 方法,但是编译器会出现类型不匹配的错误。

所以从更广泛的意义上说,我想要实现的是拥有一个属性列表,可以根据一组给定的不同类型的规则进行评估,而不是将这些属性按类型拆分为单独的列表。

我尝试修改上限/下限和隐式类型,但最终一无所获。

完整代码:




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

}

错误:

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

感谢您的帮助。

【问题讨论】:

    标签: scala generics


    【解决方案1】:

    首先,根据错误消息,从Product with Serializable 扩展Rule 是有意义的

    https://typelevel.org/blog/2018/05/09/product-with-serializable.html

    然后错误信息变为:

    type mismatch;
     found   : App.Rule[_1(in value $anonfun)] where type _1(in value $anonfun) >: String with Double
     required: App.Rule[<root>._1]
    

    其次,你可以使Rule逆变

    trait Rule[-T] extends Product with Serializable {
      def evaluate(valueToCheck: T): Boolean
    }
    
    type mismatch;
     found   : App.Rule[String with Double]
     required: App.Rule[_1]
    

    Property协变

    trait Property[+T] {
      val name: String
      def conformsTo(rule: Rule[T]): Boolean
    }
    
    type mismatch;
     found   : App.Rule[String with Double]
     required: App.Rule[Any]
    

    问题是这不适用于Listattributes 的元素具有 Property[Double]Property[String] 类型。但是,当您将它们添加到列表中时,它们会丢失它们各自的类型并变成 Property[Any](如果 Scala 2 中有联合类型,则为 Property[Double | String])。

    同样,ruleMap 的值具有 Rule[Double]Rule[String] 类型。但是,当您将它们与一些键一起放入映射时,它们会丢失各自的类型,并变成 Rule[Double with String](甚至是 Rule[Nothing])。

    如果您没有修改 PropertyRule 的差异,那么类型将是 Property[_]Rule[_](实际上是 Property[_1]Rule[_2]),其中类型参数不对应于每个其他。

    conformsTo 的签名表明property.conformsTo(rule) 仅在参数具有Property[T]Rule[T] 类型为相同T 时编译,您无法保证。

    您可以尝试使用HList 代替ListHMap 代替Map 以保留单个类型的元素。但是为了使filter 工作,您应该知道ruleMap.contains(x.name) 在编译时是否。因此,您必须使键具有单例类型和特征 Property 以依赖于这另一个类型参数 trait Property[+T, S &lt;: 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


    编译代码的最简单方法是

    val nonConforming = attributes
      .filter(x => ruleMap.contains(x.name))
      .filter{
        case x: Property[t] => !x.conformsTo(ruleMap(x.name).asInstanceOf[Rule[t]])
      }
    

    【讨论】:

    • 我相信最简单的解决方案是为属性和规则的可能类型制作一个简单的 ADT。
    • @LuisMiguelMejíaSuárez 如果我们这样做filter{ case x: Property[t] =&gt; x.conformsTo(ruleMap(x.name).asInstanceOf[Rule[t]]) } 会怎样?
    • 我对类型模式真的不太了解,我相信这行得通,但在我看来它是不安全的。
    • 感谢您的回答。 @LuisMiguelMejíaSuárez - 你能详细说明一下,你的意思是代数数据类型吗?
    • @DmytroMitin 是的,正如我所说,您的替代方案可能是安全的,而且显然更短。但老实说,我会做你在 Scastie 所做的事情,因为这样我确信它是安全的。请注意,这样的决定是我个人的决定,主要是由于我的无知和(有点)害怕打字检查;不是说 OP 应该这样做。
    猜你喜欢
    • 2017-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-13
    • 2017-12-01
    • 1970-01-01
    • 2019-09-09
    • 1970-01-01
    相关资源
    最近更新 更多