Проверка во время компиляции для векторного измерения
Я реализую некоторые легкие математические векторы в 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 ответа
Мои предложения:
- Peano Numbers от Apocalysp (ссылка на часть a, остальные части следуют)
- Церковные цифры Джим МакБит
Я предлагаю что-то вроде этого:
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