В Scala 2.11+ как использовать в качестве доказательства типа именно синглтон-тип? - PullRequest
4 голосов
/ 19 июня 2019

Я пишу математическую библиотеку для сложных векторных вычислений, часть которой выглядит так:

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]

Странно то, что я не могу найти где-нибудь в реализации инфиксного класса =:=, который требует, чтобы его операнд был AnyVal, так почему я получаю эту ошибку? И как это исправить / обойти, чтобы добиться требования? (а именно, создать функцию, которая может быть применена только к кватерниону)

1 Ответ

3 голосов
/ 19 июня 2019

Синглтон-типы, создаваемые .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, я предлагаю посмотреть бесформенный , если вы хотите пойти по этому маршруту.

Тем не менее, согласно моему комментарию, это выглядит какочень странный способ решения проблемы, поскольку идея наличия черты T[A] { def foo(a: A) }, где вызов foo будет компилироваться, только если A - это определенный тип, кажется мне довольно патологической.Если вы действительно хотите, чтобы метод plus был доступен только для кватернионов, вы можете сделать что-то вроде

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

, и этот метод не будет присутствовать для других измерений.

...