Я не уверен, что понимаю, что именно вы делаете с DimensionCons
и бизнесом DigitN
, но можно использовать Shapeless для написания метода, который будет принимать целое число (хотя только литеральная константа - оно должно быть известно во время компиляции) и иметь это целое число, определяющее статический тип возвращаемого значения.
Чтобы упростить ваш код ради полного рабочего примера, предположим, что мы хотим написать метод, подобный следующему:
trait Dimension
case class Dim1() extends Dimension
case class Dim2() extends Dimension
case class Dim3() extends Dimension
// And so on...
def getDimensionTpe[D <: Dimension](dim: Int) : D
… где getDimensionTpe(1)
вернет Dim1()
(статически набрано Dim1
), getDimensionTpe(2)
вернет Dim2()
и т. Д. Для этого мы можем ввести отображение класса типов между натуральными числами без формы и измерениями:
import shapeless.{ DepFn0, Nat }
trait DimMap[N <: Nat] extends DepFn0 {
type Out <: Dimension
}
object DimMap {
type Aux[N <: Nat, D <: Dimension] = DimMap[N] { type Out = D }
implicit val dimMap1: Aux[Nat._1, Dim1] = new DimMap[Nat._1] {
type Out = Dim1
def apply(): Dim1 = Dim1()
}
implicit val dimMap2: Aux[Nat._2, Dim2] = new DimMap[Nat._2] {
type Out = Dim2
def apply(): Dim2 = Dim2()
}
implicit val dimMap3: Aux[Nat._3, Dim3] = new DimMap[Nat._3] {
type Out = Dim3
def apply(): Dim3 = Dim3()
}
// And so on as needed.
}
Если вы располагаете большей структурой для своего типоразмера, возможно, здесь можно избежать шаблонного примера, но мне не ясно, как работает ваш DigitN
материал. Это кажется ортогональным к основному вопросу вопроса о том, как определить getDimensionTpe
.
Вы можете определить это как нечто с подписью, очень похожее на следующее, если вы не возражаете написать макрос белого ящика:
def getDimensionTpe[D <: Dimension](dim: Int) : D
Это огромная боль в заднице, однако, и Shapeless позволяет вам писать что-то, что по сути то же самое, без пользовательских макросов. Учитывая вышеприведенный класс DimMap
, вы можете написать следующее:
def getDimensionTpe(dim: Nat)(implicit m: DimMap[dim.N]) : m.Out = m()
А потом:
scala> val d1: Dim1 = getDimensionTpe(1)
d1: Dim1 = Dim1()
scala> val d2: Dim2 = getDimensionTpe(2)
d2: Dim2 = Dim2()
scala> val d3: Dim3 = getDimensionTpe(3)
d3: Dim3 = Dim3()
Если вы неправильно указали статический тип, компилятор скажет вам:
scala> val d3: Dim1 = getDimensionTpe(3)
<console>:15: error: type mismatch;
found : DimMap.dimMap3.Out
(which expands to) Dim3
required: Dim1
val d3: Dim1 = getDimensionTpe(3)
^
Если вы предоставляете целочисленный литерал без отображения, это также ошибка времени компиляции:
scala> getDimensionTpe(0)
<console>:14: error: could not find implicit value for parameter m: DimMap[shapeless._0]
getDimensionTpe(0)
^
И, наконец, если вы предоставите аргумент Int
, который не является целочисленным литералом, вы также получите ошибку компилятора:
scala> val x = 1
x: Int = 1
scala> getDimensionTpe(x)
<console>:16: error: Expression x does not evaluate to a non-negative Int literal
getDimensionTpe(x)
^
За кулисами Shapeless использует макрос, чтобы сделать это возможным, и даже это страшно - вы можете прочитать определение NatMacros
для деталей.