在 scala 2.11+ 中如何使用单例类型作为类型证据?

In scala 2.11+ how to use exactly the singleton type as type evidence?

我正在编写一个用于复杂向量计算的数学库,其中一部分如下所示:

object Example1 {

  val two = 2
  val three = 3

  // SU means 'special unitary group'
  trait SU_n[D <: Int] {

    def plus(v1: ComplexVector[D], v2: ComplexVector[D])(
        implicit ev: D =:= two.type
    ): ComplexVector[D] = {
      //TODO: some unspeakable magic here
      ???
    }
  }

  class ComplexVector[D <: Int: ClassTag](xyzw: List[(Double, Double)]) {

    {
      assert(xyzw.size.isInstanceOf[D])
    }
  }

  type Quaternion = ComplexVector[two.type]

  val alsoTwo = 2

  object SU_2 extends SU_n[two.type] {}
  object SU_Also2 extends SU_n[alsoTwo.type] {}

  object SU_3 extends SU_n[three.type] {}

  val q = new Quaternion(List(1.0 -> 2.0, 3.0 -> 4.0))

  {
    val v2 = SU_2.plus(q, q)
    val also_v2 = SU_Also2.plus(q, q)

  }

  val vec =
    new ComplexVector[three.type](List(1.0 -> 2.0, 3.0 -> 4.0, 5.0 -> 6.0))

  // This will break
  //  {
  //    val v3 = SU_3.plus(vec, vec)
  //  }
}

中缀类型=:=用于确保函数plus不能用于除四元数以外的向量,当我编译它时,我得到以下错误信息:

Error: type mismatch;
 found   : <mypackage>.Example1.two.type (with underlying type Int)
 required: AnyRef
  type Quaternion = ComplexVector[two.type]

奇怪的是,我在中缀 class =:= 的实现中找不到任何地方要求它的操作数是 AnyVal,所以为什么我得到这个错误?以及如何fix/bypass它来达到要求? (即创建一个只能应用于四元数的函数)

.type 生成的单例类型并不像您想象的那样工作 - 特别是,它们不是文字类型。要查看此内容,请验证:

val two = 2
val alsoTwo = 2
type V = two.type
type W = alsoTwo.type
implicitly[V =:= W] // will fail with an implicit error
val test : V = 2 // will fail with "expression does not conform to type"
def id(x: V) : W = x //will fail with "expression does not conform to type"

从历史上看,此 .type 语法仅用于 AnyRef,编译器不会为原始类型公开单例类型。从 2.13 开始,这已经改变,语言现在支持文字类型,但看起来 .type 的行为仍然相同。

如果2.13是一个选项,你可以写

  trait SU_n[D <: Int] {

    def plus(v1: ComplexVector[D], v2: ComplexVector[D])(
      implicit ev: D =:= 2
    ): ComplexVector[D] = {
      //TODO: some unspeakable magic here
      ???
    }
  }

  type Quaternion = ComplexVector[2]

  object SU_2 extends SU_n[2] {}
  object SU_Also2 extends SU_n[2] {}

  object SU_3 extends SU_n[3] {}

一切都会按预期进行。如果您需要坚持使用 2.11 或 2.12,我建议查看 shapeless 是否这是您要走的路线。

也就是说,根据我的评论,这似乎是一种非常奇怪的解决问题的方法,因为具有特征 T[A] { def foo(a: A) } 的想法只有在 foo 时才会编译19=] 是一种特殊类型,让我觉得相当病态。如果你真的想要一个只能被四元数访问的 plus 方法,你可以做类似

implicit class QuaternionSupport(quaternions: SU_n[2]) {
  def plus(quaternion1: Quaternion, quaternion2: Quaternion) : Quaternion = ???
}

并且该方法不会出现在其他维度上。