I am trying to create a specific and unique type given some variable parameter.
Listed below you find the prediined types representing the first four natural numbers (with an HList like approach of Digit0..Digit9, DimensionCons and EndDimension (like HNil)).
object Defined {
type D1 = DimensionCons[Digit1, EndDimension.type]
val d1 = DimensionCons(Digit1(), EndDimension)
type D2 = DimensionCons[Digit2, EndDimension.type]
val d2 = DimensionCons(Digit2(), EndDimension)
type D3 = DimensionCons[Digit3, EndDimension.type]
val d3 = DimensionCons(Digit3(), EndDimension)
type D4 = DimensionCons[Digit4, EndDimension.type]
val d4 = DimensionCons(Digit4(), EndDimension)
}
I am seeking for a method with an indicated signature like
def getDimensionTpe[D <: Dimension](dim: Int) : D
that e.g. returns DimensionCons[Digit2, EndDimension.type]
for getDimensionTpe(2)
.
Questions:
Thanks, Martin
So methods of generic or nongeneric classes can use generic types as argument and return types as well. Here are examples of those usages: // Not generic methods class GenericClass < T > { // method using generic class parameter type public void T cache ( T entry ) { ... } }
The syntax for a generic method includes a list of type parameters, inside angle brackets, which appears before the method's return type. For static generic methods, the type parameter section must appear before the method's return type.
Generic Methods A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
From the point of view of reflection, the difference between a generic type and an ordinary type is that a generic type has associated with it a set of type parameters (if it is a generic type definition) or type arguments (if it is a constructed type). A generic method differs from an ordinary method in the same way.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With