【问题标题】:Scala: Why does the following give a type mismatch error?Scala:为什么以下给出类型不匹配错误?
【发布时间】:2018-03-31 19:55:09
【问题描述】:

我有以下代码sn-p:

class Statistics[T, U <: NumberLike[T]] {
  def mean(xs: Vector[U]): U =
    xs.reduce(_ + _) / xs.size
}

trait NumberLike[T] {
  def + (that: NumberLike[T]): NumberLike[T]
  def / (that: Int): NumberLike[T]
}

编译时报错:

error: type mismatch;
 found   : NumberLike[T]
 required: U
    xs.reduce(_ + _) / xs.size
                ^
one error found

我不明白为什么编译器不接受这个;我已经定义了U 应该是NumberLike[T] 的子类。

【问题讨论】:

    标签: scala types


    【解决方案1】:

    错误消息准确地说明了问题所在:您的 mean 方法承诺返回 U,但它却返回了 NumberLike[T]

    你可以通过 F-bounded polymorphism 来解决它:

    class Statistics[T, U <: NumberLike[T, U]] {
      def mean(xs: Vector[U]): U =
        xs.reduce(_ + _) / xs.size
    }
    
    trait NumberLike[T, Res <: NumberLike[T, Res]] {
      def + (that: NumberLike[T, Res]): Res
      def / (that: Int): Res
    }
    

    这行得通,因为现在NumberLike 带有+/ 的精确返回类型。它现在保证返回一个Res,而不是返回一些未指定的NumberLike

    由于您似乎没有在任何地方使用T 类型,因此如果您完全省略T,实际上会更清楚:

    class Statistics[U <: NumberLike[U]] {
      def mean(xs: Vector[U]): U =
        xs.reduce(_ + _) / xs.size
    }
    
    trait NumberLike[U <: NumberLike[U]] {
      def + (that: NumberLike[U]): U
      def / (that: Int): U
    }
    

    请注意,现在+ 返回一个U,而不仅仅是一些不相关的NumberLike[T]


    实际上,您应该考虑将功能(加法、除法)与运算符的语法糖分开。您可以将实际计算移到单独的类型类中,并通过隐式类提供+/ 方法:

    /* The operations */
    trait NumberTypeclass[U] {
      def add(a: U, b: U): U
      def divideByInt(a: U, i: Int): U
    }
    
    /* The syntactic sugar */
    implicit class NumberOps[U: NumberTypeclass](u: U) {
      def +(other: U): U = implicitly[NumberTypeclass[U]].add(u, other)
      def /(i: Int): U = implicitly[NumberTypeclass[U]].divideByInt(u, i)
    }
    
    /* Usage example */
    class Statistics[U: NumberTypeclass] {
      def mean(xs: Vector[U]): U = {
        xs.reduce(_ + _) / xs.size
      }
    }
    

    乍一看,它有点冗长,但它的优点是可以省略复杂的 F 有界多态,并且可以为相同的功能提供不同的语法样式。类型类也往往比具有 F 有界多态性的复杂类层次结构更好。

    【讨论】:

    • 你提到类型类很有趣,因为我写了上面的代码作为我自己关于类型类为什么有用的学习练习。
    【解决方案2】:

    这个错误有点误导,但如果你仔细观察^+上,这意味着该操作的返回类型是错误的。

    问题在于+/ 是返回NumberLike[T] 的操作,但您需要U 作为mean 的返回类型。

    由于函数的返回类型是协变的,因此您不能返回 NumberLike,而应该返回 U

    考虑到这一点,您可以通过确保您的 NumberLike 操作返回更精确的类型来解决此问题:

    trait NumberLike[T] {
      def +[A <: NumberLike[T]](that: A): A
      def /[A <: NumberLike[T]](that: Int): A
    }
    

    现在你对mean 的定义应该可以编译了。

    【讨论】:

      【解决方案3】:

      reduce 中,预计二元运算符具有统一签名,在本例中为(U, U) =&gt; U,尽管在您的情况下为(U, NumberLike[T]) =&gt; NumberLike[T]

      即使你修复了这个特征,这也不起作用,因为mean+/ 中的预期结果类型。你告诉过它应该是mean 中的U,而NumberLike 中的+/ 方法返回NumberLike[T] 值,而不是它的子类型。 (Scala 不支持 MyTypes,但 this question 可能对您来说很有趣。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-01
        • 1970-01-01
        • 2012-02-16
        • 1970-01-01
        • 2021-06-27
        • 2010-12-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多