【问题标题】:Type mismatch for generic Integral in ScalaScala中通用积分的类型不匹配
【发布时间】:2014-02-05 04:42:42
【问题描述】:

我正在尝试在 Scala 中为 Base64 Variable Length Quanties 编写解码器。

(Base64 VLQ对有符号整数序列进行编码。编码对编码的整数范围没有限制。)

对于我当前的用例,我知道Seq[Int] 就足够了。当然,我想让它解码成Seq[Long],或Seq[BigInteger],甚至是Seq[Short]

算法完全一样,只是类型不同。

泛型来拯救! (或者我是这么认为的。)


object Base64Vlq {
  val continuationMask = (1 << 5).toByte
  val signMask = 1.toByte

  def decode[T <: Integral[T]](bytes: Seq[Byte]): Seq[T] = {
    val result = scala.collection.mutable.ListBuffer[T]()
    bytes.foldLeft((0, 0)) { (arg, byte) =>
      val (value, n) = arg
      if((byte & continuationMask) == 0) {
        val newValue = value + (byte / 2 & ~continuationMask) << 5 * n
        result += (if((byte & signMask) == 0) { newValue } else { -newValue })
        (0, 0)
      } else {
        (value + (byte & ~continuationMask).toInt << 5 * n, n + 1)
      }
    }
    result
  }
}

这有一个编译错误:

error: type mismatch;
found   : newValue.type (with underlying type Int)
required: T
       result += (if((byte & signMask) == 0) { newValue } else { -newValue })

我可以看到问题:0valuenewValue 都是 Int 类型。

我能做些什么来解决这个问题?


仅供参考,这就是我在 C++ 中做同样事情的方式,只需要多几行代码。

#include <vector>

template<typename T>
std::vector<T> decode(std::vector<char> bytes) {
    char continuationMask = 1 << 5;
    char signMask = 1;
    vector<T> result;
    int n(0);
    T value(0);
    for(auto it = bytes.begin(); it != bytes.end(); it++) {
        if(*it & continuationMask) {
            value += (*it & ~continuationMask) << 5 * n;
            n++;
        } else {
            value += (*it / 2 & ~continuationMask) << 5 * n;
            result.push_back(*it & signMask ? -value : value);
            value = 0;
            n = 0;
        }
    }
    return result;
}

附:附带说明一下,如果有人知道公共 Maven/Ivy 存储库中基于 Java/Scala 的 Base64 VLQ 实现,即使它不是通用的,我会很感激知道它。

【问题讨论】:

  • Scala 中的任何“通用”解决方案都会很慢。您可能会尝试使用专业化来获得更好的性能,但我不确定这会让您走多远。如果您需要速度,而这看起来需要,请保持非泛型。
  • @DanielC.Sobral,是的,Java 泛型很慢。 Alexey 的解决方案,即使有专门化,仍然会影响性能。

标签: scala generics encoding type-conversion


【解决方案1】:

绑定的T &lt;: Integral[T]T 必须是Integral[T]子类型,这是错误的;你需要一个 context bound T: Integral 或者(在这种情况下更好)一个隐式参数(实际上,一个 context bound 编译为一个隐式参数,唯一的区别是你不能给它一个名称)。

类似这样的东西(未经测试,只是检查它是否可以编译):

object Base64Vlq {
  val continuationMask = (1 << 5).toByte
  val signMask = 1.toByte

  def decode[T](bytes: Seq[Byte])(implicit ev: Integral[T]): Seq[T] = {
    val result = scala.collection.mutable.ListBuffer[T]()
    val tZero = ev.zero
    bytes.foldLeft((tZero, 0)) { (arg, byte) =>
      val (value, n) = arg
      if((byte & continuationMask) == 0) {
        val newValue = ev.plus(value, ev.fromInt((byte / 2 & ~continuationMask) << 5 * n))
        result += (if((byte & signMask) == 0) { newValue } else { ev.negate(newValue) })
        (tZero, 0)
      } else {
        (ev.plus(value, ev.fromInt((byte & ~continuationMask).toInt << 5 * n)), n + 1)
      }
    }
    result
  }
}

要使用,你必须说出你想出去的类型:

编辑:我没有在您的 C++ 代码中看到 nT 并假设它是 Int

评论的答案:

  1. Integral 这样的类型就是这样工作的:它们是 Scala 的 type classes 版本。如果要对现有类型引入新的操作,显然不能向它们添加新的超类型,因此会创建 Integral[Int]Integral[BigInt] 等的隐式实例。

  2. 编译器不知道T 使用什么类型,你需要告诉它:例如Base64Vlq.decode[Int](Seq[Byte]())Base64Vlq.decode[BigInt](Seq[Byte]())

【讨论】:

  • 您能否(1)详细说明为什么必须有Integral[T] 的实例,以及(2)如何使用它? Base64Vlq.decode(Seq[Byte]())error: ambiguous implicit values: both object BigIntIsIntegral in object Numeric of type object scala.math.Numeric.BigIntIsIntegral and object IntIsIntegral in object Numeric of type object scala.math.Numeric.IntIsIntegral match expected type Integral[T]
  • Oh duh...#2 应该很明显。谢谢!
  • 请注意,您可以使用一个导入来避免 Integral 类型类的笨拙语法并使用普通的中缀数学运算符(同时仍使用类型类来实现它们)。
【解决方案2】:

这并不能解决您的问题,但希望它可以帮助您编写通用解决方案。看看IntegralIntegralOps 提供的操作。问题在于位操作。如果您选择采用这种方式,则必须使用 Integral 和 IntegralOps 支持的操作重写它们。祝你好运。

object Base64Vlq {
  val continuationMask = (1 << 5).toByte
  val signMask = 1.toByte

  def decode[T](bytes: Seq[Byte])(implicit integral: Integral[T]): Seq[T] = {
    import integral._

    val result = scala.collection.mutable.ListBuffer[T]()
    bytes.foldLeft((integral.zero, integral.zero)) { (arg, byte) =>
      val (value, n) = arg
      if((byte & continuationMask) == integral.zero) {
        // Just a meaningless example calculation:
        result += integral.fromInt(2) + (value * n)

        (integral.zero, integral.zero)
      } else {
        // Just a meaningless example calculation:
        (value + integral.fromInt(3) * n, n + integral.fromInt(1))
      }
    }
    result
  }
}

【讨论】:

    猜你喜欢
    • 2013-09-03
    • 2012-08-27
    • 1970-01-01
    • 1970-01-01
    • 2019-04-15
    • 1970-01-01
    • 1970-01-01
    • 2016-07-03
    • 2017-02-25
    相关资源
    最近更新 更多