Проверка во время компиляции для векторного измерения

Я реализую некоторые легкие математические векторы в Scala. Я хотел бы использовать систему типов для проверки совместимости векторов во время компиляции. Например, попытка добавить вектор измерения 2 к другому вектору измерения 3 должна привести к ошибке компиляции.

До сих пор я определял размеры как классы дел:

sealed trait Dim
case class One() extends Dim
case class Two() extends Dim
case class Three() extends Dim
case class Four() extends Dim
case class Five() extends Dim

И вот определение векторов:

class Vec[D <: Dim](val values: Vector[Double]) {

  def apply(i: Int) = values(i)

  def *(k: Double) = new Vec[D]( values.map(_*k) )

  def +(that: Vec[D]) = {
    val newValues = ( values zip that.values ) map { 
      pair => pair._1 + pair._2
    }
    new Vec[D](newValues)
  }

  override lazy val toString = "Vec(" + values.mkString(", ") + ")"

}

Это решение работает хорошо, однако у меня есть две проблемы:

  • Как я могу добавить dimension():Int метод, который возвращает размерность (т.е. 3 для Vec[Three])?

  • Как я могу обрабатывать более высокие измерения, не объявляя все необходимые классы дел заранее?

PS: я знаю, что есть хорошие математические векторные библиотеки, я просто пытаюсь улучшить свое понимание scala.

3 ответа

Решение

Мои предложения:

Я предлагаю что-то вроде этого:

sealed abstract class Dim(val dimension:Int)

object Dim {
  class One extends Dim(1)
  class Two extends Dim(2)
  class Three extends Dim(3)

  implicit object One extends One
  implicit object Two extends Two
  implicit object Three extends Three
}

case class Vec[D <: Dim](values: Vector[Double])(implicit dim:D) {

  require(values.size == dim.dimension)

  def apply(i: Int) = values(i)

  def *(k: Double) = Vec[D]( values.map(_*k) )

  def +(that: Vec[D]) = Vec[D](
     ( values zip that.values ) map {
      pair => pair._1 + pair._2
  })

  override lazy val toString = values.mkString("Vec(",", ",")")
}

Конечно, вы можете получить только проверку во время выполнения на длину вектора таким образом, но, как уже отмечали другие, вам нужно что-то вроде церковных цифр или других методов программирования на уровне типов для выполнения проверок времени компиляции.

  import Dim._
  val a = Vec[Two](Vector(1.0,2.0))
  val b = Vec[Two](Vector(1.0,3.0))
  println(a + b)
  //--> Vec(2.0, 5.0) 

  val c = Vec[Three](Vector(1.0,3.0)) 
  //--> Exception in thread "main" java.lang.ExceptionInInitializerError
  //-->        at scalatest.vecTest.main(vecTest.scala)
  //--> Caused by: java.lang.IllegalArgumentException: requirement failed

Если вы не хотите идти по маршруту Пеано, у вас всегда может быть Vec быть построен с D а затем использовать экземпляр для определения размера с помощью Dim сопутствующий объект. Например:

object Dim {
  def dimensionOf(d : Dim) = d match {
    case One => 1
    case Two => 2
    case Three => 3
  }
}
sealed trait Dim

Я думаю, что по вашему выбору вы должны использовать case-объекты, а не case-классы:

case object One extends Dim
case object Two extends Dim

Тогда на вашем векторе вам, возможно, придется хранить Dim:

object Vec {
  def vec1 = new Vec[One](One)
  def vec2 = new Vec[Two](Two)
  def vec3 = new Vec[Three](Three)
}

class Vec[D <: Dim](d : D) {
  def dimension : Int = Dim dimensionOf d
  //etc
Другие вопросы по тегам