【问题标题】:Scala - Enforcing size of Vector at compile timeScala - 在编译时强制执行 Vector 的大小
【发布时间】:2013-02-06 23:45:42
【问题描述】:

是否可以在编译时强制传递给方法的Vector 的大小?我想使用空间中看起来像这样的点集合来建模一个 n 维欧几里得空间(这就是我现在所拥有的):

case class EuclideanPoint(coordinates: Vector[Double]) {
  def distanceTo(desination: EuclieanPoint): Double = ???
}

如果我有一个通过EuclideanPoint(Vector(1, 0, 0)) 创建的坐标,则它是一个 3D 欧几里得点。鉴于此,我想确保在对 distanceTo 的调用中传递的目标点具有相同的维度。

我知道我可以通过使用Tuple1Tuple22 来做到这一点,但是我想表示许多不同的几何空间,如果我使用Tuples 的话,我将为每个空间编写 22 个类 - 有吗更好的方法?

【问题讨论】:

  • 我不能有意识地把它作为一个答案,但它可能符合一个想法......我想到的第一件事是结合一个值类(2.10 中的新功能)使用路径相关类型来获取表示 s 特定整数的类型。我真的不知道这是否可以奏效。当一天的工作结束时,我可能会尝试一下...参见 SIP 15:docs.scala-lang.org/overviews/core/value-classes.html
  • 这种约束可以用“类型级编程”编码。例如,参见Apocalisp blog series,尤其是 HList。

标签: scala tuples compile-time type-systems


【解决方案1】:

可以通过多种方式执行此操作,这些方式都或多或少类似于 Randall Schulz 在评论中描述的内容。 Shapeless library 提供了一个特别方便的实现,它可以让你得到非常接近你想要的东西:

import shapeless._

case class EuclideanPoint[N <: Nat](
   coordinates: Sized[IndexedSeq[Double], N] { type A = Double }
) {
  def distanceTo(destination: EuclideanPoint[N]): Double = 
    math.sqrt(
      (this.coordinates zip destination.coordinates).map {
        case (a, b) => (a - b) * (a - b)
      }.sum
    )
}

现在您可以编写以下内容:

val orig2d = EuclideanPoint(Sized(0.0, 0.0))
val unit2d = EuclideanPoint(Sized(1.0, 1.0))

val orig3d = EuclideanPoint(Sized(0.0, 0.0, 0.0))
val unit3d = EuclideanPoint(Sized(1.0, 1.0, 1.0))

还有:

scala> orig2d distanceTo unit2d
res0: Double = 1.4142135623730951

scala> orig3d distanceTo unit3d
res1: Double = 1.7320508075688772

但不是:

scala> orig2d distanceTo unit3d
<console>:15: error: type mismatch;
 found   : EuclideanPoint[shapeless.Nat._3]
 required: EuclideanPoint[shapeless.Nat._2]
              orig2d distanceTo unit3d
                                ^

Sized 带有许多不错的功能,包括一些带有关于长度的静态保证的集合操作。例如,我们可以这样写:

val somewhere = EuclideanPoint(Sized(0.0) ++ Sized(1.0, 0.0))

并且在三维空间中拥有一个普通的老点。

【讨论】:

  • 太棒了,谢谢!快速提问 - 是否可以将 IndexedSeq[Double] 转换为 Sized 版本?我正在挖掘代码,但无法弄清楚。
  • 问题:Shapeless 是否需要预发布/快照/新生的 Scala 2.11?因为当我检索并构建它时,它使用了 2.11 快照 Scala。 (另外,它不能编译,但我不确定那是关于什么的......)
  • 不,您可以通过 SBT 或 Maven 依赖项或通过查看 shapeless-1.2.3 标记并构建它来获得 2.10.0(或 2.9.2)的 Shapeless 1.2.3 版本.
  • @adelbertc:您可以使用Sized.wrap,但请注意,您必须在类型级别指定长度,然后您有责任确保这是正确的——如果不是,所有从那时起,您获得的保证几乎毫无价值。
【解决方案2】:

您可以通过对自然数进行类型级编码来自己做一些事情,例如:http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/。然后只需通过自然参数化您的矢量。不需要额外的依赖,但可能会比使用 Shapeless 更复杂。

【讨论】:

    猜你喜欢
    • 2017-01-14
    • 1970-01-01
    • 2014-11-05
    • 2015-05-19
    • 1970-01-01
    • 2021-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多