【问题标题】:Why can't I convert this type into a generic one?为什么我不能将这种类型转换为通用类型?
【发布时间】:2018-11-04 20:05:25
【问题描述】:
def linearInterpolation(weights: Seq[Double], points: Seq[Seq[Double]]) : Seq[T] = {

  weights.zip(points).map(
    weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate)
  ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2).asInstanceOf[T]))

}

在这段代码中,我试图将返回的类型 Seq[Double] 转换为 Seq[T]。在执行调用时,T 可以是例如Double 或 Int。

这个转换应该是感谢.asInstanceOf[T]实现的。

错误

代码编译。

但如果我执行它,我会收到以下错误:

错误:(25, 61) 类型不匹配; 发现:(Seq[Double], Seq[Double]) => Seq[T] 必需: (Seq[Any], Seq[Any]) => Seq[Any] ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2).asInstanceOf[T]))

错误:(25, 13) 类型不匹配; 发现:序列[任何] 必需:序列[T] ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2).asInstanceOf[T]))

问题

为什么执行失败?如何实现从Seq[Double] 到Seq[T] 的这种转换?

【问题讨论】:

    标签: scala generics type-conversion generic-programming generic-collections


    【解决方案1】:

    首先,不,此代码无法编译:这些是编译错误,而不是运行时异常。可以看到,因为它以Error: 开头,指向源代码中的确切位置(异常只有行号),并且没有堆栈跟踪。

    现在,如果它确实编译了,它将无法工作,但这是一个单独的问题。

    那为什么不编译呢? weights.zip(points).map(...) 的类型是Seq[Seq[Double]],所以reduce 的签名就变成了reduce[A1 >: Seq[Double]](op: (A1, A1) => A1): A1。请注意,reduce 的参数的返回和参数类型必须匹配,而在你的情况下它们不匹配(你有(Seq[Double], Seq[Double]) => Seq[T])。就其本身而言,不编译就足够了。

    整个weights.zip(points).map(...).reduce(...)的预期类型是Seq[T],所以编译器需要选择A1,即:

    1. Seq[Double] 的超类型以满足约束

    2. Seq[T] 的子类型以使返回类型匹配

    这样的类型不存在(对T 没有额外的限制),但如果它存在,它将是Seq[SomeType],这是编译器应该得到的。为什么它最终显示Any,我真的不明白。

    如何实现从 Seq[Double] 到 Seq[T] 的这种转换?

    如果你有weights: Seq[T], points: Seq[Seq[T]] 会更有意义。在这种情况下,请使用Numeric。 Stack Overflow 和外部有很多答案解释了如何,例如Scala equivalent of Java's Number.

    对于weights: Seq[Double], points: Seq[Seq[Double]],我只需添加一个函数Double => T 作为额外参数:

    def linearInterpolation(weights: Seq[Double], points: Seq[Seq[Double]])(fromDouble: Double => T) : Seq[T] = {
    
      weights.zip(points).map(
        weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate)
      ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => coordinate_points._1 + coordinate_points._2)).map(fromDouble)
    
    }
    

    【讨论】:

    • 感谢您非常完整的回答!但是,您的最后一个代码意味着定义 fromDouble (即:定义一些转换器)。相反,您认为有可能声明 weights : Seq[Numeric]、points : Seq[Numeric] 并返回我的函数 Numeric 吗? Numeric 或其他。在我的通话中,我可以将Double 用于第一个,Int 用于后者:转换将是自动的。因此,我的函数的返回是可以的,reduce 将有参数的类型 = 返回的类型。
    • "在我的通话中,我可以将 Double 用于第一个,Int 用于后者:转换将是自动的"数字本身是不够的,你需要添加额外的隐式转换,但您的算法确实不太适合那里。它自然会提供与输入相同类型的输出。至少如果 * 和 + 像在 Scala 中那样保留类型。
    • 您可以使用@Gabor Kolok 回答中的方法做到这一点。
    【解决方案2】:

    我增加了 Alexey 的回答。 我会将 fromDouble 声明为隐式并将转换器函数创建为隐式值,您可以按照您的设计使用您的函数。 例如:

    object DoubleConverters {
      implicit val doubleToDouble: Double => Double = identity
      implicit val doubleToInt: Double => Int = _.round.toInt
    }
    
    def linearInterpolation[T](weights: Seq[Double], points: Seq[Seq[Double]])(implicit converter: Double => T) : Seq[T] =
      weights.zip(points)
         .map(weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate))
         .reduce((point_a: Seq[Double], point_b: Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2)))
         .map(converter)
    
    // sample call
    val result = linearInterpolation[Double]( weights = Seq(1.1, 1.3), Seq(Seq(1.1, 1.2), Seq(1.3, 1.4)))
    

    // 示例调用 val 结果 = linearInterpolation[Double]( weights = Seq(1.1, 1.3), Seq(Seq(1.1, 1.2), Seq(1.3, 1.4)))

    【讨论】:

    • 但请注意implicit Double => Int 很容易意外使用,并且在您不想使用时会丢失精度。隐含的T => T(包括Double => Double)也已在Predef 中可用。
    • 是的,不需要 doubleToDouble。如果不小心使用了隐式 Double => Int,我们可以为此创建一个类型类。当然还有几行。
    【解决方案3】:

    所以与类型类的效果相同:

    trait DoubleConverter[T] {
        def convert(d: Double): T
    }
    
    object DoubleConverter {
        implicit val doubleToInt: DoubleConverter[Int] = new DoubleConverter[Int] {
            override def convert(d: Double): Int = d.round.toInt
        }
    
        implicit val doubleToDouble: DoubleConverter[Double] = new DoubleConverter[Double] {
            override def convert(d: Double): Double = d
        }
    }
    
    def linearInterpolation[T](weights: Seq[Double], points: Seq[Seq[Double]])(implicit converter: DoubleConverter[T]) : Seq[T] =
        weights.zip(points)
           .map(weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate))
           .reduce((point_a: Seq[Double], point_b: Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2)))
           .map(converter.convert)
    
    // sample call
    val result = linearInterpolation[Int]( weights = Seq(1.1, 1.3), Seq(Seq(1.1, 1.2), Seq(1.3, 1.4)))
    

    看到用法保持不变。我们避免了意外的 Double => Int 用法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-18
      • 1970-01-01
      • 2021-02-14
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多