I'm not sure I understand what exactly you're doing with DimensionCons and the DigitN business, but it is possible to use Shapeless to write a method that will take a integer (although only a literal constant—it must be known at compile-time) and have that integer determine the static type of the return value.
To simplify your code for the sake of having a complete working example, suppose we want to write a method like the following:
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
…where getDimensionTpe(1) would return Dim1() (statically typed as Dim1), getDimensionTpe(2) would return Dim2(), etc. To do this we can introduce a type class mapping between Shapeless natural numbers and dimensions:
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.
}
If you have more structure for your dimension type, it might be possible to avoid the boilerplate here, but it's not clear to me how your DigitN stuff is working. That seems orthogonal to the main point of the question, though, about how to define getDimensionTpe.
You could define it as something with a signature very much like the following, if you didn't mind writing a white-box macro:
def getDimensionTpe[D <: Dimension](dim: Int) : D
That's a huge pain in the ass, though, and Shapeless lets you write something that's essentially the same without custom macros. Given the DimMap type class above, you can write the following:
def getDimensionTpe(dim: Nat)(implicit m: DimMap[dim.N]) : m.Out = m()
And then:
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()
If you get the static type wrong, the compiler will tell you:
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)
^
If you provide an integer literal without a mapping, that's also a compile-time error:
scala> getDimensionTpe(0)
<console>:14: error: could not find implicit value for parameter m: DimMap[shapeless._0]
getDimensionTpe(0)
^
And finally if you provide an Int argument that isn't an integer literal, you'll also get a compiler error:
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)
^
Behind the scenes Shapeless is using a macro to make this possible, and it's not even that scary—you can read the NatMacros definition for details.